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