| // 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...) |
| } |