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