mirror of
https://github.com/golang/go
synced 2024-11-20 00:04:43 -07:00
220a6de47e
This CL adjusts code referring to src/pkg to refer to src. Immediately after submitting this CL, I will submit a change doing 'hg mv src/pkg/* src'. That change will be too large to review with Rietveld but will contain only the 'hg mv'. This CL will break the build. The followup 'hg mv' will fix it. For more about the move, see golang.org/s/go14nopkg. LGTM=r R=r CC=golang-codereviews https://golang.org/cl/134570043
1036 lines
24 KiB
Go
1036 lines
24 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, 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"
|
|
storage "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.3"
|
|
defaultTourTag = "release-branch.go1.3"
|
|
)
|
|
|
|
// 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/exp",
|
|
"src/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)
|
|
}
|
|
}
|
|
ok := true
|
|
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)
|
|
ok = false
|
|
}
|
|
}
|
|
if !ok {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
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-NtF3iqIQz",
|
|
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
|
|
}
|