1
0
mirror of https://github.com/golang/go synced 2024-11-18 19:04:40 -07:00
go/misc/dist/bindist.go
Brad Fitzpatrick 2ae8605859 misc/dist: use archive/zip, seek out windows deps, add --upload flag
Use archive/zip instead of 7z on Windows.

Look for all Windows deps before starting build, and include looking
for them in their common locations instead of making users update
their PATHs.

Add an --upload flag that, if set to false, doesn't require credential
files.

R=golang-dev, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5794046
2012-03-11 21:02:40 -07:00

618 lines
13 KiB
Go

// 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 (
"archive/zip"
"bufio"
"bytes"
"encoding/base64"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"os/exec"
"path/filepath"
"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")
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",
}
var sourceCleanFiles = []string{
"bin",
"pkg",
}
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 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
}
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
}
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 = b.run("", "tar", "czf", targ, "-C", work, "go")
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")
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("Go %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("Go %s (source only)", version)
}
// 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 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
}