github.com/zach-klippenstein/go@v0.0.0-20150108044943-fcfbeb3adf58/misc/makerelease/makerelease.go (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This is a tool for packaging binary releases.
     6  // It supports FreeBSD, Linux, NetBSD, OpenBSD, OS X, and Windows.
     7  package main
     8  
     9  import (
    10  	"archive/tar"
    11  	"archive/zip"
    12  	"bufio"
    13  	"bytes"
    14  	"compress/gzip"
    15  	"crypto/sha1"
    16  	"encoding/json"
    17  	"errors"
    18  	"flag"
    19  	"fmt"
    20  	"io"
    21  	"io/ioutil"
    22  	"log"
    23  	"net/http"
    24  	"net/url"
    25  	"os"
    26  	"os/exec"
    27  	"path"
    28  	"path/filepath"
    29  	"regexp"
    30  	"runtime"
    31  	"strings"
    32  
    33  	"code.google.com/p/goauth2/oauth"
    34  	storage "code.google.com/p/google-api-go-client/storage/v1"
    35  )
    36  
    37  var (
    38  	tag             = flag.String("tag", "release", "mercurial tag to check out")
    39  	toolTag         = flag.String("tool", defaultToolTag, "go.tools tag to check out")
    40  	tourTag         = flag.String("tour", defaultTourTag, "go-tour tag to check out")
    41  	repo            = flag.String("repo", "https://code.google.com/p/go", "repo URL")
    42  	verbose         = flag.Bool("v", false, "verbose output")
    43  	upload          = flag.Bool("upload", false, "upload resulting files to Google Code")
    44  	addLabel        = flag.String("label", "", "additional label to apply to file when uploading")
    45  	includeRace     = flag.Bool("race", true, "build race detector packages")
    46  	versionOverride = flag.String("version", "", "override version name")
    47  	staticToolchain = flag.Bool("static", true, "try to build statically linked toolchain (only supported on ELF targets)")
    48  	tokenCache      = flag.String("token", defaultCacheFile, "Authentication token cache file")
    49  	storageBucket   = flag.String("bucket", "golang", "Cloud Storage Bucket")
    50  	uploadURL       = flag.String("upload_url", defaultUploadURL, "Upload URL")
    51  
    52  	defaultCacheFile = filepath.Join(os.Getenv("HOME"), ".makerelease-request-token")
    53  	defaultUploadURL = "http://golang.org/dl/upload"
    54  )
    55  
    56  const (
    57  	blogPath       = "golang.org/x/blog"
    58  	toolPath       = "golang.org/x/tools"
    59  	tourPath       = "code.google.com/p/go-tour"
    60  	defaultToolTag = "release-branch.go1.4"
    61  	defaultTourTag = "release-branch.go1.4"
    62  )
    63  
    64  // Import paths for tool commands.
    65  // These must be the command that cmd/go knows to install to $GOROOT/bin
    66  // or $GOROOT/pkg/tool.
    67  var toolPaths = []string{
    68  	"golang.org/x/tools/cmd/cover",
    69  	"golang.org/x/tools/cmd/godoc",
    70  	"golang.org/x/tools/cmd/vet",
    71  }
    72  
    73  var preBuildCleanFiles = []string{
    74  	"lib/codereview",
    75  	"misc/dashboard/godashboard",
    76  	"src/cmd/cov",
    77  	"src/cmd/prof",
    78  	"src/exp",
    79  	"src/old",
    80  }
    81  
    82  var cleanFiles = []string{
    83  	".hg",
    84  	".hgtags",
    85  	".hgignore",
    86  	"VERSION.cache",
    87  }
    88  
    89  var sourceCleanFiles = []string{
    90  	"bin",
    91  	"pkg",
    92  }
    93  
    94  var tourPackages = []string{
    95  	"pic",
    96  	"tree",
    97  	"wc",
    98  }
    99  
   100  var tourContent = []string{
   101  	"content",
   102  	"solutions",
   103  	"static",
   104  	"template",
   105  }
   106  
   107  var blogContent = []string{
   108  	"content",
   109  	"template",
   110  }
   111  
   112  // The os-arches that support the race toolchain.
   113  var raceAvailable = []string{
   114  	"darwin-amd64",
   115  	"linux-amd64",
   116  	"windows-amd64",
   117  }
   118  
   119  // The OSes that support building statically linked toolchain
   120  // Only ELF platforms are supported.
   121  var staticLinkAvailable = []string{
   122  	"linux",
   123  	"freebsd",
   124  	"openbsd",
   125  	"netbsd",
   126  }
   127  
   128  var fileRe = regexp.MustCompile(`^(go[a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+)(?:-([a-z0-9.]+))?)\.(tar\.gz|zip|pkg|msi)$`)
   129  
   130  // OAuth2-authenticated HTTP client used to make calls to Cloud Storage.
   131  var oauthClient *http.Client
   132  
   133  // Builder key as specified in ~/.gobuildkey
   134  var builderKey string
   135  
   136  func main() {
   137  	flag.Usage = func() {
   138  		fmt.Fprintf(os.Stderr, "usage: %s [flags] targets...\n", os.Args[0])
   139  		flag.PrintDefaults()
   140  		os.Exit(2)
   141  	}
   142  	flag.Parse()
   143  	if flag.NArg() == 0 {
   144  		flag.Usage()
   145  	}
   146  	if runtime.GOOS == "windows" {
   147  		checkWindowsDeps()
   148  	}
   149  
   150  	if *upload {
   151  		if err := readCredentials(); err != nil {
   152  			log.Fatalln("readCredentials:", err)
   153  		}
   154  		if err := setupOAuthClient(); err != nil {
   155  			log.Fatalln("setupOAuthClient:", err)
   156  		}
   157  	}
   158  	ok := true
   159  	for _, targ := range flag.Args() {
   160  		var b Build
   161  		if m := fileRe.FindStringSubmatch(targ); m != nil {
   162  			// targ is a file name; upload it to googlecode.
   163  			version := m[1]
   164  			if m[2] == "src" {
   165  				b.Source = true
   166  			} else {
   167  				b.OS = m[3]
   168  				b.Arch = m[4]
   169  				b.Label = m[5]
   170  			}
   171  			if !*upload {
   172  				log.Printf("%s: -upload=false, skipping", targ)
   173  				continue
   174  			}
   175  			if err := b.Upload(version, targ); err != nil {
   176  				log.Printf("uploading %s: %v", targ, err)
   177  			}
   178  			continue
   179  		}
   180  		if targ == "source" {
   181  			b.Source = true
   182  		} else {
   183  			p := strings.SplitN(targ, "-", 3)
   184  			if len(p) < 2 {
   185  				log.Println("Ignoring unrecognized target:", targ)
   186  				continue
   187  			}
   188  			b.OS = p[0]
   189  			b.Arch = p[1]
   190  			if len(p) >= 3 {
   191  				b.Label = p[2]
   192  			}
   193  			if *includeRace {
   194  				for _, t := range raceAvailable {
   195  					if t == targ || strings.HasPrefix(targ, t+"-") {
   196  						b.Race = true
   197  					}
   198  				}
   199  			}
   200  			if *staticToolchain {
   201  				for _, os := range staticLinkAvailable {
   202  					if b.OS == os {
   203  						b.static = true
   204  					}
   205  				}
   206  			}
   207  		}
   208  		if err := b.Do(); err != nil {
   209  			log.Printf("%s: %v", targ, err)
   210  			ok = false
   211  		}
   212  	}
   213  	if !ok {
   214  		os.Exit(1)
   215  	}
   216  }
   217  
   218  type Build struct {
   219  	Source bool // if true, OS and Arch must be empty
   220  	Race   bool // build race toolchain
   221  	OS     string
   222  	Arch   string
   223  	Label  string
   224  	root   string
   225  	gopath string
   226  	static bool // if true, build statically linked toolchain
   227  }
   228  
   229  func (b *Build) Do() error {
   230  	work, err := ioutil.TempDir("", "makerelease")
   231  	if err != nil {
   232  		return err
   233  	}
   234  	defer os.RemoveAll(work)
   235  	b.root = filepath.Join(work, "go")
   236  	b.gopath = work
   237  
   238  	// Clone Go distribution and update to tag.
   239  	_, err = b.hgCmd(work, "clone", *repo, b.root)
   240  	if err != nil {
   241  		return err
   242  	}
   243  	_, err = b.hgCmd(b.root, "update", *tag)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	// Remove exp and old packages.
   249  	if err := b.clean(preBuildCleanFiles); err != nil {
   250  		return err
   251  	}
   252  
   253  	src := filepath.Join(b.root, "src")
   254  	if b.Source {
   255  		if runtime.GOOS == "windows" {
   256  			log.Print("Warning: running make.bash on Windows; source builds are intended to be run on a Unix machine")
   257  		}
   258  		// Build dist tool only.
   259  		_, err = b.run(src, "bash", "make.bash", "--dist-tool")
   260  	} else {
   261  		// Build.
   262  		if b.OS == "windows" {
   263  			_, err = b.run(src, "cmd", "/C", "make.bat")
   264  		} else {
   265  			_, err = b.run(src, "bash", "make.bash")
   266  		}
   267  		if b.Race {
   268  			if err != nil {
   269  				return err
   270  			}
   271  			goCmd := filepath.Join(b.root, "bin", "go")
   272  			if b.OS == "windows" {
   273  				goCmd += ".exe"
   274  			}
   275  			_, err = b.run(src, goCmd, "install", "-race", "std")
   276  			if err != nil {
   277  				return err
   278  			}
   279  			// Re-install std without -race, so that we're not left
   280  			// with a slower, race-enabled cmd/go, etc.
   281  			_, err = b.run(src, goCmd, "install", "-a", "std")
   282  			// Re-building go command leaves old versions of go.exe as go.exe~ on windows.
   283  			// See (*builder).copyFile in $GOROOT/src/cmd/go/build.go for details.
   284  			// Remove it manually.
   285  			if b.OS == "windows" {
   286  				os.Remove(goCmd + "~")
   287  			}
   288  		}
   289  		if err != nil {
   290  			return err
   291  		}
   292  		err = b.extras()
   293  	}
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	// Get version strings.
   299  	var (
   300  		version     string // "weekly.2012-03-04"
   301  		fullVersion []byte // "weekly.2012-03-04 9353aa1efdf3"
   302  	)
   303  	pat := filepath.Join(b.root, "pkg/tool/*/dist*") // trailing * for .exe
   304  	m, err := filepath.Glob(pat)
   305  	if err != nil {
   306  		return err
   307  	}
   308  	if len(m) == 0 {
   309  		return fmt.Errorf("couldn't find dist in %q", pat)
   310  	}
   311  	fullVersion, err = b.run("", m[0], "version")
   312  	if err != nil {
   313  		return err
   314  	}
   315  	fullVersion = bytes.TrimSpace(fullVersion)
   316  	v := bytes.SplitN(fullVersion, []byte(" "), 2)
   317  	version = string(v[0])
   318  	if *versionOverride != "" {
   319  		version = *versionOverride
   320  	}
   321  
   322  	// Write VERSION file.
   323  	err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), fullVersion, 0644)
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	// Clean goroot.
   329  	if err := b.clean(cleanFiles); err != nil {
   330  		return err
   331  	}
   332  	if b.Source {
   333  		if err := b.clean(sourceCleanFiles); err != nil {
   334  			return err
   335  		}
   336  	}
   337  
   338  	// Create packages.
   339  	base := fmt.Sprintf("%s.%s-%s", version, b.OS, b.Arch)
   340  	if b.Label != "" {
   341  		base += "-" + b.Label
   342  	}
   343  	if !strings.HasPrefix(base, "go") {
   344  		base = "go." + base
   345  	}
   346  	var targs []string
   347  	switch b.OS {
   348  	case "linux", "freebsd", "netbsd", "":
   349  		// build tarball
   350  		targ := base
   351  		if b.Source {
   352  			targ = fmt.Sprintf("%s.src", version)
   353  			if !strings.HasPrefix(targ, "go") {
   354  				targ = "go." + targ
   355  			}
   356  		}
   357  		targ += ".tar.gz"
   358  		err = makeTar(targ, work)
   359  		targs = append(targs, targ)
   360  	case "darwin":
   361  		// build tarball
   362  		targ := base + ".tar.gz"
   363  		err = makeTar(targ, work)
   364  		targs = append(targs, targ)
   365  
   366  		makerelease := filepath.Join(runtime.GOROOT(), "misc/makerelease")
   367  
   368  		// build pkg
   369  		// arrange work so it's laid out as the dest filesystem
   370  		etc := filepath.Join(makerelease, "darwin/etc")
   371  		_, err = b.run(work, "cp", "-r", etc, ".")
   372  		if err != nil {
   373  			return err
   374  		}
   375  		localDir := filepath.Join(work, "usr/local")
   376  		err = os.MkdirAll(localDir, 0755)
   377  		if err != nil {
   378  			return err
   379  		}
   380  		_, err = b.run(work, "mv", "go", localDir)
   381  		if err != nil {
   382  			return err
   383  		}
   384  		// build package
   385  		pkgdest, err := ioutil.TempDir("", "pkgdest")
   386  		if err != nil {
   387  			return err
   388  		}
   389  		defer os.RemoveAll(pkgdest)
   390  		_, err = b.run("", "pkgbuild",
   391  			"--identifier", "com.googlecode.go",
   392  			"--version", version,
   393  			"--scripts", filepath.Join(makerelease, "darwin/scripts"),
   394  			"--root", work,
   395  			filepath.Join(pkgdest, "com.googlecode.go.pkg"))
   396  		if err != nil {
   397  			return err
   398  		}
   399  		targ = base + ".pkg"
   400  		_, err = b.run("", "productbuild",
   401  			"--distribution", filepath.Join(makerelease, "darwin/Distribution"),
   402  			"--resources", filepath.Join(makerelease, "darwin/Resources"),
   403  			"--package-path", pkgdest,
   404  			targ)
   405  		if err != nil {
   406  			return err
   407  		}
   408  		targs = append(targs, targ)
   409  	case "windows":
   410  		// Create ZIP file.
   411  		zip := filepath.Join(work, base+".zip")
   412  		err = makeZip(zip, work)
   413  		// Copy zip to target file.
   414  		targ := base + ".zip"
   415  		err = cp(targ, zip)
   416  		if err != nil {
   417  			return err
   418  		}
   419  		targs = append(targs, targ)
   420  
   421  		// Create MSI installer.
   422  		win := filepath.Join(runtime.GOROOT(), "misc/makerelease/windows")
   423  		installer := filepath.Join(win, "installer.wxs")
   424  		appfiles := filepath.Join(work, "AppFiles.wxs")
   425  		msi := filepath.Join(work, "installer.msi")
   426  		// Gather files.
   427  		_, err = b.run(work, "heat", "dir", "go",
   428  			"-nologo",
   429  			"-gg", "-g1", "-srd", "-sfrag",
   430  			"-cg", "AppFiles",
   431  			"-template", "fragment",
   432  			"-dr", "INSTALLDIR",
   433  			"-var", "var.SourceDir",
   434  			"-out", appfiles)
   435  		if err != nil {
   436  			return err
   437  		}
   438  		// Build package.
   439  		_, err = b.run(work, "candle",
   440  			"-nologo",
   441  			"-dGoVersion="+version,
   442  			"-dWixGoVersion="+wixVersion(version),
   443  			"-dArch="+b.Arch,
   444  			"-dSourceDir=go",
   445  			installer, appfiles)
   446  		if err != nil {
   447  			return err
   448  		}
   449  		appfiles = filepath.Join(work, "AppFiles.wixobj")
   450  		installer = filepath.Join(work, "installer.wixobj")
   451  		_, err = b.run(win, "light",
   452  			"-nologo",
   453  			"-ext", "WixUIExtension",
   454  			"-ext", "WixUtilExtension",
   455  			installer, appfiles,
   456  			"-o", msi)
   457  		if err != nil {
   458  			return err
   459  		}
   460  		// Copy installer to target file.
   461  		targ = base + ".msi"
   462  		err = cp(targ, msi)
   463  		targs = append(targs, targ)
   464  	}
   465  	if err == nil && *upload {
   466  		for _, targ := range targs {
   467  			err = b.Upload(version, targ)
   468  			if err != nil {
   469  				return fmt.Errorf("uploading %s: %v", targ, err)
   470  			}
   471  		}
   472  	}
   473  	return err
   474  }
   475  
   476  var versionRe = regexp.MustCompile(`^go([0-9]+(\.[0-9]+)*)`)
   477  
   478  // The Microsoft installer requires version format major.minor.build
   479  // (http://msdn.microsoft.com/en-us/library/aa370859%28v=vs.85%29.aspx).
   480  // Where the major and minor field has a maximum value of 255 and build 65535.
   481  // The offical Go version format is goMAJOR.MINOR.PATCH at $GOROOT/VERSION.
   482  // It's based on the Mercurial tag. Remove prefix and suffix to make the
   483  // installer happy.
   484  func wixVersion(v string) string {
   485  	m := versionRe.FindStringSubmatch(v)
   486  	if m == nil {
   487  		return "0.0.0"
   488  	}
   489  	return m[1]
   490  }
   491  
   492  // extras fetches the go.tools, go.blog, and go-tour repositories,
   493  // builds them and copies the resulting binaries and static assets
   494  // to the new GOROOT.
   495  func (b *Build) extras() error {
   496  	defer b.cleanGopath()
   497  
   498  	if err := b.tools(); err != nil {
   499  		return err
   500  	}
   501  	if err := b.blog(); err != nil {
   502  		return err
   503  	}
   504  	return b.tour()
   505  }
   506  
   507  func (b *Build) get(repoPath, revision string) error {
   508  	// Fetch the packages (without building/installing).
   509  	_, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"),
   510  		"get", "-d", repoPath+"/...")
   511  	if err != nil {
   512  		return err
   513  	}
   514  
   515  	// Update the repo to the specified revision.
   516  	dest := filepath.Join(b.gopath, "src", filepath.FromSlash(repoPath))
   517  	switch {
   518  	case exists(filepath.Join(dest, ".git")):
   519  		_, err = b.run(dest, "git", "checkout", revision)
   520  	case exists(filepath.Join(dest, ".hg")):
   521  		_, err = b.run(dest, "hg", "update", revision)
   522  	default:
   523  		err = errors.New("unknown version control system")
   524  	}
   525  	return err
   526  }
   527  
   528  func (b *Build) tools() error {
   529  	// Fetch the go.tools repository.
   530  	if err := b.get(toolPath, *toolTag); err != nil {
   531  		return err
   532  	}
   533  
   534  	// Install tools.
   535  	args := append([]string{"install"}, toolPaths...)
   536  	_, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), args...)
   537  	if err != nil {
   538  		return err
   539  	}
   540  
   541  	// Copy doc.go from go.tools/cmd/$CMD to $GOROOT/src/cmd/$CMD
   542  	// while rewriting "package main" to "package documentation".
   543  	for _, p := range toolPaths {
   544  		d, err := ioutil.ReadFile(filepath.Join(b.gopath, "src",
   545  			filepath.FromSlash(p), "doc.go"))
   546  		if err != nil {
   547  			return err
   548  		}
   549  		d = bytes.Replace(d, []byte("\npackage main\n"),
   550  			[]byte("\npackage documentation\n"), 1)
   551  		cmdDir := filepath.Join(b.root, "src", "cmd", path.Base(p))
   552  		if err := os.MkdirAll(cmdDir, 0755); err != nil {
   553  			return err
   554  		}
   555  		docGo := filepath.Join(cmdDir, "doc.go")
   556  		if err := ioutil.WriteFile(docGo, d, 0644); err != nil {
   557  			return err
   558  		}
   559  	}
   560  
   561  	return nil
   562  }
   563  
   564  func (b *Build) blog() error {
   565  	// Fetch the blog repository.
   566  	_, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), "get", "-d", blogPath+"/blog")
   567  	if err != nil {
   568  		return err
   569  	}
   570  
   571  	// Copy blog content to $GOROOT/blog.
   572  	blogSrc := filepath.Join(b.gopath, "src", filepath.FromSlash(blogPath))
   573  	contentDir := filepath.Join(b.root, "blog")
   574  	return cpAllDir(contentDir, blogSrc, blogContent...)
   575  }
   576  
   577  func (b *Build) tour() error {
   578  	// Fetch the go-tour repository.
   579  	if err := b.get(tourPath, *tourTag); err != nil {
   580  		return err
   581  	}
   582  
   583  	// Build tour binary.
   584  	_, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"),
   585  		"install", tourPath+"/gotour")
   586  	if err != nil {
   587  		return err
   588  	}
   589  
   590  	// Copy all the tour content to $GOROOT/misc/tour.
   591  	importPath := filepath.FromSlash(tourPath)
   592  	tourSrc := filepath.Join(b.gopath, "src", importPath)
   593  	contentDir := filepath.Join(b.root, "misc", "tour")
   594  	if err = cpAllDir(contentDir, tourSrc, tourContent...); err != nil {
   595  		return err
   596  	}
   597  
   598  	// Copy the tour source code so it's accessible with $GOPATH pointing to $GOROOT/misc/tour.
   599  	if err = cpAllDir(filepath.Join(contentDir, "src", importPath), tourSrc, tourPackages...); err != nil {
   600  		return err
   601  	}
   602  
   603  	// Copy gotour binary to tool directory as "tour"; invoked as "go tool tour".
   604  	return cp(
   605  		filepath.Join(b.root, "pkg", "tool", b.OS+"_"+b.Arch, "tour"+ext()),
   606  		filepath.Join(b.gopath, "bin", "gotour"+ext()),
   607  	)
   608  }
   609  
   610  func (b *Build) cleanGopath() {
   611  	for _, d := range []string{"bin", "pkg", "src"} {
   612  		os.RemoveAll(filepath.Join(b.gopath, d))
   613  	}
   614  }
   615  
   616  func ext() string {
   617  	if runtime.GOOS == "windows" {
   618  		return ".exe"
   619  	}
   620  	return ""
   621  }
   622  
   623  func (b *Build) hgCmd(dir string, args ...string) ([]byte, error) {
   624  	return b.run(dir, "hg", append([]string{"--config", "extensions.codereview=!"}, args...)...)
   625  }
   626  
   627  func (b *Build) run(dir, name string, args ...string) ([]byte, error) {
   628  	buf := new(bytes.Buffer)
   629  	absName, err := lookPath(name)
   630  	if err != nil {
   631  		return nil, err
   632  	}
   633  	cmd := exec.Command(absName, args...)
   634  	var output io.Writer = buf
   635  	if *verbose {
   636  		log.Printf("Running %q %q", absName, args)
   637  		output = io.MultiWriter(buf, os.Stdout)
   638  	}
   639  	cmd.Stdout = output
   640  	cmd.Stderr = output
   641  	cmd.Dir = dir
   642  	cmd.Env = b.env()
   643  	if err := cmd.Run(); err != nil {
   644  		fmt.Fprintf(os.Stderr, "%s", buf.Bytes())
   645  		return nil, fmt.Errorf("%s %s: %v", name, strings.Join(args, " "), err)
   646  	}
   647  	return buf.Bytes(), nil
   648  }
   649  
   650  var cleanEnv = []string{
   651  	"GOARCH",
   652  	"GOBIN",
   653  	"GOHOSTARCH",
   654  	"GOHOSTOS",
   655  	"GOOS",
   656  	"GOROOT",
   657  	"GOROOT_FINAL",
   658  	"GOPATH",
   659  }
   660  
   661  func (b *Build) env() []string {
   662  	env := os.Environ()
   663  	for i := 0; i < len(env); i++ {
   664  		for _, c := range cleanEnv {
   665  			if strings.HasPrefix(env[i], c+"=") {
   666  				env = append(env[:i], env[i+1:]...)
   667  			}
   668  		}
   669  	}
   670  	final := "/usr/local/go"
   671  	if b.OS == "windows" {
   672  		final = `c:\go`
   673  	}
   674  	env = append(env,
   675  		"GOARCH="+b.Arch,
   676  		"GOHOSTARCH="+b.Arch,
   677  		"GOHOSTOS="+b.OS,
   678  		"GOOS="+b.OS,
   679  		"GOROOT="+b.root,
   680  		"GOROOT_FINAL="+final,
   681  		"GOPATH="+b.gopath,
   682  	)
   683  	if b.static {
   684  		env = append(env, "GO_DISTFLAGS=-s")
   685  	}
   686  	return env
   687  }
   688  
   689  func (b *Build) Upload(version string, filename string) error {
   690  	file, err := ioutil.ReadFile(filename)
   691  	if err != nil {
   692  		return err
   693  	}
   694  
   695  	svc, err := storage.New(oauthClient)
   696  	if err != nil {
   697  		return err
   698  	}
   699  	obj := &storage.Object{
   700  		Acl:  []*storage.ObjectAccessControl{{Entity: "allUsers", Role: "READER"}},
   701  		Name: filename,
   702  	}
   703  	_, err = svc.Objects.Insert(*storageBucket, obj).Media(bytes.NewReader(file)).Do()
   704  	if err != nil {
   705  		return err
   706  	}
   707  
   708  	sum := fmt.Sprintf("%x", sha1.Sum(file))
   709  	kind := "unknown"
   710  	switch {
   711  	case b.Source:
   712  		kind = "source"
   713  	case strings.HasSuffix(filename, ".tar.gz"), strings.HasSuffix(filename, ".zip"):
   714  		kind = "archive"
   715  	case strings.HasSuffix(filename, ".msi"), strings.HasSuffix(filename, ".pkg"):
   716  		kind = "installer"
   717  	}
   718  	req, err := json.Marshal(File{
   719  		Filename: filename,
   720  		Version:  version,
   721  		OS:       b.OS,
   722  		Arch:     b.Arch,
   723  		Checksum: sum,
   724  		Kind:     kind,
   725  	})
   726  	if err != nil {
   727  		return err
   728  	}
   729  	u := fmt.Sprintf("%s?%s", *uploadURL, url.Values{"key": []string{builderKey}}.Encode())
   730  	resp, err := http.Post(u, "application/json", bytes.NewReader(req))
   731  	if err != nil {
   732  		return err
   733  	}
   734  	defer resp.Body.Close()
   735  	if resp.StatusCode != http.StatusOK {
   736  		return fmt.Errorf("upload status: %v", resp.Status)
   737  	}
   738  
   739  	return nil
   740  }
   741  
   742  type File struct {
   743  	Filename string
   744  	OS       string
   745  	Arch     string
   746  	Version  string
   747  	Checksum string `datastore:",noindex"`
   748  	Kind     string // "archive", "installer", "source"
   749  }
   750  
   751  func setupOAuthClient() error {
   752  	config := &oauth.Config{
   753  		ClientId:     "999119582588-h7kpj5pcm6d9solh5lgrbusmvvk4m9dn.apps.googleusercontent.com",
   754  		ClientSecret: "8YLFgOhXIELWbO-NtF3iqIQz",
   755  		Scope:        storage.DevstorageRead_writeScope,
   756  		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
   757  		TokenURL:     "https://accounts.google.com/o/oauth2/token",
   758  		TokenCache:   oauth.CacheFile(*tokenCache),
   759  		RedirectURL:  "oob",
   760  	}
   761  	transport := &oauth.Transport{Config: config}
   762  	if token, err := config.TokenCache.Token(); err != nil {
   763  		url := transport.Config.AuthCodeURL("")
   764  		fmt.Println("Visit the following URL, obtain an authentication" +
   765  			"code, and enter it below.")
   766  		fmt.Println(url)
   767  		fmt.Print("Enter authentication code: ")
   768  		code := ""
   769  		if _, err := fmt.Scan(&code); err != nil {
   770  			return err
   771  		}
   772  		if _, err := transport.Exchange(code); err != nil {
   773  			return err
   774  		}
   775  	} else {
   776  		transport.Token = token
   777  	}
   778  	oauthClient = transport.Client()
   779  	return nil
   780  }
   781  
   782  func (b *Build) clean(files []string) error {
   783  	for _, name := range files {
   784  		err := os.RemoveAll(filepath.Join(b.root, name))
   785  		if err != nil {
   786  			return err
   787  		}
   788  	}
   789  	return nil
   790  }
   791  
   792  func exists(path string) bool {
   793  	_, err := os.Stat(path)
   794  	return err == nil
   795  }
   796  
   797  func readCredentials() error {
   798  	name := os.Getenv("HOME")
   799  	if runtime.GOOS == "windows" {
   800  		name = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
   801  	}
   802  	name = filepath.Join(name, ".gobuildkey")
   803  	f, err := os.Open(name)
   804  	if err != nil {
   805  		return err
   806  	}
   807  	defer f.Close()
   808  	s := bufio.NewScanner(f)
   809  	if s.Scan() {
   810  		builderKey = s.Text()
   811  	}
   812  	return s.Err()
   813  }
   814  
   815  func cp(dst, src string) error {
   816  	sf, err := os.Open(src)
   817  	if err != nil {
   818  		return err
   819  	}
   820  	defer sf.Close()
   821  	fi, err := sf.Stat()
   822  	if err != nil {
   823  		return err
   824  	}
   825  	df, err := os.Create(dst)
   826  	if err != nil {
   827  		return err
   828  	}
   829  	defer df.Close()
   830  	// Windows doesn't currently implement Fchmod
   831  	if runtime.GOOS != "windows" {
   832  		if err := df.Chmod(fi.Mode()); err != nil {
   833  			return err
   834  		}
   835  	}
   836  	_, err = io.Copy(df, sf)
   837  	return err
   838  }
   839  
   840  func cpDir(dst, src string) error {
   841  	walk := func(srcPath string, info os.FileInfo, err error) error {
   842  		if err != nil {
   843  			return err
   844  		}
   845  		dstPath := filepath.Join(dst, srcPath[len(src):])
   846  		if info.IsDir() {
   847  			return os.MkdirAll(dstPath, 0755)
   848  		}
   849  		return cp(dstPath, srcPath)
   850  	}
   851  	return filepath.Walk(src, walk)
   852  }
   853  
   854  func cpAllDir(dst, basePath string, dirs ...string) error {
   855  	for _, dir := range dirs {
   856  		if err := cpDir(filepath.Join(dst, dir), filepath.Join(basePath, dir)); err != nil {
   857  			return err
   858  		}
   859  	}
   860  	return nil
   861  }
   862  
   863  func makeTar(targ, workdir string) error {
   864  	f, err := os.Create(targ)
   865  	if err != nil {
   866  		return err
   867  	}
   868  	zout := gzip.NewWriter(f)
   869  	tw := tar.NewWriter(zout)
   870  
   871  	err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
   872  		if !strings.HasPrefix(path, workdir) {
   873  			log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
   874  		}
   875  		name := path[len(workdir):]
   876  
   877  		// Chop of any leading / from filename, leftover from removing workdir.
   878  		if strings.HasPrefix(name, "/") {
   879  			name = name[1:]
   880  		}
   881  		// Don't include things outside of the go subdirectory (for instance,
   882  		// the zip file that we're currently writing here.)
   883  		if !strings.HasPrefix(name, "go/") {
   884  			return nil
   885  		}
   886  		if *verbose {
   887  			log.Printf("adding to tar: %s", name)
   888  		}
   889  		target, _ := os.Readlink(path)
   890  		hdr, err := tar.FileInfoHeader(fi, target)
   891  		if err != nil {
   892  			return err
   893  		}
   894  		hdr.Name = name
   895  		hdr.Uname = "root"
   896  		hdr.Gname = "root"
   897  		hdr.Uid = 0
   898  		hdr.Gid = 0
   899  
   900  		// Force permissions to 0755 for executables, 0644 for everything else.
   901  		if fi.Mode().Perm()&0111 != 0 {
   902  			hdr.Mode = hdr.Mode&^0777 | 0755
   903  		} else {
   904  			hdr.Mode = hdr.Mode&^0777 | 0644
   905  		}
   906  
   907  		err = tw.WriteHeader(hdr)
   908  		if err != nil {
   909  			return fmt.Errorf("Error writing file %q: %v", name, err)
   910  		}
   911  		if fi.IsDir() {
   912  			return nil
   913  		}
   914  		r, err := os.Open(path)
   915  		if err != nil {
   916  			return err
   917  		}
   918  		defer r.Close()
   919  		_, err = io.Copy(tw, r)
   920  		return err
   921  	})
   922  	if err != nil {
   923  		return err
   924  	}
   925  	if err := tw.Close(); err != nil {
   926  		return err
   927  	}
   928  	if err := zout.Close(); err != nil {
   929  		return err
   930  	}
   931  	return f.Close()
   932  }
   933  
   934  func makeZip(targ, workdir string) error {
   935  	f, err := os.Create(targ)
   936  	if err != nil {
   937  		return err
   938  	}
   939  	zw := zip.NewWriter(f)
   940  
   941  	err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
   942  		if !strings.HasPrefix(path, workdir) {
   943  			log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
   944  		}
   945  		name := path[len(workdir):]
   946  
   947  		// Convert to Unix-style named paths, as that's the
   948  		// type of zip file that archive/zip creates.
   949  		name = strings.Replace(name, "\\", "/", -1)
   950  		// Chop of any leading / from filename, leftover from removing workdir.
   951  		if strings.HasPrefix(name, "/") {
   952  			name = name[1:]
   953  		}
   954  		// Don't include things outside of the go subdirectory (for instance,
   955  		// the zip file that we're currently writing here.)
   956  		if !strings.HasPrefix(name, "go/") {
   957  			return nil
   958  		}
   959  		if *verbose {
   960  			log.Printf("adding to zip: %s", name)
   961  		}
   962  		fh, err := zip.FileInfoHeader(fi)
   963  		if err != nil {
   964  			return err
   965  		}
   966  		fh.Name = name
   967  		fh.Method = zip.Deflate
   968  		if fi.IsDir() {
   969  			fh.Name += "/"        // append trailing slash
   970  			fh.Method = zip.Store // no need to deflate 0 byte files
   971  		}
   972  		w, err := zw.CreateHeader(fh)
   973  		if err != nil {
   974  			return err
   975  		}
   976  		if fi.IsDir() {
   977  			return nil
   978  		}
   979  		r, err := os.Open(path)
   980  		if err != nil {
   981  			return err
   982  		}
   983  		defer r.Close()
   984  		_, err = io.Copy(w, r)
   985  		return err
   986  	})
   987  	if err != nil {
   988  		return err
   989  	}
   990  	if err := zw.Close(); err != nil {
   991  		return err
   992  	}
   993  	return f.Close()
   994  }
   995  
   996  type tool struct {
   997  	name       string
   998  	commonDirs []string
   999  }
  1000  
  1001  var wixTool = tool{
  1002  	"http://wix.sourceforge.net/, version 3.5",
  1003  	[]string{`C:\Program Files\Windows Installer XML v3.5\bin`,
  1004  		`C:\Program Files (x86)\Windows Installer XML v3.5\bin`},
  1005  }
  1006  
  1007  var hgTool = tool{
  1008  	"http://mercurial.selenic.com/wiki/WindowsInstall",
  1009  	[]string{`C:\Program Files\Mercurial`,
  1010  		`C:\Program Files (x86)\Mercurial`,
  1011  	},
  1012  }
  1013  
  1014  var gccTool = tool{
  1015  	"Mingw gcc; http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/",
  1016  	[]string{`C:\Mingw\bin`},
  1017  }
  1018  
  1019  var windowsDeps = map[string]tool{
  1020  	"gcc":    gccTool,
  1021  	"heat":   wixTool,
  1022  	"candle": wixTool,
  1023  	"light":  wixTool,
  1024  	"cmd":    {"Windows cmd.exe", nil},
  1025  	"hg":     hgTool,
  1026  }
  1027  
  1028  func checkWindowsDeps() {
  1029  	for prog, help := range windowsDeps {
  1030  		absPath, err := lookPath(prog)
  1031  		if err != nil {
  1032  			log.Fatalf("Failed to find necessary binary %q in path or common locations; %s", prog, help)
  1033  		}
  1034  		if *verbose {
  1035  			log.Printf("found windows dep %s at %s", prog, absPath)
  1036  		}
  1037  	}
  1038  }
  1039  
  1040  func lookPath(prog string) (absPath string, err error) {
  1041  	absPath, err = exec.LookPath(prog)
  1042  	if err == nil {
  1043  		return
  1044  	}
  1045  	t, ok := windowsDeps[prog]
  1046  	if !ok {
  1047  		return
  1048  	}
  1049  	for _, dir := range t.commonDirs {
  1050  		for _, ext := range []string{"exe", "bat"} {
  1051  			absPath = filepath.Join(dir, prog+"."+ext)
  1052  			if _, err1 := os.Stat(absPath); err1 == nil {
  1053  				err = nil
  1054  				os.Setenv("PATH", os.Getenv("PATH")+";"+dir)
  1055  				return
  1056  			}
  1057  		}
  1058  	}
  1059  	return
  1060  }