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