summaryrefslogtreecommitdiff
path: root/misc/dist/bindist.go
diff options
context:
space:
mode:
Diffstat (limited to 'misc/dist/bindist.go')
-rw-r--r--misc/dist/bindist.go796
1 files changed, 796 insertions, 0 deletions
diff --git a/misc/dist/bindist.go b/misc/dist/bindist.go
new file mode 100644
index 000000000..16193bdc1
--- /dev/null
+++ b/misc/dist/bindist.go
@@ -0,0 +1,796 @@
+// 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, OS X, and Windows.
+package main
+
+import (
+ "archive/tar"
+ "archive/zip"
+ "bufio"
+ "bytes"
+ "compress/gzip"
+ "encoding/base64"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "mime/multipart"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+)
+
+var (
+ tag = flag.String("tag", "weekly", "mercurial 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", true, "upload resulting files to Google Code")
+ wxsFile = flag.String("wxs", "", "path to custom installer.wxs")
+ addLabel = flag.String("label", "", "additional label to apply to file when uploading")
+
+ username, password string // for Google Code upload
+)
+
+const (
+ packageMaker = "/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker"
+ uploadURL = "https://go.googlecode.com/files"
+)
+
+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 fileRe = regexp.MustCompile(`^go\.([a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+))\.`)
+
+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.Println("readCredentials:", 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]
+ }
+ if !*upload {
+ log.Printf("%s: -upload=false, skipping", targ)
+ continue
+ }
+ if err := b.Upload(version, targ); err != nil {
+ log.Printf("%s: %v", targ, err)
+ }
+ continue
+ }
+ if targ == "source" {
+ b.Source = true
+ } else {
+ p := strings.SplitN(targ, "-", 2)
+ if len(p) != 2 {
+ log.Println("Ignoring unrecognized target:", targ)
+ continue
+ }
+ b.OS = p[0]
+ b.Arch = p[1]
+ }
+ 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
+ OS string
+ Arch string
+ root string
+}
+
+func (b *Build) Do() error {
+ work, err := ioutil.TempDir("", "bindist")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(work)
+ b.root = filepath.Join(work, "go")
+
+ // Clone Go distribution and update to tag.
+ _, err = b.run(work, "hg", "clone", "-q", *repo, b.root)
+ if err != nil {
+ return err
+ }
+ _, err = b.run(b.root, "hg", "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 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])
+
+ // 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("go.%s.%s-%s", version, b.OS, b.Arch)
+ var targs []string
+ switch b.OS {
+ case "linux", "freebsd", "":
+ // build tarball
+ targ := base
+ if b.Source {
+ targ = fmt.Sprintf("go.%s.src", version)
+ }
+ targ += ".tar.gz"
+ err = makeTar(targ, work)
+ targs = append(targs, targ)
+ case "darwin":
+ // arrange work so it's laid out as the dest filesystem
+ etc := filepath.Join(b.root, "misc/dist/darwin/etc")
+ _, err = b.run(work, "cp", "-r", etc, ".")
+ if err != nil {
+ return err
+ }
+ localDir := filepath.Join(work, "usr/local")
+ err = os.MkdirAll(localDir, 0744)
+ if err != nil {
+ return err
+ }
+ _, err = b.run(work, "mv", "go", localDir)
+ if err != nil {
+ return err
+ }
+ // build package
+ pm := packageMaker
+ if !exists(pm) {
+ pm = "/Developer" + pm
+ if !exists(pm) {
+ return errors.New("couldn't find PackageMaker")
+ }
+ }
+ targ := base + ".pkg"
+ scripts := filepath.Join(work, "usr/local/go/misc/dist/darwin/scripts")
+ _, err = b.run("", pm, "-v",
+ "-r", work,
+ "-o", targ,
+ "--scripts", scripts,
+ "--id", "com.googlecode.go",
+ "--title", "Go",
+ "--version", "1.0",
+ "--target", "10.6")
+ 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(b.root, "misc/dist/windows")
+ installer := filepath.Join(win, "installer.wxs")
+ if *wxsFile != "" {
+ installer = *wxsFile
+ }
+ 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 err
+ }
+ }
+ }
+ return err
+}
+
+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",
+}
+
+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,
+ )
+ return env
+}
+
+func (b *Build) Upload(version string, filename string) error {
+ // Prepare upload metadata.
+ var labels []string
+ os_, arch := b.OS, b.Arch
+ switch b.Arch {
+ case "386":
+ arch = "32-bit"
+ case "amd64":
+ arch = "64-bit"
+ }
+ if arch != "" {
+ labels = append(labels, "Arch-"+b.Arch)
+ }
+ switch b.OS {
+ case "linux":
+ os_ = "Linux"
+ labels = append(labels, "Type-Archive", "OpSys-Linux")
+ case "freebsd":
+ os_ = "FreeBSD"
+ labels = append(labels, "Type-Archive", "OpSys-FreeBSD")
+ case "darwin":
+ os_ = "Mac OS X"
+ labels = append(labels, "Type-Installer", "OpSys-OSX")
+ case "windows":
+ os_ = "Windows"
+ labels = append(labels, "OpSys-Windows")
+ }
+ summary := fmt.Sprintf("%s %s (%s)", version, os_, arch)
+ if b.OS == "windows" {
+ switch {
+ case strings.HasSuffix(filename, ".msi"):
+ labels = append(labels, "Type-Installer")
+ summary += " MSI installer"
+ case strings.HasSuffix(filename, ".zip"):
+ labels = append(labels, "Type-Archive")
+ summary += " ZIP archive"
+ }
+ }
+ if b.Source {
+ labels = append(labels, "Type-Source")
+ summary = fmt.Sprintf("%s (source only)", version)
+ }
+ if *addLabel != "" {
+ labels = append(labels, *addLabel)
+ }
+ // Put "Go" prefix on summary when it doesn't already begin with "go".
+ if !strings.HasPrefix(strings.ToLower(summary), "go") {
+ summary = "Go " + summary
+ }
+
+ // Open file to upload.
+ f, err := os.Open(filename)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ // Prepare multipart payload.
+ body := new(bytes.Buffer)
+ w := multipart.NewWriter(body)
+ if err := w.WriteField("summary", summary); err != nil {
+ return err
+ }
+ for _, l := range labels {
+ if err := w.WriteField("label", l); err != nil {
+ return err
+ }
+ }
+ fw, err := w.CreateFormFile("filename", filename)
+ if err != nil {
+ return err
+ }
+ if _, err = io.Copy(fw, f); err != nil {
+ return err
+ }
+ if err := w.Close(); err != nil {
+ return err
+ }
+
+ // Send the file to Google Code.
+ req, err := http.NewRequest("POST", uploadURL, body)
+ if err != nil {
+ return err
+ }
+ token := fmt.Sprintf("%s:%s", username, password)
+ token = base64.StdEncoding.EncodeToString([]byte(token))
+ req.Header.Set("Authorization", "Basic "+token)
+ req.Header.Set("Content-type", w.FormDataContentType())
+
+ resp, err := http.DefaultTransport.RoundTrip(req)
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode/100 != 2 {
+ fmt.Fprintln(os.Stderr, "upload failed")
+ defer resp.Body.Close()
+ io.Copy(os.Stderr, resp.Body)
+ return fmt.Errorf("upload: %s", resp.Status)
+ }
+ 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()
+ r := bufio.NewReader(f)
+ for i := 0; i < 3; i++ {
+ b, _, err := r.ReadLine()
+ if err != nil {
+ return err
+ }
+ b = bytes.TrimSpace(b)
+ switch i {
+ case 1:
+ username = string(b)
+ case 2:
+ password = string(b)
+ }
+ }
+ return nil
+}
+
+func cp(dst, src string) error {
+ sf, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer sf.Close()
+ df, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer df.Close()
+ _, err = io.Copy(df, sf)
+ return err
+}
+
+func makeTar(targ, workdir string) error {
+ f, err := os.Create(targ)
+ if err != nil {
+ return err
+ }
+ zout := gzip.NewWriter(f)
+ tw := tar.NewWriter(zout)
+
+ filepath.Walk(workdir, filepath.WalkFunc(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)
+ }
+ if fi.IsDir() {
+ return nil
+ }
+ hdr, err := tarFileInfoHeader(fi, path)
+ 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)
+ }
+ r, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+ _, err = io.Copy(tw, r)
+ 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)
+
+ filepath.Walk(workdir, filepath.WalkFunc(func(path string, fi os.FileInfo, err error) error {
+ if fi.IsDir() {
+ return nil
+ }
+ 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
+ w, err := zw.CreateHeader(fh)
+ if err != nil {
+ return err
+ }
+ r, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+ _, err = io.Copy(w, r)
+ 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
+}
+
+// sysStat, if non-nil, populates h from system-dependent fields of fi.
+var sysStat func(fi os.FileInfo, h *tar.Header) error
+
+// Mode constants from the tar spec.
+const (
+ c_ISDIR = 040000
+ c_ISFIFO = 010000
+ c_ISREG = 0100000
+ c_ISLNK = 0120000
+ c_ISBLK = 060000
+ c_ISCHR = 020000
+ c_ISSOCK = 0140000
+)
+
+// tarFileInfoHeader creates a partially-populated Header from an os.FileInfo.
+// The filename parameter is used only in the case of symlinks, to call os.Readlink.
+// If fi is a symlink but filename is empty, an error is returned.
+func tarFileInfoHeader(fi os.FileInfo, filename string) (*tar.Header, error) {
+ h := &tar.Header{
+ Name: fi.Name(),
+ ModTime: fi.ModTime(),
+ Mode: int64(fi.Mode().Perm()), // or'd with c_IS* constants later
+ }
+ switch {
+ case fi.Mode()&os.ModeType == 0:
+ h.Mode |= c_ISREG
+ h.Typeflag = tar.TypeReg
+ h.Size = fi.Size()
+ case fi.IsDir():
+ h.Typeflag = tar.TypeDir
+ h.Mode |= c_ISDIR
+ case fi.Mode()&os.ModeSymlink != 0:
+ h.Typeflag = tar.TypeSymlink
+ h.Mode |= c_ISLNK
+ if filename == "" {
+ return h, fmt.Errorf("archive/tar: unable to populate Header.Linkname of symlinks")
+ }
+ targ, err := os.Readlink(filename)
+ if err != nil {
+ return h, err
+ }
+ h.Linkname = targ
+ case fi.Mode()&os.ModeDevice != 0:
+ if fi.Mode()&os.ModeCharDevice != 0 {
+ h.Mode |= c_ISCHR
+ h.Typeflag = tar.TypeChar
+ } else {
+ h.Mode |= c_ISBLK
+ h.Typeflag = tar.TypeBlock
+ }
+ case fi.Mode()&os.ModeSocket != 0:
+ h.Mode |= c_ISSOCK
+ default:
+ return nil, fmt.Errorf("archive/tar: unknown file mode %v", fi.Mode())
+ }
+ if sysStat != nil {
+ return h, sysStat(fi, h)
+ }
+ return h, nil
+}