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