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