зеркало из https://github.com/golang/build.git
357 строки
10 KiB
Go
357 строки
10 KiB
Go
// Copyright 2015 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.
|
|
|
|
// The upload command writes a file to Google Cloud Storage. It's used
|
|
// exclusively by the Makefiles in the Go project repos. Think of it
|
|
// as a very light version of gsutil or gcloud, but with some
|
|
// Go-specific configuration knowledge baked in.
|
|
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"context"
|
|
"crypto/md5"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"cloud.google.com/go/storage"
|
|
"golang.org/x/build/envutil"
|
|
)
|
|
|
|
var (
|
|
public = flag.Bool("public", false, "object should be world-readable")
|
|
cacheable = flag.Bool("cacheable", true, "object should be cacheable")
|
|
file = flag.String("file", "-", "Filename to read object from, or '-' for stdin. If it begins with 'go:' then the rest is considered to be a Go target to install first, and then upload.")
|
|
verbose = flag.Bool("verbose", false, "verbose logging")
|
|
osarch = flag.String("osarch", "", "Optional 'GOOS-GOARCH' value to cross-compile; used only if --file begins with 'go:'. As a special case, if the value contains a '.' byte, anything up to and including that period is discarded.")
|
|
project = flag.String("project", "", "GCE Project. If blank, it's automatically inferred from the bucket name for the common Go buckets.")
|
|
tags = flag.String("tags", "", "tags to pass to go list, go install, etc. Only applicable if the --file value begins with 'go:'")
|
|
doGzip = flag.Bool("gzip", false, "gzip the stored contents (not the upload's Content-Encoding); this forces the Content-Type to be application/octet-stream. To prevent misuse, the object name must also end in '.gz'")
|
|
extraEnv = flag.String("extraenv", "", "comma-separated list of addition KEY=val environment pairs to include in build environment when building a target to upload")
|
|
installSuffix = flag.String("installsuffix", "", "installsuffix for the go command")
|
|
static = flag.Bool("static", false, "compile the binary statically, adds necessary ldflags")
|
|
)
|
|
|
|
// to match uploads to e.g. https://storage.googleapis.com/golang/go1.4-bootstrap-20170531.tar.gz.
|
|
var go14BootstrapRx = regexp.MustCompile(`^go1\.4-bootstrap-20\d{6}\.tar\.gz$`)
|
|
|
|
func main() {
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(os.Stderr, `Usage: upload [--public] [--file=...] <bucket/object>
|
|
|
|
If <bucket/object> is of the form "golang/go1.4-bootstrap-20yymmdd.tar.gz",
|
|
then the current release-branch.go1.4 is uploaded from Gerrit, with each
|
|
tar entry filename beginning with the prefix "go/".
|
|
|
|
`)
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.Parse()
|
|
if flag.NArg() != 1 {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
args := strings.SplitN(flag.Arg(0), "/", 2)
|
|
if len(args) != 2 {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
if strings.HasPrefix(*file, "go:") {
|
|
buildGoTarget()
|
|
}
|
|
bucket, object := args[0], args[1]
|
|
|
|
// Special support for auto-tarring up Go 1.4 tarballs from the 1.4 release branch.
|
|
is14Src := bucket == "golang" && go14BootstrapRx.MatchString(object)
|
|
if is14Src {
|
|
if *file != "-" {
|
|
log.Fatalf("invalid use of --file with Go 1.4 tarball %v", object)
|
|
}
|
|
*doGzip = true
|
|
*public = true
|
|
*cacheable = true
|
|
}
|
|
|
|
if *doGzip && !strings.HasSuffix(object, ".gz") {
|
|
log.Fatalf("--gzip flag requires object ending in .gz")
|
|
}
|
|
|
|
proj := *project
|
|
if proj == "" {
|
|
proj, _ = bucketProject[bucket]
|
|
if proj == "" {
|
|
log.Fatalf("bucket %q doesn't have an associated project in upload.go", bucket)
|
|
}
|
|
}
|
|
|
|
ctx := context.Background()
|
|
storageClient, err := storage.NewClient(ctx)
|
|
if err != nil {
|
|
log.Fatalf("storage.NewClient: %v", err)
|
|
}
|
|
|
|
if is14Src {
|
|
_, err := storageClient.Bucket(bucket).Object(object).Attrs(context.Background())
|
|
if err != storage.ErrObjectNotExist {
|
|
if err == nil {
|
|
log.Fatalf("object %v already exists; refusing to overwrite.", object)
|
|
}
|
|
log.Fatalf("error checking for %v: %v", object, err)
|
|
}
|
|
} else if alreadyUploaded(storageClient, bucket, object) {
|
|
if *verbose {
|
|
log.Printf("Already uploaded.")
|
|
}
|
|
return
|
|
}
|
|
|
|
w := storageClient.Bucket(bucket).Object(object).NewWriter(ctx)
|
|
// If you don't give the owners access, the web UI seems to
|
|
// have a bug and doesn't have access to see that it's public, so
|
|
// won't render the "Shared Publicly" link. So we do that, even
|
|
// though it's dumb and unnecessary otherwise:
|
|
w.ACL = append(w.ACL, storage.ACLRule{Entity: storage.ACLEntity("project-owners-" + proj), Role: storage.RoleOwner})
|
|
if *public {
|
|
w.ACL = append(w.ACL, storage.ACLRule{Entity: storage.AllUsers, Role: storage.RoleReader})
|
|
if !*cacheable {
|
|
w.CacheControl = "no-cache"
|
|
}
|
|
}
|
|
var content io.Reader
|
|
switch {
|
|
case is14Src:
|
|
content = generate14Tarfile()
|
|
case *file == "-":
|
|
content = os.Stdin
|
|
default:
|
|
content, err = os.Open(*file)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
if *doGzip {
|
|
var zbuf bytes.Buffer
|
|
zw := gzip.NewWriter(&zbuf)
|
|
if _, err := io.Copy(zw, content); err != nil {
|
|
log.Fatalf("compressing content: %v", err)
|
|
}
|
|
if err := zw.Close(); err != nil {
|
|
log.Fatalf("gzip.Close: %v", err)
|
|
}
|
|
content = &zbuf
|
|
}
|
|
|
|
const maxSlurp = 1 << 20
|
|
var buf bytes.Buffer
|
|
n, err := io.CopyN(&buf, content, maxSlurp)
|
|
if err != nil && err != io.EOF {
|
|
log.Fatalf("Error reading from stdin: %v, %v", n, err)
|
|
}
|
|
if *doGzip {
|
|
w.ContentType = "application/octet-stream"
|
|
} else {
|
|
w.ContentType = http.DetectContentType(buf.Bytes())
|
|
}
|
|
|
|
_, err = io.Copy(w, io.MultiReader(&buf, content))
|
|
if cerr := w.Close(); cerr != nil && err == nil {
|
|
err = cerr
|
|
}
|
|
if err != nil {
|
|
log.Fatalf("Write error: %v", err)
|
|
}
|
|
if *verbose {
|
|
log.Printf("Wrote %v", object)
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
|
|
var bucketProject = map[string]string{
|
|
"dev-gccgo-builder-data": "gccgo-dashboard-dev",
|
|
"dev-go-builder-data": "go-dashboard-dev",
|
|
"gccgo-builder-data": "gccgo-dashboard-builders",
|
|
"go-builder-data": "symbolic-datum-552",
|
|
"go-build-log": "symbolic-datum-552",
|
|
"http2-demo-server-tls": "symbolic-datum-552",
|
|
"winstrap": "999119582588",
|
|
"gobuilder": "999119582588", // deprecated
|
|
"golang": "999119582588",
|
|
}
|
|
|
|
func buildGoTarget() {
|
|
target := strings.TrimPrefix(*file, "go:")
|
|
var goos, goarch string
|
|
if *osarch != "" {
|
|
*osarch = strings.TrimSuffix(*osarch, ".gz")
|
|
*osarch = (*osarch)[strings.LastIndex(*osarch, ".")+1:]
|
|
v := strings.Split(*osarch, "-")
|
|
if len(v) == 3 {
|
|
v = v[:2] // support e.g. "linux-arm-scaleway" as GOOS=linux, GOARCH=arm
|
|
}
|
|
if len(v) != 2 || v[0] == "" || v[1] == "" {
|
|
log.Fatalf("invalid -osarch value %q", *osarch)
|
|
}
|
|
goos, goarch = v[0], v[1]
|
|
}
|
|
|
|
env := append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch)
|
|
if *extraEnv != "" {
|
|
env = append(env, strings.Split(*extraEnv, ",")...)
|
|
}
|
|
env = envutil.Dedup(runtime.GOOS == "windows", env)
|
|
cmd := exec.Command("go",
|
|
"list",
|
|
"--tags="+*tags,
|
|
"--installsuffix="+*installSuffix,
|
|
"-f", "{{.Target}}",
|
|
target)
|
|
cmd.Env = env
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
log.Fatalf("go list: %v", err)
|
|
}
|
|
outFile := string(bytes.TrimSpace(out))
|
|
fi0, err := os.Stat(outFile)
|
|
if os.IsNotExist(err) {
|
|
if *verbose {
|
|
log.Printf("File %s doesn't exist; building...", outFile)
|
|
}
|
|
}
|
|
|
|
version := os.Getenv("USER") + "-" + time.Now().Format(time.RFC3339)
|
|
ldflags := "-X main.Version=" + version
|
|
if *static {
|
|
ldflags = "-linkmode=external -extldflags '-static -pthread' " + ldflags
|
|
}
|
|
cmd = exec.Command("go",
|
|
"install",
|
|
"--tags="+*tags,
|
|
"--installsuffix="+*installSuffix,
|
|
"-x",
|
|
"--ldflags="+ldflags,
|
|
target)
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
if *verbose {
|
|
cmd.Stdout = os.Stdout
|
|
}
|
|
cmd.Env = env
|
|
if err := cmd.Run(); err != nil {
|
|
log.Fatalf("go install %s: %v, %s", target, err, stderr.Bytes())
|
|
}
|
|
|
|
fi1, err := os.Stat(outFile)
|
|
if err != nil {
|
|
log.Fatalf("Expected output file %s stat failure after go install %v: %v", outFile, target, err)
|
|
}
|
|
if !os.SameFile(fi0, fi1) {
|
|
if *verbose {
|
|
log.Printf("File %s rebuilt.", outFile)
|
|
}
|
|
}
|
|
*file = outFile
|
|
}
|
|
|
|
// alreadyUploaded reports whether *file has already been uploaded and the correct contents
|
|
// are on cloud storage already.
|
|
func alreadyUploaded(storageClient *storage.Client, bucket, object string) bool {
|
|
if *file == "-" {
|
|
return false // don't know.
|
|
}
|
|
o, err := storageClient.Bucket(bucket).Object(object).Attrs(context.Background())
|
|
if err == storage.ErrObjectNotExist {
|
|
return false
|
|
}
|
|
if err != nil {
|
|
log.Printf("Warning: stat failure: %v", err)
|
|
return false
|
|
}
|
|
m5 := md5.New()
|
|
fi, err := os.Stat(*file)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if fi.Size() != o.Size {
|
|
return false
|
|
}
|
|
f, err := os.Open(*file)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
n, err := io.Copy(m5, f)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if n != fi.Size() {
|
|
log.Printf("Warning: file size of %v changed", *file)
|
|
}
|
|
return bytes.Equal(m5.Sum(nil), o.MD5)
|
|
}
|
|
|
|
// generate14Tarfile downloads the release-branch.go1.4 release branch
|
|
// tarball and returns it uncompressed, with the "go/" prefix before
|
|
// each tar header's filename.
|
|
func generate14Tarfile() io.Reader {
|
|
const tarURL = "https://go.googlesource.com/go/+archive/release-branch.go1.4.tar.gz"
|
|
res, err := http.Get(tarURL)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
log.Fatalf("%v: %v", tarURL, res.Status)
|
|
}
|
|
if got, want := res.Header.Get("Content-Type"), "application/x-gzip"; got != want {
|
|
log.Fatalf("%v: response Content-Type = %q; expected %q", tarURL, got, want)
|
|
}
|
|
|
|
var out bytes.Buffer // output tar (not gzipped)
|
|
|
|
tw := tar.NewWriter(&out)
|
|
|
|
zr, err := gzip.NewReader(res.Body)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
tr := tar.NewReader(zr)
|
|
|
|
for {
|
|
hdr, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
switch hdr.Typeflag {
|
|
case tar.TypeReg, tar.TypeRegA, tar.TypeSymlink, tar.TypeDir:
|
|
// Accept these.
|
|
default:
|
|
continue
|
|
}
|
|
hdr.Name = "go/" + hdr.Name
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
log.Fatalf("WriteHeader: %v", err)
|
|
}
|
|
if _, err := io.Copy(tw, tr); err != nil {
|
|
log.Fatalf("tar copying %v: %v", hdr.Name, err)
|
|
}
|
|
}
|
|
if err := tw.Close(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return &out
|
|
}
|