diff options
author | Michael Stapelberg <stapelberg@debian.org> | 2014-06-19 09:22:53 +0200 |
---|---|---|
committer | Michael Stapelberg <stapelberg@debian.org> | 2014-06-19 09:22:53 +0200 |
commit | 8a39ee361feb9bf46d728ff1ba4f07ca1d9610b1 (patch) | |
tree | 4449f2036cccf162e8417cc5841a35815b3e7ac5 /misc/makerelease/makerelease.go | |
parent | c8bf49ef8a92e2337b69c14b9b88396efe498600 (diff) | |
download | golang-51f2ca399fb8da86b2e7b3a0582e083fab731a98.tar.gz |
Imported Upstream version 1.3upstream/1.3
Diffstat (limited to 'misc/makerelease/makerelease.go')
-rw-r--r-- | misc/makerelease/makerelease.go | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/misc/makerelease/makerelease.go b/misc/makerelease/makerelease.go new file mode 100644 index 000000000..2496a865a --- /dev/null +++ b/misc/makerelease/makerelease.go @@ -0,0 +1,1030 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This is a tool for packaging binary releases. +// It supports FreeBSD, Linux, NetBSD, OpenBSD, OS X, and Windows. +package main + +import ( + "archive/tar" + "archive/zip" + "bufio" + "bytes" + "compress/gzip" + "crypto/sha1" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "runtime" + "strings" + + "code.google.com/p/goauth2/oauth" + "code.google.com/p/google-api-go-client/storage/v1beta2" +) + +var ( + tag = flag.String("tag", "release", "mercurial tag to check out") + toolTag = flag.String("tool", defaultToolTag, "go.tools tag to check out") + tourTag = flag.String("tour", defaultTourTag, "go-tour tag to check out") + repo = flag.String("repo", "https://code.google.com/p/go", "repo URL") + verbose = flag.Bool("v", false, "verbose output") + upload = flag.Bool("upload", false, "upload resulting files to Google Code") + addLabel = flag.String("label", "", "additional label to apply to file when uploading") + includeRace = flag.Bool("race", true, "build race detector packages") + versionOverride = flag.String("version", "", "override version name") + staticToolchain = flag.Bool("static", true, "try to build statically linked toolchain (only supported on ELF targets)") + tokenCache = flag.String("token", defaultCacheFile, "Authentication token cache file") + storageBucket = flag.String("bucket", "golang", "Cloud Storage Bucket") + uploadURL = flag.String("upload_url", defaultUploadURL, "Upload URL") + + defaultCacheFile = filepath.Join(os.Getenv("HOME"), ".makerelease-request-token") + defaultUploadURL = "http://golang.org/dl/upload" +) + +const ( + blogPath = "code.google.com/p/go.blog" + toolPath = "code.google.com/p/go.tools" + tourPath = "code.google.com/p/go-tour" + defaultToolTag = "release-branch.go1.2" + defaultTourTag = "release-branch.go1.2" +) + +// Import paths for tool commands. +// These must be the command that cmd/go knows to install to $GOROOT/bin +// or $GOROOT/pkg/tool. +var toolPaths = []string{ + "code.google.com/p/go.tools/cmd/cover", + "code.google.com/p/go.tools/cmd/godoc", + "code.google.com/p/go.tools/cmd/vet", +} + +var preBuildCleanFiles = []string{ + "lib/codereview", + "misc/dashboard/godashboard", + "src/cmd/cov", + "src/cmd/prof", + "src/pkg/exp", + "src/pkg/old", +} + +var cleanFiles = []string{ + ".hg", + ".hgtags", + ".hgignore", + "VERSION.cache", +} + +var sourceCleanFiles = []string{ + "bin", + "pkg", +} + +var tourPackages = []string{ + "pic", + "tree", + "wc", +} + +var tourContent = []string{ + "content", + "solutions", + "static", + "template", +} + +var blogContent = []string{ + "content", + "template", +} + +// The os-arches that support the race toolchain. +var raceAvailable = []string{ + "darwin-amd64", + "linux-amd64", + "windows-amd64", +} + +// The OSes that support building statically linked toolchain +// Only ELF platforms are supported. +var staticLinkAvailable = []string{ + "linux", + "freebsd", + "openbsd", + "netbsd", +} + +var fileRe = regexp.MustCompile(`^(go[a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+)(?:-([a-z0-9.]+))?)\.(tar\.gz|zip|pkg|msi)$`) + +// OAuth2-authenticated HTTP client used to make calls to Cloud Storage. +var oauthClient *http.Client + +// Builder key as specified in ~/.gobuildkey +var builderKey string + +func main() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "usage: %s [flags] targets...\n", os.Args[0]) + flag.PrintDefaults() + os.Exit(2) + } + flag.Parse() + if flag.NArg() == 0 { + flag.Usage() + } + if runtime.GOOS == "windows" { + checkWindowsDeps() + } + + if *upload { + if err := readCredentials(); err != nil { + log.Fatalln("readCredentials:", err) + } + if err := setupOAuthClient(); err != nil { + log.Fatalln("setupOAuthClient:", err) + } + } + for _, targ := range flag.Args() { + var b Build + if m := fileRe.FindStringSubmatch(targ); m != nil { + // targ is a file name; upload it to googlecode. + version := m[1] + if m[2] == "src" { + b.Source = true + } else { + b.OS = m[3] + b.Arch = m[4] + b.Label = m[5] + } + if !*upload { + log.Printf("%s: -upload=false, skipping", targ) + continue + } + if err := b.Upload(version, targ); err != nil { + log.Printf("uploading %s: %v", targ, err) + } + continue + } + if targ == "source" { + b.Source = true + } else { + p := strings.SplitN(targ, "-", 3) + if len(p) < 2 { + log.Println("Ignoring unrecognized target:", targ) + continue + } + b.OS = p[0] + b.Arch = p[1] + if len(p) >= 3 { + b.Label = p[2] + } + if *includeRace { + for _, t := range raceAvailable { + if t == targ || strings.HasPrefix(targ, t+"-") { + b.Race = true + } + } + } + if *staticToolchain { + for _, os := range staticLinkAvailable { + if b.OS == os { + b.static = true + } + } + } + } + if err := b.Do(); err != nil { + log.Printf("%s: %v", targ, err) + } + } +} + +type Build struct { + Source bool // if true, OS and Arch must be empty + Race bool // build race toolchain + OS string + Arch string + Label string + root string + gopath string + static bool // if true, build statically linked toolchain +} + +func (b *Build) Do() error { + work, err := ioutil.TempDir("", "makerelease") + if err != nil { + return err + } + defer os.RemoveAll(work) + b.root = filepath.Join(work, "go") + b.gopath = work + + // Clone Go distribution and update to tag. + _, err = b.hgCmd(work, "clone", *repo, b.root) + if err != nil { + return err + } + _, err = b.hgCmd(b.root, "update", *tag) + if err != nil { + return err + } + + // Remove exp and old packages. + if err := b.clean(preBuildCleanFiles); err != nil { + return err + } + + src := filepath.Join(b.root, "src") + if b.Source { + if runtime.GOOS == "windows" { + log.Print("Warning: running make.bash on Windows; source builds are intended to be run on a Unix machine") + } + // Build dist tool only. + _, err = b.run(src, "bash", "make.bash", "--dist-tool") + } else { + // Build. + if b.OS == "windows" { + _, err = b.run(src, "cmd", "/C", "make.bat") + } else { + _, err = b.run(src, "bash", "make.bash") + } + if b.Race { + if err != nil { + return err + } + goCmd := filepath.Join(b.root, "bin", "go") + if b.OS == "windows" { + goCmd += ".exe" + } + _, err = b.run(src, goCmd, "install", "-race", "std") + if err != nil { + return err + } + // Re-install std without -race, so that we're not left + // with a slower, race-enabled cmd/go, etc. + _, err = b.run(src, goCmd, "install", "-a", "std") + // Re-building go command leaves old versions of go.exe as go.exe~ on windows. + // See (*builder).copyFile in $GOROOT/src/cmd/go/build.go for details. + // Remove it manually. + if b.OS == "windows" { + os.Remove(goCmd + "~") + } + } + if err != nil { + return err + } + err = b.extras() + } + if err != nil { + return err + } + + // Get version strings. + var ( + version string // "weekly.2012-03-04" + fullVersion []byte // "weekly.2012-03-04 9353aa1efdf3" + ) + pat := filepath.Join(b.root, "pkg/tool/*/dist*") // trailing * for .exe + m, err := filepath.Glob(pat) + if err != nil { + return err + } + if len(m) == 0 { + return fmt.Errorf("couldn't find dist in %q", pat) + } + fullVersion, err = b.run("", m[0], "version") + if err != nil { + return err + } + fullVersion = bytes.TrimSpace(fullVersion) + v := bytes.SplitN(fullVersion, []byte(" "), 2) + version = string(v[0]) + if *versionOverride != "" { + version = *versionOverride + } + + // Write VERSION file. + err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), fullVersion, 0644) + if err != nil { + return err + } + + // Clean goroot. + if err := b.clean(cleanFiles); err != nil { + return err + } + if b.Source { + if err := b.clean(sourceCleanFiles); err != nil { + return err + } + } + + // Create packages. + base := fmt.Sprintf("%s.%s-%s", version, b.OS, b.Arch) + if b.Label != "" { + base += "-" + b.Label + } + if !strings.HasPrefix(base, "go") { + base = "go." + base + } + var targs []string + switch b.OS { + case "linux", "freebsd", "netbsd", "": + // build tarball + targ := base + if b.Source { + targ = fmt.Sprintf("%s.src", version) + if !strings.HasPrefix(targ, "go") { + targ = "go." + targ + } + } + targ += ".tar.gz" + err = makeTar(targ, work) + targs = append(targs, targ) + case "darwin": + // build tarball + targ := base + ".tar.gz" + err = makeTar(targ, work) + targs = append(targs, targ) + + makerelease := filepath.Join(runtime.GOROOT(), "misc/makerelease") + + // build pkg + // arrange work so it's laid out as the dest filesystem + etc := filepath.Join(makerelease, "darwin/etc") + _, err = b.run(work, "cp", "-r", etc, ".") + if err != nil { + return err + } + localDir := filepath.Join(work, "usr/local") + err = os.MkdirAll(localDir, 0755) + if err != nil { + return err + } + _, err = b.run(work, "mv", "go", localDir) + if err != nil { + return err + } + // build package + pkgdest, err := ioutil.TempDir("", "pkgdest") + if err != nil { + return err + } + defer os.RemoveAll(pkgdest) + _, err = b.run("", "pkgbuild", + "--identifier", "com.googlecode.go", + "--version", version, + "--scripts", filepath.Join(makerelease, "darwin/scripts"), + "--root", work, + filepath.Join(pkgdest, "com.googlecode.go.pkg")) + if err != nil { + return err + } + targ = base + ".pkg" + _, err = b.run("", "productbuild", + "--distribution", filepath.Join(makerelease, "darwin/Distribution"), + "--resources", filepath.Join(makerelease, "darwin/Resources"), + "--package-path", pkgdest, + targ) + if err != nil { + return err + } + targs = append(targs, targ) + case "windows": + // Create ZIP file. + zip := filepath.Join(work, base+".zip") + err = makeZip(zip, work) + // Copy zip to target file. + targ := base + ".zip" + err = cp(targ, zip) + if err != nil { + return err + } + targs = append(targs, targ) + + // Create MSI installer. + win := filepath.Join(runtime.GOROOT(), "misc/makerelease/windows") + installer := filepath.Join(win, "installer.wxs") + appfiles := filepath.Join(work, "AppFiles.wxs") + msi := filepath.Join(work, "installer.msi") + // Gather files. + _, err = b.run(work, "heat", "dir", "go", + "-nologo", + "-gg", "-g1", "-srd", "-sfrag", + "-cg", "AppFiles", + "-template", "fragment", + "-dr", "INSTALLDIR", + "-var", "var.SourceDir", + "-out", appfiles) + if err != nil { + return err + } + // Build package. + _, err = b.run(work, "candle", + "-nologo", + "-dVersion="+version, + "-dArch="+b.Arch, + "-dSourceDir=go", + installer, appfiles) + if err != nil { + return err + } + appfiles = filepath.Join(work, "AppFiles.wixobj") + installer = filepath.Join(work, "installer.wixobj") + _, err = b.run(win, "light", + "-nologo", + "-ext", "WixUIExtension", + "-ext", "WixUtilExtension", + installer, appfiles, + "-o", msi) + if err != nil { + return err + } + // Copy installer to target file. + targ = base + ".msi" + err = cp(targ, msi) + targs = append(targs, targ) + } + if err == nil && *upload { + for _, targ := range targs { + err = b.Upload(version, targ) + if err != nil { + return fmt.Errorf("uploading %s: %v", targ, err) + } + } + } + return err +} + +// extras fetches the go.tools, go.blog, and go-tour repositories, +// builds them and copies the resulting binaries and static assets +// to the new GOROOT. +func (b *Build) extras() error { + defer b.cleanGopath() + + if err := b.tools(); err != nil { + return err + } + if err := b.blog(); err != nil { + return err + } + return b.tour() +} + +func (b *Build) get(repoPath, revision string) error { + // Fetch the packages (without building/installing). + _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), + "get", "-d", repoPath+"/...") + if err != nil { + return err + } + + // Update the repo to the specified revision. + p := filepath.Join(b.gopath, "src", filepath.FromSlash(repoPath)) + _, err = b.run(p, "hg", "update", revision) + return err +} + +func (b *Build) tools() error { + // Fetch the go.tools repository. + if err := b.get(toolPath, *toolTag); err != nil { + return err + } + + // Install tools. + args := append([]string{"install"}, toolPaths...) + _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), args...) + if err != nil { + return err + } + + // Copy doc.go from go.tools/cmd/$CMD to $GOROOT/src/cmd/$CMD + // while rewriting "package main" to "package documentation". + for _, p := range toolPaths { + d, err := ioutil.ReadFile(filepath.Join(b.gopath, "src", + filepath.FromSlash(p), "doc.go")) + if err != nil { + return err + } + d = bytes.Replace(d, []byte("\npackage main\n"), + []byte("\npackage documentation\n"), 1) + cmdDir := filepath.Join(b.root, "src", "cmd", path.Base(p)) + if err := os.MkdirAll(cmdDir, 0755); err != nil { + return err + } + docGo := filepath.Join(cmdDir, "doc.go") + if err := ioutil.WriteFile(docGo, d, 0644); err != nil { + return err + } + } + + return nil +} + +func (b *Build) blog() error { + // Fetch the blog repository. + _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), "get", "-d", blogPath+"/blog") + if err != nil { + return err + } + + // Copy blog content to $GOROOT/blog. + blogSrc := filepath.Join(b.gopath, "src", filepath.FromSlash(blogPath)) + contentDir := filepath.Join(b.root, "blog") + return cpAllDir(contentDir, blogSrc, blogContent...) +} + +func (b *Build) tour() error { + // Fetch the go-tour repository. + if err := b.get(tourPath, *tourTag); err != nil { + return err + } + + // Build tour binary. + _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), + "install", tourPath+"/gotour") + if err != nil { + return err + } + + // Copy all the tour content to $GOROOT/misc/tour. + importPath := filepath.FromSlash(tourPath) + tourSrc := filepath.Join(b.gopath, "src", importPath) + contentDir := filepath.Join(b.root, "misc", "tour") + if err = cpAllDir(contentDir, tourSrc, tourContent...); err != nil { + return err + } + + // Copy the tour source code so it's accessible with $GOPATH pointing to $GOROOT/misc/tour. + if err = cpAllDir(filepath.Join(contentDir, "src", importPath), tourSrc, tourPackages...); err != nil { + return err + } + + // Copy gotour binary to tool directory as "tour"; invoked as "go tool tour". + return cp( + filepath.Join(b.root, "pkg", "tool", b.OS+"_"+b.Arch, "tour"+ext()), + filepath.Join(b.gopath, "bin", "gotour"+ext()), + ) +} + +func (b *Build) cleanGopath() { + for _, d := range []string{"bin", "pkg", "src"} { + os.RemoveAll(filepath.Join(b.gopath, d)) + } +} + +func ext() string { + if runtime.GOOS == "windows" { + return ".exe" + } + return "" +} + +func (b *Build) hgCmd(dir string, args ...string) ([]byte, error) { + return b.run(dir, "hg", append([]string{"--config", "extensions.codereview=!"}, args...)...) +} + +func (b *Build) run(dir, name string, args ...string) ([]byte, error) { + buf := new(bytes.Buffer) + absName, err := lookPath(name) + if err != nil { + return nil, err + } + cmd := exec.Command(absName, args...) + var output io.Writer = buf + if *verbose { + log.Printf("Running %q %q", absName, args) + output = io.MultiWriter(buf, os.Stdout) + } + cmd.Stdout = output + cmd.Stderr = output + cmd.Dir = dir + cmd.Env = b.env() + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "%s", buf.Bytes()) + return nil, fmt.Errorf("%s %s: %v", name, strings.Join(args, " "), err) + } + return buf.Bytes(), nil +} + +var cleanEnv = []string{ + "GOARCH", + "GOBIN", + "GOHOSTARCH", + "GOHOSTOS", + "GOOS", + "GOROOT", + "GOROOT_FINAL", + "GOPATH", +} + +func (b *Build) env() []string { + env := os.Environ() + for i := 0; i < len(env); i++ { + for _, c := range cleanEnv { + if strings.HasPrefix(env[i], c+"=") { + env = append(env[:i], env[i+1:]...) + } + } + } + final := "/usr/local/go" + if b.OS == "windows" { + final = `c:\go` + } + env = append(env, + "GOARCH="+b.Arch, + "GOHOSTARCH="+b.Arch, + "GOHOSTOS="+b.OS, + "GOOS="+b.OS, + "GOROOT="+b.root, + "GOROOT_FINAL="+final, + "GOPATH="+b.gopath, + ) + if b.static { + env = append(env, "GO_DISTFLAGS=-s") + } + return env +} + +func (b *Build) Upload(version string, filename string) error { + file, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + + svc, err := storage.New(oauthClient) + if err != nil { + return err + } + obj := &storage.Object{ + Acl: []*storage.ObjectAccessControl{{Entity: "allUsers", Role: "READER"}}, + Name: filename, + } + _, err = svc.Objects.Insert(*storageBucket, obj).Media(bytes.NewReader(file)).Do() + if err != nil { + return err + } + + sum := fmt.Sprintf("%x", sha1.Sum(file)) + kind := "unknown" + switch { + case b.Source: + kind = "source" + case strings.HasSuffix(filename, ".tar.gz"), strings.HasSuffix(filename, ".zip"): + kind = "archive" + case strings.HasSuffix(filename, ".msi"), strings.HasSuffix(filename, ".pkg"): + kind = "installer" + } + req, err := json.Marshal(File{ + Filename: filename, + Version: version, + OS: b.OS, + Arch: b.Arch, + Checksum: sum, + Kind: kind, + }) + if err != nil { + return err + } + u := fmt.Sprintf("%s?%s", *uploadURL, url.Values{"key": []string{builderKey}}.Encode()) + resp, err := http.Post(u, "application/json", bytes.NewReader(req)) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("upload status: %v", resp.Status) + } + + return nil +} + +type File struct { + Filename string + OS string + Arch string + Version string + Checksum string `datastore:",noindex"` + Kind string // "archive", "installer", "source" +} + +func setupOAuthClient() error { + config := &oauth.Config{ + ClientId: "999119582588-h7kpj5pcm6d9solh5lgrbusmvvk4m9dn.apps.googleusercontent.com", + ClientSecret: "8YLFgOhXIELWbO", + Scope: storage.DevstorageRead_writeScope, + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://accounts.google.com/o/oauth2/token", + TokenCache: oauth.CacheFile(*tokenCache), + RedirectURL: "oob", + } + transport := &oauth.Transport{Config: config} + if token, err := config.TokenCache.Token(); err != nil { + url := transport.Config.AuthCodeURL("") + fmt.Println("Visit the following URL, obtain an authentication" + + "code, and enter it below.") + fmt.Println(url) + fmt.Print("Enter authentication code: ") + code := "" + if _, err := fmt.Scan(&code); err != nil { + return err + } + if _, err := transport.Exchange(code); err != nil { + return err + } + } else { + transport.Token = token + } + oauthClient = transport.Client() + return nil +} + +func (b *Build) clean(files []string) error { + for _, name := range files { + err := os.RemoveAll(filepath.Join(b.root, name)) + if err != nil { + return err + } + } + return nil +} + +func exists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func readCredentials() error { + name := os.Getenv("HOME") + if runtime.GOOS == "windows" { + name = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + } + name = filepath.Join(name, ".gobuildkey") + f, err := os.Open(name) + if err != nil { + return err + } + defer f.Close() + s := bufio.NewScanner(f) + if s.Scan() { + builderKey = s.Text() + } + return s.Err() +} + +func cp(dst, src string) error { + sf, err := os.Open(src) + if err != nil { + return err + } + defer sf.Close() + fi, err := sf.Stat() + if err != nil { + return err + } + df, err := os.Create(dst) + if err != nil { + return err + } + defer df.Close() + // Windows doesn't currently implement Fchmod + if runtime.GOOS != "windows" { + if err := df.Chmod(fi.Mode()); err != nil { + return err + } + } + _, err = io.Copy(df, sf) + return err +} + +func cpDir(dst, src string) error { + walk := func(srcPath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + dstPath := filepath.Join(dst, srcPath[len(src):]) + if info.IsDir() { + return os.MkdirAll(dstPath, 0755) + } + return cp(dstPath, srcPath) + } + return filepath.Walk(src, walk) +} + +func cpAllDir(dst, basePath string, dirs ...string) error { + for _, dir := range dirs { + if err := cpDir(filepath.Join(dst, dir), filepath.Join(basePath, dir)); err != nil { + return err + } + } + return nil +} + +func makeTar(targ, workdir string) error { + f, err := os.Create(targ) + if err != nil { + return err + } + zout := gzip.NewWriter(f) + tw := tar.NewWriter(zout) + + err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error { + if !strings.HasPrefix(path, workdir) { + log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir) + } + name := path[len(workdir):] + + // Chop of any leading / from filename, leftover from removing workdir. + if strings.HasPrefix(name, "/") { + name = name[1:] + } + // Don't include things outside of the go subdirectory (for instance, + // the zip file that we're currently writing here.) + if !strings.HasPrefix(name, "go/") { + return nil + } + if *verbose { + log.Printf("adding to tar: %s", name) + } + target, _ := os.Readlink(path) + hdr, err := tar.FileInfoHeader(fi, target) + if err != nil { + return err + } + hdr.Name = name + hdr.Uname = "root" + hdr.Gname = "root" + hdr.Uid = 0 + hdr.Gid = 0 + + // Force permissions to 0755 for executables, 0644 for everything else. + if fi.Mode().Perm()&0111 != 0 { + hdr.Mode = hdr.Mode&^0777 | 0755 + } else { + hdr.Mode = hdr.Mode&^0777 | 0644 + } + + err = tw.WriteHeader(hdr) + if err != nil { + return fmt.Errorf("Error writing file %q: %v", name, err) + } + if fi.IsDir() { + return nil + } + r, err := os.Open(path) + if err != nil { + return err + } + defer r.Close() + _, err = io.Copy(tw, r) + return err + }) + if err != nil { + return err + } + if err := tw.Close(); err != nil { + return err + } + if err := zout.Close(); err != nil { + return err + } + return f.Close() +} + +func makeZip(targ, workdir string) error { + f, err := os.Create(targ) + if err != nil { + return err + } + zw := zip.NewWriter(f) + + err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error { + if !strings.HasPrefix(path, workdir) { + log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir) + } + name := path[len(workdir):] + + // Convert to Unix-style named paths, as that's the + // type of zip file that archive/zip creates. + name = strings.Replace(name, "\\", "/", -1) + // Chop of any leading / from filename, leftover from removing workdir. + if strings.HasPrefix(name, "/") { + name = name[1:] + } + // Don't include things outside of the go subdirectory (for instance, + // the zip file that we're currently writing here.) + if !strings.HasPrefix(name, "go/") { + return nil + } + if *verbose { + log.Printf("adding to zip: %s", name) + } + fh, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + fh.Name = name + fh.Method = zip.Deflate + if fi.IsDir() { + fh.Name += "/" // append trailing slash + fh.Method = zip.Store // no need to deflate 0 byte files + } + w, err := zw.CreateHeader(fh) + if err != nil { + return err + } + if fi.IsDir() { + return nil + } + r, err := os.Open(path) + if err != nil { + return err + } + defer r.Close() + _, err = io.Copy(w, r) + return err + }) + if err != nil { + return err + } + if err := zw.Close(); err != nil { + return err + } + return f.Close() +} + +type tool struct { + name string + commonDirs []string +} + +var wixTool = tool{ + "http://wix.sourceforge.net/, version 3.5", + []string{`C:\Program Files\Windows Installer XML v3.5\bin`, + `C:\Program Files (x86)\Windows Installer XML v3.5\bin`}, +} + +var hgTool = tool{ + "http://mercurial.selenic.com/wiki/WindowsInstall", + []string{`C:\Program Files\Mercurial`, + `C:\Program Files (x86)\Mercurial`, + }, +} + +var gccTool = tool{ + "Mingw gcc; http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/", + []string{`C:\Mingw\bin`}, +} + +var windowsDeps = map[string]tool{ + "gcc": gccTool, + "heat": wixTool, + "candle": wixTool, + "light": wixTool, + "cmd": {"Windows cmd.exe", nil}, + "hg": hgTool, +} + +func checkWindowsDeps() { + for prog, help := range windowsDeps { + absPath, err := lookPath(prog) + if err != nil { + log.Fatalf("Failed to find necessary binary %q in path or common locations; %s", prog, help) + } + if *verbose { + log.Printf("found windows dep %s at %s", prog, absPath) + } + } +} + +func lookPath(prog string) (absPath string, err error) { + absPath, err = exec.LookPath(prog) + if err == nil { + return + } + t, ok := windowsDeps[prog] + if !ok { + return + } + for _, dir := range t.commonDirs { + for _, ext := range []string{"exe", "bat"} { + absPath = filepath.Join(dir, prog+"."+ext) + if _, err1 := os.Stat(absPath); err1 == nil { + err = nil + os.Setenv("PATH", os.Getenv("PATH")+";"+dir) + return + } + } + } + return +} |