build/version/version.go

460 строки
12 KiB
Go
Исходник Обычный вид История

version: convert to generated copy of golang.org/dl/internal/version Package version has been copied from golang.org/x/build/version to golang.org/dl/internal/version as part of golang/go#23223. The old package was marked deprecated but kept for backwards compatibility reasons. Fixes and changes have not been backported from the new dl/internal/version package (to avoid spending time unproductively). bundle has recently been updated to accept an empty prefix in CL 105515. This allows one to use bundle to make a generated copy of a package. A generated copied package is better than a manually-maintained copied package for the following reasons: 1. Keeping the copied package up to date with upstream is low effort, just need to run go generate. 2. It's clear to contributors that the copied package isn't the canonical version, since there is a generated comment like "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT." at the top of the file. It's easy to reject changes to the copied package, or redirect them to the upstream. Overall, making the copied package generated rather than hand-written reduces the maintenace cost, and makes it more viable to keep it more up to date with upstream. The diff to version.go in this CL is due to some dl/internal/version CLs being effectively backported by bundle, including CL 134435, CL 143545, and CL 144698. Updates golang/go#23223 Fixes golang/go#23635 Change-Id: Id8cd63c52b660b6817e6fdba080373966789e1e8 Reviewed-on: https://go-review.googlesource.com/c/148881 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2018-11-10 04:59:16 +03:00
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
//go:generate bundle -o version.go -prefix= golang.org/dl/internal/version
// The version package permits running a specific version of Go.
//
version: convert to generated copy of golang.org/dl/internal/version Package version has been copied from golang.org/x/build/version to golang.org/dl/internal/version as part of golang/go#23223. The old package was marked deprecated but kept for backwards compatibility reasons. Fixes and changes have not been backported from the new dl/internal/version package (to avoid spending time unproductively). bundle has recently been updated to accept an empty prefix in CL 105515. This allows one to use bundle to make a generated copy of a package. A generated copied package is better than a manually-maintained copied package for the following reasons: 1. Keeping the copied package up to date with upstream is low effort, just need to run go generate. 2. It's clear to contributors that the copied package isn't the canonical version, since there is a generated comment like "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT." at the top of the file. It's easy to reject changes to the copied package, or redirect them to the upstream. Overall, making the copied package generated rather than hand-written reduces the maintenace cost, and makes it more viable to keep it more up to date with upstream. The diff to version.go in this CL is due to some dl/internal/version CLs being effectively backported by bundle, including CL 134435, CL 143545, and CL 144698. Updates golang/go#23223 Fixes golang/go#23635 Change-Id: Id8cd63c52b660b6817e6fdba080373966789e1e8 Reviewed-on: https://go-review.googlesource.com/c/148881 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2018-11-10 04:59:16 +03:00
package version
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"crypto/sha256"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"runtime"
"strings"
"time"
"golang.org/x/build/envutil"
)
func init() {
http.DefaultTransport = &userAgentTransport{http.DefaultTransport}
}
// Run runs the "go" tool of the provided Go version.
func Run(version string) {
log.SetFlags(0)
root, err := goroot(version)
if err != nil {
log.Fatalf("%s: %v", version, err)
}
if len(os.Args) == 2 && os.Args[1] == "download" {
if err := install(root, version); err != nil {
log.Fatalf("%s: download failed: %v", version, err)
}
os.Exit(0)
}
if _, err := os.Stat(filepath.Join(root, unpackedOkay)); err != nil {
log.Fatalf("%s: not downloaded. Run '%s download' to install to %v", version, version, root)
}
gobin := filepath.Join(root, "bin", "go"+exe())
cmd := exec.Command(gobin, os.Args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
version: convert to generated copy of golang.org/dl/internal/version Package version has been copied from golang.org/x/build/version to golang.org/dl/internal/version as part of golang/go#23223. The old package was marked deprecated but kept for backwards compatibility reasons. Fixes and changes have not been backported from the new dl/internal/version package (to avoid spending time unproductively). bundle has recently been updated to accept an empty prefix in CL 105515. This allows one to use bundle to make a generated copy of a package. A generated copied package is better than a manually-maintained copied package for the following reasons: 1. Keeping the copied package up to date with upstream is low effort, just need to run go generate. 2. It's clear to contributors that the copied package isn't the canonical version, since there is a generated comment like "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT." at the top of the file. It's easy to reject changes to the copied package, or redirect them to the upstream. Overall, making the copied package generated rather than hand-written reduces the maintenace cost, and makes it more viable to keep it more up to date with upstream. The diff to version.go in this CL is due to some dl/internal/version CLs being effectively backported by bundle, including CL 134435, CL 143545, and CL 144698. Updates golang/go#23223 Fixes golang/go#23635 Change-Id: Id8cd63c52b660b6817e6fdba080373966789e1e8 Reviewed-on: https://go-review.googlesource.com/c/148881 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2018-11-10 04:59:16 +03:00
newPath := filepath.Join(root, "bin")
if p := os.Getenv("PATH"); p != "" {
newPath += string(filepath.ListSeparator) + p
}
// This envutil.Dedup call is unnecessary when the binary is
// built with Go 1.9+, but keep it around for now until Go 1.8
// is no longer seen in the wild in common distros.
cmd.Env = envutil.Dedup(caseInsensitiveEnv, append(os.Environ(), "GOROOT="+root, "PATH="+newPath))
if err := cmd.Run(); err != nil {
// TODO: return the same exit status maybe.
os.Exit(1)
}
os.Exit(0)
}
// install installs a version of Go to the named target directory, creating the
// directory as needed.
func install(targetDir, version string) error {
if _, err := os.Stat(filepath.Join(targetDir, unpackedOkay)); err == nil {
log.Printf("%s: already downloaded in %v", version, targetDir)
return nil
}
if err := os.MkdirAll(targetDir, 0755); err != nil {
return err
}
version: convert to generated copy of golang.org/dl/internal/version Package version has been copied from golang.org/x/build/version to golang.org/dl/internal/version as part of golang/go#23223. The old package was marked deprecated but kept for backwards compatibility reasons. Fixes and changes have not been backported from the new dl/internal/version package (to avoid spending time unproductively). bundle has recently been updated to accept an empty prefix in CL 105515. This allows one to use bundle to make a generated copy of a package. A generated copied package is better than a manually-maintained copied package for the following reasons: 1. Keeping the copied package up to date with upstream is low effort, just need to run go generate. 2. It's clear to contributors that the copied package isn't the canonical version, since there is a generated comment like "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT." at the top of the file. It's easy to reject changes to the copied package, or redirect them to the upstream. Overall, making the copied package generated rather than hand-written reduces the maintenace cost, and makes it more viable to keep it more up to date with upstream. The diff to version.go in this CL is due to some dl/internal/version CLs being effectively backported by bundle, including CL 134435, CL 143545, and CL 144698. Updates golang/go#23223 Fixes golang/go#23635 Change-Id: Id8cd63c52b660b6817e6fdba080373966789e1e8 Reviewed-on: https://go-review.googlesource.com/c/148881 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2018-11-10 04:59:16 +03:00
goURL := versionArchiveURL(version)
res, err := http.Head(goURL)
if err != nil {
return err
}
if res.StatusCode == http.StatusNotFound {
return fmt.Errorf("no binary release of %v for %v/%v at %v", version, getOS(), runtime.GOARCH, goURL)
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("server returned %v checking size of %v", http.StatusText(res.StatusCode), goURL)
}
base := path.Base(goURL)
archiveFile := filepath.Join(targetDir, base)
if fi, err := os.Stat(archiveFile); err != nil || fi.Size() != res.ContentLength {
if err != nil && !os.IsNotExist(err) {
// Something weird. Don't try to download.
return err
}
if err := copyFromURL(archiveFile, goURL); err != nil {
return fmt.Errorf("error downloading %v: %v", goURL, err)
}
fi, err = os.Stat(archiveFile)
if err != nil {
return err
}
if fi.Size() != res.ContentLength {
return fmt.Errorf("downloaded file %s size %v doesn't match server size %v", archiveFile, fi.Size(), res.ContentLength)
}
}
wantSHA, err := slurpURLToString(goURL + ".sha256")
if err != nil {
return err
}
if err := verifySHA256(archiveFile, strings.TrimSpace(wantSHA)); err != nil {
return fmt.Errorf("error verifying SHA256 of %v: %v", archiveFile, err)
}
log.Printf("Unpacking %v ...", archiveFile)
if err := unpackArchive(targetDir, archiveFile); err != nil {
return fmt.Errorf("extracting archive %v: %v", archiveFile, err)
}
if err := ioutil.WriteFile(filepath.Join(targetDir, unpackedOkay), nil, 0644); err != nil {
return err
}
log.Printf("Success. You may now run '%v'", version)
return nil
}
// unpackArchive unpacks the provided archive zip or tar.gz file to targetDir,
// removing the "go/" prefix from file entries.
func unpackArchive(targetDir, archiveFile string) error {
switch {
case strings.HasSuffix(archiveFile, ".zip"):
return unpackZip(targetDir, archiveFile)
case strings.HasSuffix(archiveFile, ".tar.gz"):
return unpackTarGz(targetDir, archiveFile)
default:
return errors.New("unsupported archive file")
}
}
// unpackTarGz is the tar.gz implementation of unpackArchive.
func unpackTarGz(targetDir, archiveFile string) error {
r, err := os.Open(archiveFile)
if err != nil {
return err
}
defer r.Close()
madeDir := map[string]bool{}
zr, err := gzip.NewReader(r)
if err != nil {
return err
}
tr := tar.NewReader(zr)
for {
f, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if !validRelPath(f.Name) {
return fmt.Errorf("tar file contained invalid name %q", f.Name)
}
rel := filepath.FromSlash(strings.TrimPrefix(f.Name, "go/"))
abs := filepath.Join(targetDir, rel)
fi := f.FileInfo()
mode := fi.Mode()
switch {
case mode.IsRegular():
// Make the directory. This is redundant because it should
// already be made by a directory entry in the tar
// beforehand. Thus, don't check for errors; the next
// write will fail with the same error.
dir := filepath.Dir(abs)
if !madeDir[dir] {
if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil {
return err
}
madeDir[dir] = true
}
wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm())
if err != nil {
return err
}
n, err := io.Copy(wf, tr)
if closeErr := wf.Close(); closeErr != nil && err == nil {
err = closeErr
}
if err != nil {
return fmt.Errorf("error writing to %s: %v", abs, err)
}
if n != f.Size {
return fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size)
}
if !f.ModTime.IsZero() {
if err := os.Chtimes(abs, f.ModTime, f.ModTime); err != nil {
// benign error. Gerrit doesn't even set the
// modtime in these, and we don't end up relying
// on it anywhere (the gomote push command relies
// on digests only), so this is a little pointless
// for now.
log.Printf("error changing modtime: %v", err)
}
}
case mode.IsDir():
if err := os.MkdirAll(abs, 0755); err != nil {
return err
}
madeDir[abs] = true
default:
return fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode)
}
}
return nil
}
// unpackZip is the zip implementation of unpackArchive.
func unpackZip(targetDir, archiveFile string) error {
zr, err := zip.OpenReader(archiveFile)
if err != nil {
return err
}
defer zr.Close()
for _, f := range zr.File {
name := strings.TrimPrefix(f.Name, "go/")
outpath := filepath.Join(targetDir, name)
if f.FileInfo().IsDir() {
if err := os.MkdirAll(outpath, 0755); err != nil {
return err
}
continue
}
rc, err := f.Open()
if err != nil {
return err
}
// File
if err := os.MkdirAll(filepath.Dir(outpath), 0755); err != nil {
return err
}
out, err := os.OpenFile(outpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
_, err = io.Copy(out, rc)
rc.Close()
if err != nil {
out.Close()
return err
}
if err := out.Close(); err != nil {
return err
}
}
return nil
}
// verifySHA256 reports whether the named file has contents with
// SHA-256 of the given wantHex value.
func verifySHA256(file, wantHex string) error {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
hash := sha256.New()
if _, err := io.Copy(hash, f); err != nil {
return err
}
if fmt.Sprintf("%x", hash.Sum(nil)) != wantHex {
return fmt.Errorf("%s corrupt? does not have expected SHA-256 of %v", file, wantHex)
}
return nil
}
// slurpURLToString downloads the given URL and returns it as a string.
func slurpURLToString(url_ string) (string, error) {
res, err := http.Get(url_)
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("%s: %v", url_, res.Status)
}
slurp, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("reading %s: %v", url_, err)
}
return string(slurp), nil
}
// copyFromURL downloads srcURL to dstFile.
func copyFromURL(dstFile, srcURL string) (err error) {
f, err := os.Create(dstFile)
if err != nil {
return err
}
defer func() {
if err != nil {
f.Close()
os.Remove(dstFile)
}
}()
c := &http.Client{
Transport: &userAgentTransport{&http.Transport{
// It's already compressed. Prefer accurate ContentLength.
// (Not that GCS would try to compress it, though)
DisableCompression: true,
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
}},
}
res, err := c.Get(srcURL)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return errors.New(res.Status)
}
pw := &progressWriter{w: f, total: res.ContentLength}
n, err := io.Copy(pw, res.Body)
if err != nil {
return err
}
if res.ContentLength != -1 && res.ContentLength != n {
return fmt.Errorf("copied %v bytes; expected %v", n, res.ContentLength)
}
pw.update() // 100%
return f.Close()
}
type progressWriter struct {
w io.Writer
n int64
total int64
last time.Time
}
func (p *progressWriter) update() {
end := " ..."
if p.n == p.total {
end = ""
}
fmt.Fprintf(os.Stderr, "Downloaded %0.1f%% (%d / %d bytes)%s\n",
(100.0*float64(p.n))/float64(p.total),
p.n, p.total, end)
}
func (p *progressWriter) Write(buf []byte) (n int, err error) {
n, err = p.w.Write(buf)
p.n += int64(n)
if now := time.Now(); now.Unix() != p.last.Unix() {
p.update()
p.last = now
}
return
}
// getOS returns runtime.GOOS. It exists as a function just for lazy
// testing of the Windows zip path when running on Linux/Darwin.
func getOS() string {
return runtime.GOOS
}
// versionArchiveURL returns the zip or tar.gz URL of the given Go version.
version: convert to generated copy of golang.org/dl/internal/version Package version has been copied from golang.org/x/build/version to golang.org/dl/internal/version as part of golang/go#23223. The old package was marked deprecated but kept for backwards compatibility reasons. Fixes and changes have not been backported from the new dl/internal/version package (to avoid spending time unproductively). bundle has recently been updated to accept an empty prefix in CL 105515. This allows one to use bundle to make a generated copy of a package. A generated copied package is better than a manually-maintained copied package for the following reasons: 1. Keeping the copied package up to date with upstream is low effort, just need to run go generate. 2. It's clear to contributors that the copied package isn't the canonical version, since there is a generated comment like "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT." at the top of the file. It's easy to reject changes to the copied package, or redirect them to the upstream. Overall, making the copied package generated rather than hand-written reduces the maintenace cost, and makes it more viable to keep it more up to date with upstream. The diff to version.go in this CL is due to some dl/internal/version CLs being effectively backported by bundle, including CL 134435, CL 143545, and CL 144698. Updates golang/go#23223 Fixes golang/go#23635 Change-Id: Id8cd63c52b660b6817e6fdba080373966789e1e8 Reviewed-on: https://go-review.googlesource.com/c/148881 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2018-11-10 04:59:16 +03:00
func versionArchiveURL(version string) string {
goos := getOS()
// TODO: Maybe we should parse
// https://storage.googleapis.com/go-builder-data/dl-index.txt ?
// Let's just guess the URL for now and see if it's there.
// Then we don't have to maintain that txt file too.
ext := ".tar.gz"
if goos == "windows" {
ext = ".zip"
}
arch := runtime.GOARCH
if goos == "linux" && runtime.GOARCH == "arm" {
arch = "armv6l"
}
version: convert to generated copy of golang.org/dl/internal/version Package version has been copied from golang.org/x/build/version to golang.org/dl/internal/version as part of golang/go#23223. The old package was marked deprecated but kept for backwards compatibility reasons. Fixes and changes have not been backported from the new dl/internal/version package (to avoid spending time unproductively). bundle has recently been updated to accept an empty prefix in CL 105515. This allows one to use bundle to make a generated copy of a package. A generated copied package is better than a manually-maintained copied package for the following reasons: 1. Keeping the copied package up to date with upstream is low effort, just need to run go generate. 2. It's clear to contributors that the copied package isn't the canonical version, since there is a generated comment like "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT." at the top of the file. It's easy to reject changes to the copied package, or redirect them to the upstream. Overall, making the copied package generated rather than hand-written reduces the maintenace cost, and makes it more viable to keep it more up to date with upstream. The diff to version.go in this CL is due to some dl/internal/version CLs being effectively backported by bundle, including CL 134435, CL 143545, and CL 144698. Updates golang/go#23223 Fixes golang/go#23635 Change-Id: Id8cd63c52b660b6817e6fdba080373966789e1e8 Reviewed-on: https://go-review.googlesource.com/c/148881 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2018-11-10 04:59:16 +03:00
return "https://storage.googleapis.com/golang/" + version + "." + goos + "-" + arch + ext
}
const caseInsensitiveEnv = runtime.GOOS == "windows"
// unpackedOkay is a sentinel zero-byte file to indicate that the Go
// version was downloaded and unpacked successfully.
const unpackedOkay = ".unpacked-success"
func exe() string {
if runtime.GOOS == "windows" {
return ".exe"
}
return ""
}
func goroot(version string) (string, error) {
home, err := homedir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %v", err)
}
return filepath.Join(home, "sdk", version), nil
}
func homedir() (string, error) {
switch getOS() {
case "plan9":
return "", fmt.Errorf("%q not yet supported", runtime.GOOS)
case "windows":
if dir := os.Getenv("USERPROFILE"); dir != "" {
return dir, nil
}
return "", errors.New("can't find user home directory; %USERPROFILE% is empty")
default:
if dir := os.Getenv("HOME"); dir != "" {
return dir, nil
}
if u, err := user.Current(); err == nil && u.HomeDir != "" {
return u.HomeDir, nil
}
return "", errors.New("can't find user home directory; $HOME is empty")
}
}
func validRelPath(p string) bool {
if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") {
return false
}
return true
}
type userAgentTransport struct {
rt http.RoundTripper
}
func (uat userAgentTransport) RoundTrip(r *http.Request) (*http.Response, error) {
version := runtime.Version()
if strings.Contains(version, "devel") {
// Strip the SHA hash and date. We don't want spaces or other tokens (see RFC2616 14.43)
version = "devel"
}
r.Header.Set("User-Agent", "golang-x-build-version/"+version)
return uat.rt.RoundTrip(r)
}