blob: c64b28b6135b293a37e7de5328c0acba0f680356 [file] [log] [blame]
// 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.
//
package version
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"crypto/sha256"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"os/user"
"path"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"syscall"
"time"
)
// RunTip runs the "go" tool from the development tree.
func RunTip() {
log.SetFlags(0)
root, err := goroot("gotip")
if err != nil {
log.Fatalf("gotip: %v", err)
}
if len(os.Args) > 1 && os.Args[1] == "download" {
switch len(os.Args) {
case 2:
if err := installTip(root, ""); err != nil {
log.Fatalf("gotip: %v", err)
}
case 3:
if err := installTip(root, os.Args[2]); err != nil {
log.Fatalf("gotip: %v", err)
}
default:
log.Fatalf("gotip: usage: gotip download [CL number | branch name]")
}
log.Printf("Success. You may now run 'gotip'!")
os.Exit(0)
}
gobin := filepath.Join(root, "bin", "go"+exe())
if _, err := os.Stat(gobin); err != nil {
log.Fatalf("gotip: not downloaded. Run 'gotip download' to install to %v", root)
}
runGo(root)
}
func installTip(root, target string) error {
git := func(args ...string) error {
cmd := exec.Command("git", args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = root
return cmd.Run()
}
gitOutput := func(args ...string) ([]byte, error) {
cmd := exec.Command("git", args...)
cmd.Dir = root
return cmd.Output()
}
if _, err := os.Stat(filepath.Join(root, ".git")); err != nil {
if err := os.MkdirAll(root, 0755); err != nil {
return fmt.Errorf("failed to create repository: %v", err)
}
if err := git("clone", "--depth=1", "https://go.googlesource.com/go", root); err != nil {
return fmt.Errorf("failed to clone git repository: %v", err)
}
}
// If the argument is a simple decimal number, consider it a CL number.
// Otherwise, consider it a branch name. If it's missing, fetch master.
if n, _ := strconv.Atoi(target); n >= 1 && strconv.Itoa(n) == target {
fmt.Fprintf(os.Stderr, "This will download and execute code from golang.org/cl/%s, continue? [y/n] ", target)
var answer string
if fmt.Scanln(&answer); answer != "y" {
return fmt.Errorf("interrupted")
}
// ls-remote outputs a number of lines like:
// 2621ba2c60d05ec0b9ef37cd71e45047b004cead refs/changes/37/227037/1
// 51f2af2be0878e1541d2769bd9d977a7e99db9ab refs/changes/37/227037/2
// af1f3b008281c61c54a5d203ffb69334b7af007c refs/changes/37/227037/3
// 6a10ebae05ce4b01cb93b73c47bef67c0f5c5f2a refs/changes/37/227037/meta
refs, err := gitOutput("ls-remote")
if err != nil {
return fmt.Errorf("failed to list remotes: %v", err)
}
r := regexp.MustCompile(`refs/changes/\d\d/` + target + `/(\d+)`)
match := r.FindAllStringSubmatch(string(refs), -1)
if match == nil {
return fmt.Errorf("CL %v not found", target)
}
var ref string
var patchSet int
for _, m := range match {
ps, _ := strconv.Atoi(m[1])
if ps > patchSet {
patchSet = ps
ref = m[0]
}
}
log.Printf("Fetching CL %v, Patch Set %v...", target, patchSet)
if err := git("fetch", "origin", ref); err != nil {
return fmt.Errorf("failed to fetch %s: %v", ref, err)
}
} else if target != "" {
log.Printf("Fetching branch %v...", target)
ref := "refs/heads/" + target
if err := git("fetch", "origin", ref); err != nil {
return fmt.Errorf("failed to fetch %s: %v", ref, err)
}
} else {
log.Printf("Updating the go development tree...")
if err := git("fetch", "origin", "master"); err != nil {
return fmt.Errorf("failed to fetch git repository updates: %v", err)
}
}
// Use checkout and a detached HEAD, because it will refuse to overwrite
// local changes, and warn if commits are being left behind, but will not
// mind if master is force-pushed upstream.
if err := git("-c", "advice.detachedHead=false", "checkout", "FETCH_HEAD"); err != nil {
return fmt.Errorf("failed to checkout git repository: %v", err)
}
// It shouldn't be the case, but in practice sometimes binary artifacts
// generated by earlier Go versions interfere with the build.
//
// Ask the user what to do about them if they are not gitignored. They might
// be artifacts that used to be ignored in previous versions, or precious
// uncommitted source files.
if err := git("clean", "-i", "-d"); err != nil {
return fmt.Errorf("failed to cleanup git repository: %v", err)
}
// Wipe away probably boring ignored files without bothering the user.
if err := git("clean", "-q", "-f", "-d", "-X"); err != nil {
return fmt.Errorf("failed to cleanup git repository: %v", err)
}
cmd := exec.Command(filepath.Join(root, "src", makeScript()))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = filepath.Join(root, "src")
if runtime.GOOS == "windows" {
// Workaround make.bat not autodetecting GOROOT_BOOTSTRAP. Issue 28641.
goroot, err := exec.Command("go", "env", "GOROOT").Output()
if err != nil {
return fmt.Errorf("failed to detect an existing go installation for bootstrap: %v", err)
}
cmd.Env = append(os.Environ(), "GOROOT_BOOTSTRAP="+strings.TrimSpace(string(goroot)))
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to build go: %v", err)
}
return nil
}
func makeScript() string {
switch runtime.GOOS {
case "plan9":
return "make.rc"
case "windows":
return "make.bat"
default:
return "make.bash"
}
}
var signalsToIgnore = []os.Signal{os.Interrupt, syscall.SIGQUIT}
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)
}
runGo(root)
}
func runGo(root string) {
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
newPath := filepath.Join(root, "bin")
if p := os.Getenv("PATH"); p != "" {
newPath += string(filepath.ListSeparator) + p
}
cmd.Env = dedupEnv(caseInsensitiveEnv, append(os.Environ(), "GOROOT="+root, "PATH="+newPath))
handleSignals()
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
}
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 %5.1f%% (%*d / %d bytes)%s\n",
(100.0*float64(p.n))/float64(p.total),
ndigits(p.total), p.n, p.total, end)
}
func ndigits(i int64) int {
var n int
for ; i != 0; i /= 10 {
n++
}
return n
}
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.
func versionArchiveURL(version string) string {
goos := getOS()
ext := ".tar.gz"
if goos == "windows" {
ext = ".zip"
}
arch := runtime.GOARCH
if goos == "linux" && runtime.GOARCH == "arm" {
arch = "armv6l"
}
return "https://dl.google.com/go/" + 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) {
// This could be replaced with os.UserHomeDir, but it was introduced too
// recently, and we want this to work with go as packaged by Linux
// distributions. Note that user.Current is not enough as it does not
// prioritize $HOME. See also Issue 26463.
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)
}
// dedupEnv returns a copy of env with any duplicates removed, in favor of
// later values.
// Items are expected to be on the normal environment "key=value" form.
// If caseInsensitive is true, the case of keys is ignored.
//
// This function 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.
//
// This is copied verbatim from golang.org/x/build/envutil.Dedup at CL 10301
// (commit a91ae26).
func dedupEnv(caseInsensitive bool, env []string) []string {
out := make([]string, 0, len(env))
saw := map[string]int{} // to index in the array
for _, kv := range env {
eq := strings.Index(kv, "=")
if eq < 1 {
out = append(out, kv)
continue
}
k := kv[:eq]
if caseInsensitive {
k = strings.ToLower(k)
}
if dupIdx, isDup := saw[k]; isDup {
out[dupIdx] = kv
} else {
saw[k] = len(out)
out = append(out, kv)
}
}
return out
}
func handleSignals() {
// Ensure that signals intended for the child process are not handled by
// this process' runtime (e.g. SIGQUIT). See issue #36976.
signal.Notify(make(chan os.Signal), signalsToIgnore...)
}