summaryrefslogtreecommitdiff
path: root/misc/makerelease/makerelease.go
diff options
context:
space:
mode:
Diffstat (limited to 'misc/makerelease/makerelease.go')
-rw-r--r--misc/makerelease/makerelease.go1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/misc/makerelease/makerelease.go b/misc/makerelease/makerelease.go
new file mode 100644
index 000000000..2496a865a
--- /dev/null
+++ b/misc/makerelease/makerelease.go
@@ -0,0 +1,1030 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This is a tool for packaging binary releases.
+// It supports FreeBSD, Linux, NetBSD, OpenBSD, OS X, and Windows.
+package main
+
+import (
+ "archive/tar"
+ "archive/zip"
+ "bufio"
+ "bytes"
+ "compress/gzip"
+ "crypto/sha1"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/url"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+
+ "code.google.com/p/goauth2/oauth"
+ "code.google.com/p/google-api-go-client/storage/v1beta2"
+)
+
+var (
+ tag = flag.String("tag", "release", "mercurial tag to check out")
+ toolTag = flag.String("tool", defaultToolTag, "go.tools tag to check out")
+ tourTag = flag.String("tour", defaultTourTag, "go-tour tag to check out")
+ repo = flag.String("repo", "https://code.google.com/p/go", "repo URL")
+ verbose = flag.Bool("v", false, "verbose output")
+ upload = flag.Bool("upload", false, "upload resulting files to Google Code")
+ addLabel = flag.String("label", "", "additional label to apply to file when uploading")
+ includeRace = flag.Bool("race", true, "build race detector packages")
+ versionOverride = flag.String("version", "", "override version name")
+ staticToolchain = flag.Bool("static", true, "try to build statically linked toolchain (only supported on ELF targets)")
+ tokenCache = flag.String("token", defaultCacheFile, "Authentication token cache file")
+ storageBucket = flag.String("bucket", "golang", "Cloud Storage Bucket")
+ uploadURL = flag.String("upload_url", defaultUploadURL, "Upload URL")
+
+ defaultCacheFile = filepath.Join(os.Getenv("HOME"), ".makerelease-request-token")
+ defaultUploadURL = "http://golang.org/dl/upload"
+)
+
+const (
+ blogPath = "code.google.com/p/go.blog"
+ toolPath = "code.google.com/p/go.tools"
+ tourPath = "code.google.com/p/go-tour"
+ defaultToolTag = "release-branch.go1.2"
+ defaultTourTag = "release-branch.go1.2"
+)
+
+// Import paths for tool commands.
+// These must be the command that cmd/go knows to install to $GOROOT/bin
+// or $GOROOT/pkg/tool.
+var toolPaths = []string{
+ "code.google.com/p/go.tools/cmd/cover",
+ "code.google.com/p/go.tools/cmd/godoc",
+ "code.google.com/p/go.tools/cmd/vet",
+}
+
+var preBuildCleanFiles = []string{
+ "lib/codereview",
+ "misc/dashboard/godashboard",
+ "src/cmd/cov",
+ "src/cmd/prof",
+ "src/pkg/exp",
+ "src/pkg/old",
+}
+
+var cleanFiles = []string{
+ ".hg",
+ ".hgtags",
+ ".hgignore",
+ "VERSION.cache",
+}
+
+var sourceCleanFiles = []string{
+ "bin",
+ "pkg",
+}
+
+var tourPackages = []string{
+ "pic",
+ "tree",
+ "wc",
+}
+
+var tourContent = []string{
+ "content",
+ "solutions",
+ "static",
+ "template",
+}
+
+var blogContent = []string{
+ "content",
+ "template",
+}
+
+// The os-arches that support the race toolchain.
+var raceAvailable = []string{
+ "darwin-amd64",
+ "linux-amd64",
+ "windows-amd64",
+}
+
+// The OSes that support building statically linked toolchain
+// Only ELF platforms are supported.
+var staticLinkAvailable = []string{
+ "linux",
+ "freebsd",
+ "openbsd",
+ "netbsd",
+}
+
+var fileRe = regexp.MustCompile(`^(go[a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+)(?:-([a-z0-9.]+))?)\.(tar\.gz|zip|pkg|msi)$`)
+
+// OAuth2-authenticated HTTP client used to make calls to Cloud Storage.
+var oauthClient *http.Client
+
+// Builder key as specified in ~/.gobuildkey
+var builderKey string
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, "usage: %s [flags] targets...\n", os.Args[0])
+ flag.PrintDefaults()
+ os.Exit(2)
+ }
+ flag.Parse()
+ if flag.NArg() == 0 {
+ flag.Usage()
+ }
+ if runtime.GOOS == "windows" {
+ checkWindowsDeps()
+ }
+
+ if *upload {
+ if err := readCredentials(); err != nil {
+ log.Fatalln("readCredentials:", err)
+ }
+ if err := setupOAuthClient(); err != nil {
+ log.Fatalln("setupOAuthClient:", err)
+ }
+ }
+ for _, targ := range flag.Args() {
+ var b Build
+ if m := fileRe.FindStringSubmatch(targ); m != nil {
+ // targ is a file name; upload it to googlecode.
+ version := m[1]
+ if m[2] == "src" {
+ b.Source = true
+ } else {
+ b.OS = m[3]
+ b.Arch = m[4]
+ b.Label = m[5]
+ }
+ if !*upload {
+ log.Printf("%s: -upload=false, skipping", targ)
+ continue
+ }
+ if err := b.Upload(version, targ); err != nil {
+ log.Printf("uploading %s: %v", targ, err)
+ }
+ continue
+ }
+ if targ == "source" {
+ b.Source = true
+ } else {
+ p := strings.SplitN(targ, "-", 3)
+ if len(p) < 2 {
+ log.Println("Ignoring unrecognized target:", targ)
+ continue
+ }
+ b.OS = p[0]
+ b.Arch = p[1]
+ if len(p) >= 3 {
+ b.Label = p[2]
+ }
+ if *includeRace {
+ for _, t := range raceAvailable {
+ if t == targ || strings.HasPrefix(targ, t+"-") {
+ b.Race = true
+ }
+ }
+ }
+ if *staticToolchain {
+ for _, os := range staticLinkAvailable {
+ if b.OS == os {
+ b.static = true
+ }
+ }
+ }
+ }
+ if err := b.Do(); err != nil {
+ log.Printf("%s: %v", targ, err)
+ }
+ }
+}
+
+type Build struct {
+ Source bool // if true, OS and Arch must be empty
+ Race bool // build race toolchain
+ OS string
+ Arch string
+ Label string
+ root string
+ gopath string
+ static bool // if true, build statically linked toolchain
+}
+
+func (b *Build) Do() error {
+ work, err := ioutil.TempDir("", "makerelease")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(work)
+ b.root = filepath.Join(work, "go")
+ b.gopath = work
+
+ // Clone Go distribution and update to tag.
+ _, err = b.hgCmd(work, "clone", *repo, b.root)
+ if err != nil {
+ return err
+ }
+ _, err = b.hgCmd(b.root, "update", *tag)
+ if err != nil {
+ return err
+ }
+
+ // Remove exp and old packages.
+ if err := b.clean(preBuildCleanFiles); err != nil {
+ return err
+ }
+
+ src := filepath.Join(b.root, "src")
+ if b.Source {
+ if runtime.GOOS == "windows" {
+ log.Print("Warning: running make.bash on Windows; source builds are intended to be run on a Unix machine")
+ }
+ // Build dist tool only.
+ _, err = b.run(src, "bash", "make.bash", "--dist-tool")
+ } else {
+ // Build.
+ if b.OS == "windows" {
+ _, err = b.run(src, "cmd", "/C", "make.bat")
+ } else {
+ _, err = b.run(src, "bash", "make.bash")
+ }
+ if b.Race {
+ if err != nil {
+ return err
+ }
+ goCmd := filepath.Join(b.root, "bin", "go")
+ if b.OS == "windows" {
+ goCmd += ".exe"
+ }
+ _, err = b.run(src, goCmd, "install", "-race", "std")
+ if err != nil {
+ return err
+ }
+ // Re-install std without -race, so that we're not left
+ // with a slower, race-enabled cmd/go, etc.
+ _, err = b.run(src, goCmd, "install", "-a", "std")
+ // Re-building go command leaves old versions of go.exe as go.exe~ on windows.
+ // See (*builder).copyFile in $GOROOT/src/cmd/go/build.go for details.
+ // Remove it manually.
+ if b.OS == "windows" {
+ os.Remove(goCmd + "~")
+ }
+ }
+ if err != nil {
+ return err
+ }
+ err = b.extras()
+ }
+ if err != nil {
+ return err
+ }
+
+ // Get version strings.
+ var (
+ version string // "weekly.2012-03-04"
+ fullVersion []byte // "weekly.2012-03-04 9353aa1efdf3"
+ )
+ pat := filepath.Join(b.root, "pkg/tool/*/dist*") // trailing * for .exe
+ m, err := filepath.Glob(pat)
+ if err != nil {
+ return err
+ }
+ if len(m) == 0 {
+ return fmt.Errorf("couldn't find dist in %q", pat)
+ }
+ fullVersion, err = b.run("", m[0], "version")
+ if err != nil {
+ return err
+ }
+ fullVersion = bytes.TrimSpace(fullVersion)
+ v := bytes.SplitN(fullVersion, []byte(" "), 2)
+ version = string(v[0])
+ if *versionOverride != "" {
+ version = *versionOverride
+ }
+
+ // Write VERSION file.
+ err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), fullVersion, 0644)
+ if err != nil {
+ return err
+ }
+
+ // Clean goroot.
+ if err := b.clean(cleanFiles); err != nil {
+ return err
+ }
+ if b.Source {
+ if err := b.clean(sourceCleanFiles); err != nil {
+ return err
+ }
+ }
+
+ // Create packages.
+ base := fmt.Sprintf("%s.%s-%s", version, b.OS, b.Arch)
+ if b.Label != "" {
+ base += "-" + b.Label
+ }
+ if !strings.HasPrefix(base, "go") {
+ base = "go." + base
+ }
+ var targs []string
+ switch b.OS {
+ case "linux", "freebsd", "netbsd", "":
+ // build tarball
+ targ := base
+ if b.Source {
+ targ = fmt.Sprintf("%s.src", version)
+ if !strings.HasPrefix(targ, "go") {
+ targ = "go." + targ
+ }
+ }
+ targ += ".tar.gz"
+ err = makeTar(targ, work)
+ targs = append(targs, targ)
+ case "darwin":
+ // build tarball
+ targ := base + ".tar.gz"
+ err = makeTar(targ, work)
+ targs = append(targs, targ)
+
+ makerelease := filepath.Join(runtime.GOROOT(), "misc/makerelease")
+
+ // build pkg
+ // arrange work so it's laid out as the dest filesystem
+ etc := filepath.Join(makerelease, "darwin/etc")
+ _, err = b.run(work, "cp", "-r", etc, ".")
+ if err != nil {
+ return err
+ }
+ localDir := filepath.Join(work, "usr/local")
+ err = os.MkdirAll(localDir, 0755)
+ if err != nil {
+ return err
+ }
+ _, err = b.run(work, "mv", "go", localDir)
+ if err != nil {
+ return err
+ }
+ // build package
+ pkgdest, err := ioutil.TempDir("", "pkgdest")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(pkgdest)
+ _, err = b.run("", "pkgbuild",
+ "--identifier", "com.googlecode.go",
+ "--version", version,
+ "--scripts", filepath.Join(makerelease, "darwin/scripts"),
+ "--root", work,
+ filepath.Join(pkgdest, "com.googlecode.go.pkg"))
+ if err != nil {
+ return err
+ }
+ targ = base + ".pkg"
+ _, err = b.run("", "productbuild",
+ "--distribution", filepath.Join(makerelease, "darwin/Distribution"),
+ "--resources", filepath.Join(makerelease, "darwin/Resources"),
+ "--package-path", pkgdest,
+ targ)
+ if err != nil {
+ return err
+ }
+ targs = append(targs, targ)
+ case "windows":
+ // Create ZIP file.
+ zip := filepath.Join(work, base+".zip")
+ err = makeZip(zip, work)
+ // Copy zip to target file.
+ targ := base + ".zip"
+ err = cp(targ, zip)
+ if err != nil {
+ return err
+ }
+ targs = append(targs, targ)
+
+ // Create MSI installer.
+ win := filepath.Join(runtime.GOROOT(), "misc/makerelease/windows")
+ installer := filepath.Join(win, "installer.wxs")
+ appfiles := filepath.Join(work, "AppFiles.wxs")
+ msi := filepath.Join(work, "installer.msi")
+ // Gather files.
+ _, err = b.run(work, "heat", "dir", "go",
+ "-nologo",
+ "-gg", "-g1", "-srd", "-sfrag",
+ "-cg", "AppFiles",
+ "-template", "fragment",
+ "-dr", "INSTALLDIR",
+ "-var", "var.SourceDir",
+ "-out", appfiles)
+ if err != nil {
+ return err
+ }
+ // Build package.
+ _, err = b.run(work, "candle",
+ "-nologo",
+ "-dVersion="+version,
+ "-dArch="+b.Arch,
+ "-dSourceDir=go",
+ installer, appfiles)
+ if err != nil {
+ return err
+ }
+ appfiles = filepath.Join(work, "AppFiles.wixobj")
+ installer = filepath.Join(work, "installer.wixobj")
+ _, err = b.run(win, "light",
+ "-nologo",
+ "-ext", "WixUIExtension",
+ "-ext", "WixUtilExtension",
+ installer, appfiles,
+ "-o", msi)
+ if err != nil {
+ return err
+ }
+ // Copy installer to target file.
+ targ = base + ".msi"
+ err = cp(targ, msi)
+ targs = append(targs, targ)
+ }
+ if err == nil && *upload {
+ for _, targ := range targs {
+ err = b.Upload(version, targ)
+ if err != nil {
+ return fmt.Errorf("uploading %s: %v", targ, err)
+ }
+ }
+ }
+ return err
+}
+
+// extras fetches the go.tools, go.blog, and go-tour repositories,
+// builds them and copies the resulting binaries and static assets
+// to the new GOROOT.
+func (b *Build) extras() error {
+ defer b.cleanGopath()
+
+ if err := b.tools(); err != nil {
+ return err
+ }
+ if err := b.blog(); err != nil {
+ return err
+ }
+ return b.tour()
+}
+
+func (b *Build) get(repoPath, revision string) error {
+ // Fetch the packages (without building/installing).
+ _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"),
+ "get", "-d", repoPath+"/...")
+ if err != nil {
+ return err
+ }
+
+ // Update the repo to the specified revision.
+ p := filepath.Join(b.gopath, "src", filepath.FromSlash(repoPath))
+ _, err = b.run(p, "hg", "update", revision)
+ return err
+}
+
+func (b *Build) tools() error {
+ // Fetch the go.tools repository.
+ if err := b.get(toolPath, *toolTag); err != nil {
+ return err
+ }
+
+ // Install tools.
+ args := append([]string{"install"}, toolPaths...)
+ _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), args...)
+ if err != nil {
+ return err
+ }
+
+ // Copy doc.go from go.tools/cmd/$CMD to $GOROOT/src/cmd/$CMD
+ // while rewriting "package main" to "package documentation".
+ for _, p := range toolPaths {
+ d, err := ioutil.ReadFile(filepath.Join(b.gopath, "src",
+ filepath.FromSlash(p), "doc.go"))
+ if err != nil {
+ return err
+ }
+ d = bytes.Replace(d, []byte("\npackage main\n"),
+ []byte("\npackage documentation\n"), 1)
+ cmdDir := filepath.Join(b.root, "src", "cmd", path.Base(p))
+ if err := os.MkdirAll(cmdDir, 0755); err != nil {
+ return err
+ }
+ docGo := filepath.Join(cmdDir, "doc.go")
+ if err := ioutil.WriteFile(docGo, d, 0644); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (b *Build) blog() error {
+ // Fetch the blog repository.
+ _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), "get", "-d", blogPath+"/blog")
+ if err != nil {
+ return err
+ }
+
+ // Copy blog content to $GOROOT/blog.
+ blogSrc := filepath.Join(b.gopath, "src", filepath.FromSlash(blogPath))
+ contentDir := filepath.Join(b.root, "blog")
+ return cpAllDir(contentDir, blogSrc, blogContent...)
+}
+
+func (b *Build) tour() error {
+ // Fetch the go-tour repository.
+ if err := b.get(tourPath, *tourTag); err != nil {
+ return err
+ }
+
+ // Build tour binary.
+ _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"),
+ "install", tourPath+"/gotour")
+ if err != nil {
+ return err
+ }
+
+ // Copy all the tour content to $GOROOT/misc/tour.
+ importPath := filepath.FromSlash(tourPath)
+ tourSrc := filepath.Join(b.gopath, "src", importPath)
+ contentDir := filepath.Join(b.root, "misc", "tour")
+ if err = cpAllDir(contentDir, tourSrc, tourContent...); err != nil {
+ return err
+ }
+
+ // Copy the tour source code so it's accessible with $GOPATH pointing to $GOROOT/misc/tour.
+ if err = cpAllDir(filepath.Join(contentDir, "src", importPath), tourSrc, tourPackages...); err != nil {
+ return err
+ }
+
+ // Copy gotour binary to tool directory as "tour"; invoked as "go tool tour".
+ return cp(
+ filepath.Join(b.root, "pkg", "tool", b.OS+"_"+b.Arch, "tour"+ext()),
+ filepath.Join(b.gopath, "bin", "gotour"+ext()),
+ )
+}
+
+func (b *Build) cleanGopath() {
+ for _, d := range []string{"bin", "pkg", "src"} {
+ os.RemoveAll(filepath.Join(b.gopath, d))
+ }
+}
+
+func ext() string {
+ if runtime.GOOS == "windows" {
+ return ".exe"
+ }
+ return ""
+}
+
+func (b *Build) hgCmd(dir string, args ...string) ([]byte, error) {
+ return b.run(dir, "hg", append([]string{"--config", "extensions.codereview=!"}, args...)...)
+}
+
+func (b *Build) run(dir, name string, args ...string) ([]byte, error) {
+ buf := new(bytes.Buffer)
+ absName, err := lookPath(name)
+ if err != nil {
+ return nil, err
+ }
+ cmd := exec.Command(absName, args...)
+ var output io.Writer = buf
+ if *verbose {
+ log.Printf("Running %q %q", absName, args)
+ output = io.MultiWriter(buf, os.Stdout)
+ }
+ cmd.Stdout = output
+ cmd.Stderr = output
+ cmd.Dir = dir
+ cmd.Env = b.env()
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "%s", buf.Bytes())
+ return nil, fmt.Errorf("%s %s: %v", name, strings.Join(args, " "), err)
+ }
+ return buf.Bytes(), nil
+}
+
+var cleanEnv = []string{
+ "GOARCH",
+ "GOBIN",
+ "GOHOSTARCH",
+ "GOHOSTOS",
+ "GOOS",
+ "GOROOT",
+ "GOROOT_FINAL",
+ "GOPATH",
+}
+
+func (b *Build) env() []string {
+ env := os.Environ()
+ for i := 0; i < len(env); i++ {
+ for _, c := range cleanEnv {
+ if strings.HasPrefix(env[i], c+"=") {
+ env = append(env[:i], env[i+1:]...)
+ }
+ }
+ }
+ final := "/usr/local/go"
+ if b.OS == "windows" {
+ final = `c:\go`
+ }
+ env = append(env,
+ "GOARCH="+b.Arch,
+ "GOHOSTARCH="+b.Arch,
+ "GOHOSTOS="+b.OS,
+ "GOOS="+b.OS,
+ "GOROOT="+b.root,
+ "GOROOT_FINAL="+final,
+ "GOPATH="+b.gopath,
+ )
+ if b.static {
+ env = append(env, "GO_DISTFLAGS=-s")
+ }
+ return env
+}
+
+func (b *Build) Upload(version string, filename string) error {
+ file, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+
+ svc, err := storage.New(oauthClient)
+ if err != nil {
+ return err
+ }
+ obj := &storage.Object{
+ Acl: []*storage.ObjectAccessControl{{Entity: "allUsers", Role: "READER"}},
+ Name: filename,
+ }
+ _, err = svc.Objects.Insert(*storageBucket, obj).Media(bytes.NewReader(file)).Do()
+ if err != nil {
+ return err
+ }
+
+ sum := fmt.Sprintf("%x", sha1.Sum(file))
+ kind := "unknown"
+ switch {
+ case b.Source:
+ kind = "source"
+ case strings.HasSuffix(filename, ".tar.gz"), strings.HasSuffix(filename, ".zip"):
+ kind = "archive"
+ case strings.HasSuffix(filename, ".msi"), strings.HasSuffix(filename, ".pkg"):
+ kind = "installer"
+ }
+ req, err := json.Marshal(File{
+ Filename: filename,
+ Version: version,
+ OS: b.OS,
+ Arch: b.Arch,
+ Checksum: sum,
+ Kind: kind,
+ })
+ if err != nil {
+ return err
+ }
+ u := fmt.Sprintf("%s?%s", *uploadURL, url.Values{"key": []string{builderKey}}.Encode())
+ resp, err := http.Post(u, "application/json", bytes.NewReader(req))
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("upload status: %v", resp.Status)
+ }
+
+ return nil
+}
+
+type File struct {
+ Filename string
+ OS string
+ Arch string
+ Version string
+ Checksum string `datastore:",noindex"`
+ Kind string // "archive", "installer", "source"
+}
+
+func setupOAuthClient() error {
+ config := &oauth.Config{
+ ClientId: "999119582588-h7kpj5pcm6d9solh5lgrbusmvvk4m9dn.apps.googleusercontent.com",
+ ClientSecret: "8YLFgOhXIELWbO",
+ Scope: storage.DevstorageRead_writeScope,
+ AuthURL: "https://accounts.google.com/o/oauth2/auth",
+ TokenURL: "https://accounts.google.com/o/oauth2/token",
+ TokenCache: oauth.CacheFile(*tokenCache),
+ RedirectURL: "oob",
+ }
+ transport := &oauth.Transport{Config: config}
+ if token, err := config.TokenCache.Token(); err != nil {
+ url := transport.Config.AuthCodeURL("")
+ fmt.Println("Visit the following URL, obtain an authentication" +
+ "code, and enter it below.")
+ fmt.Println(url)
+ fmt.Print("Enter authentication code: ")
+ code := ""
+ if _, err := fmt.Scan(&code); err != nil {
+ return err
+ }
+ if _, err := transport.Exchange(code); err != nil {
+ return err
+ }
+ } else {
+ transport.Token = token
+ }
+ oauthClient = transport.Client()
+ return nil
+}
+
+func (b *Build) clean(files []string) error {
+ for _, name := range files {
+ err := os.RemoveAll(filepath.Join(b.root, name))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func exists(path string) bool {
+ _, err := os.Stat(path)
+ return err == nil
+}
+
+func readCredentials() error {
+ name := os.Getenv("HOME")
+ if runtime.GOOS == "windows" {
+ name = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
+ }
+ name = filepath.Join(name, ".gobuildkey")
+ f, err := os.Open(name)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ s := bufio.NewScanner(f)
+ if s.Scan() {
+ builderKey = s.Text()
+ }
+ return s.Err()
+}
+
+func cp(dst, src string) error {
+ sf, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer sf.Close()
+ fi, err := sf.Stat()
+ if err != nil {
+ return err
+ }
+ df, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer df.Close()
+ // Windows doesn't currently implement Fchmod
+ if runtime.GOOS != "windows" {
+ if err := df.Chmod(fi.Mode()); err != nil {
+ return err
+ }
+ }
+ _, err = io.Copy(df, sf)
+ return err
+}
+
+func cpDir(dst, src string) error {
+ walk := func(srcPath string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ dstPath := filepath.Join(dst, srcPath[len(src):])
+ if info.IsDir() {
+ return os.MkdirAll(dstPath, 0755)
+ }
+ return cp(dstPath, srcPath)
+ }
+ return filepath.Walk(src, walk)
+}
+
+func cpAllDir(dst, basePath string, dirs ...string) error {
+ for _, dir := range dirs {
+ if err := cpDir(filepath.Join(dst, dir), filepath.Join(basePath, dir)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func makeTar(targ, workdir string) error {
+ f, err := os.Create(targ)
+ if err != nil {
+ return err
+ }
+ zout := gzip.NewWriter(f)
+ tw := tar.NewWriter(zout)
+
+ err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
+ if !strings.HasPrefix(path, workdir) {
+ log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
+ }
+ name := path[len(workdir):]
+
+ // Chop of any leading / from filename, leftover from removing workdir.
+ if strings.HasPrefix(name, "/") {
+ name = name[1:]
+ }
+ // Don't include things outside of the go subdirectory (for instance,
+ // the zip file that we're currently writing here.)
+ if !strings.HasPrefix(name, "go/") {
+ return nil
+ }
+ if *verbose {
+ log.Printf("adding to tar: %s", name)
+ }
+ target, _ := os.Readlink(path)
+ hdr, err := tar.FileInfoHeader(fi, target)
+ if err != nil {
+ return err
+ }
+ hdr.Name = name
+ hdr.Uname = "root"
+ hdr.Gname = "root"
+ hdr.Uid = 0
+ hdr.Gid = 0
+
+ // Force permissions to 0755 for executables, 0644 for everything else.
+ if fi.Mode().Perm()&0111 != 0 {
+ hdr.Mode = hdr.Mode&^0777 | 0755
+ } else {
+ hdr.Mode = hdr.Mode&^0777 | 0644
+ }
+
+ err = tw.WriteHeader(hdr)
+ if err != nil {
+ return fmt.Errorf("Error writing file %q: %v", name, err)
+ }
+ if fi.IsDir() {
+ return nil
+ }
+ r, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+ _, err = io.Copy(tw, r)
+ return err
+ })
+ if err != nil {
+ return err
+ }
+ if err := tw.Close(); err != nil {
+ return err
+ }
+ if err := zout.Close(); err != nil {
+ return err
+ }
+ return f.Close()
+}
+
+func makeZip(targ, workdir string) error {
+ f, err := os.Create(targ)
+ if err != nil {
+ return err
+ }
+ zw := zip.NewWriter(f)
+
+ err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
+ if !strings.HasPrefix(path, workdir) {
+ log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
+ }
+ name := path[len(workdir):]
+
+ // Convert to Unix-style named paths, as that's the
+ // type of zip file that archive/zip creates.
+ name = strings.Replace(name, "\\", "/", -1)
+ // Chop of any leading / from filename, leftover from removing workdir.
+ if strings.HasPrefix(name, "/") {
+ name = name[1:]
+ }
+ // Don't include things outside of the go subdirectory (for instance,
+ // the zip file that we're currently writing here.)
+ if !strings.HasPrefix(name, "go/") {
+ return nil
+ }
+ if *verbose {
+ log.Printf("adding to zip: %s", name)
+ }
+ fh, err := zip.FileInfoHeader(fi)
+ if err != nil {
+ return err
+ }
+ fh.Name = name
+ fh.Method = zip.Deflate
+ if fi.IsDir() {
+ fh.Name += "/" // append trailing slash
+ fh.Method = zip.Store // no need to deflate 0 byte files
+ }
+ w, err := zw.CreateHeader(fh)
+ if err != nil {
+ return err
+ }
+ if fi.IsDir() {
+ return nil
+ }
+ r, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+ _, err = io.Copy(w, r)
+ return err
+ })
+ if err != nil {
+ return err
+ }
+ if err := zw.Close(); err != nil {
+ return err
+ }
+ return f.Close()
+}
+
+type tool struct {
+ name string
+ commonDirs []string
+}
+
+var wixTool = tool{
+ "http://wix.sourceforge.net/, version 3.5",
+ []string{`C:\Program Files\Windows Installer XML v3.5\bin`,
+ `C:\Program Files (x86)\Windows Installer XML v3.5\bin`},
+}
+
+var hgTool = tool{
+ "http://mercurial.selenic.com/wiki/WindowsInstall",
+ []string{`C:\Program Files\Mercurial`,
+ `C:\Program Files (x86)\Mercurial`,
+ },
+}
+
+var gccTool = tool{
+ "Mingw gcc; http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/",
+ []string{`C:\Mingw\bin`},
+}
+
+var windowsDeps = map[string]tool{
+ "gcc": gccTool,
+ "heat": wixTool,
+ "candle": wixTool,
+ "light": wixTool,
+ "cmd": {"Windows cmd.exe", nil},
+ "hg": hgTool,
+}
+
+func checkWindowsDeps() {
+ for prog, help := range windowsDeps {
+ absPath, err := lookPath(prog)
+ if err != nil {
+ log.Fatalf("Failed to find necessary binary %q in path or common locations; %s", prog, help)
+ }
+ if *verbose {
+ log.Printf("found windows dep %s at %s", prog, absPath)
+ }
+ }
+}
+
+func lookPath(prog string) (absPath string, err error) {
+ absPath, err = exec.LookPath(prog)
+ if err == nil {
+ return
+ }
+ t, ok := windowsDeps[prog]
+ if !ok {
+ return
+ }
+ for _, dir := range t.commonDirs {
+ for _, ext := range []string{"exe", "bat"} {
+ absPath = filepath.Join(dir, prog+"."+ext)
+ if _, err1 := os.Stat(absPath); err1 == nil {
+ err = nil
+ os.Setenv("PATH", os.Getenv("PATH")+";"+dir)
+ return
+ }
+ }
+ }
+ return
+}