| // 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, and OS X. |
| package main |
| |
| import ( |
| "bytes" |
| "encoding/base64" |
| "errors" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "mime/multipart" |
| "net/http" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "strings" |
| ) |
| |
| var ( |
| tag = flag.String("tag", "weekly", "mercurial tag to check out") |
| repo = flag.String("repo", "https://code.google.com/p/go", "repo URL") |
| |
| username, password string // for Google Code upload |
| ) |
| |
| const ( |
| packageMaker = "/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker" |
| uploadURL = "https://go.googlecode.com/files" |
| ) |
| |
| var cleanFiles = []string{ |
| ".hg", |
| ".hgtags", |
| ".hgignore", |
| "VERSION.cache", |
| } |
| |
| 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() |
| } |
| readCredentials() |
| for _, targ := range flag.Args() { |
| p := strings.SplitN(targ, "-", 2) |
| if len(p) != 2 { |
| log.Println("Ignoring unrecognized target:", targ) |
| continue |
| } |
| b := Build{OS: p[0], Arch: p[1]} |
| if err := b.Do(); err != nil { |
| log.Printf("%s: %v", targ, err) |
| } |
| } |
| } |
| |
| type Build struct { |
| 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 |
| } |
| |
| // Build. |
| _, err = b.run(filepath.Join(work, "go/src"), "bash", "make.bash") |
| if err != nil { |
| return err |
| } |
| |
| // Get version string. |
| version, err := b.run("", filepath.Join(b.root, "bin/go"), "version") |
| if err != nil { |
| return err |
| } |
| v := bytes.SplitN(version, []byte(" "), 4) |
| version = bytes.Join(v[2:], []byte(" ")) |
| |
| // Write VERSION file. |
| err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), version, 0644) |
| if err != nil { |
| return err |
| } |
| |
| // Clean goroot. |
| for _, name := range cleanFiles { |
| err = os.RemoveAll(filepath.Join(b.root, name)) |
| if err != nil { |
| return err |
| } |
| } |
| |
| // Create packages. |
| targ := fmt.Sprintf("go.%s.%s-%s", v[2], b.OS, b.Arch) |
| switch b.OS { |
| case "linux", "freebsd": |
| // build tarball |
| targ += ".tar.gz" |
| _, err = b.run("", "tar", "czf", targ, "-C", work, "go") |
| 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 += ".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.5") |
| } |
| if err == nil && password != "" { |
| err = b.upload(string(v[2]), targ) |
| } |
| return err |
| } |
| |
| func (b *Build) run(dir, name string, args ...string) ([]byte, error) { |
| buf := new(bytes.Buffer) |
| cmd := exec.Command(name, args...) |
| cmd.Stdout = buf |
| cmd.Stderr = buf |
| 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:]...) |
| } |
| } |
| } |
| env = append(env, |
| "GOARCH="+b.Arch, |
| "GOHOSTARCH="+b.Arch, |
| "GOHOSTOS="+b.OS, |
| "GOOS="+b.OS, |
| "GOROOT="+b.root, |
| "GOROOT_FINAL=/usr/local/go", |
| ) |
| return env |
| } |
| |
| func (b *Build) upload(version string, filename string) error { |
| // Prepare upload metadata. |
| labels := []string{"Arch-" + b.Arch} |
| os_, arch := b.OS, b.Arch |
| switch b.Arch { |
| case "386": |
| arch = "32-bit" |
| case "amd64": |
| arch = "64-bit" |
| } |
| 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") |
| } |
| summary := fmt.Sprintf("Go %s %s (%s)", version, os_, arch) |
| |
| // 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 exists(path string) bool { |
| _, err := os.Stat(path) |
| return err == nil |
| } |
| |
| func readCredentials() { |
| name := filepath.Join(os.Getenv("HOME"), ".gobuildkey") |
| c, err := ioutil.ReadFile(name) |
| if err != nil { |
| log.Println("readCredentials:", err) |
| return |
| } |
| v := bytes.Split(c, []byte("\n")) |
| if len(v) >= 3 { |
| username, password = string(v[1]), string(v[2]) |
| } |
| } |