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
		}
		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
}
unpackZip is the zip implementation of unpackArchive.
package commands
import (
	"archive/tar"
	"archive/zip"
	"compress/gzip"
	"crypto/sha256"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"
	"github.com/manifoldco/promptui"
	"github.com/spf13/cobra"
)
const (
	goHost                = "golang.org"
	goDownloadBaseURL     = "https://dl.google.com/go"
	goSourceGitURL        = "https://github.com/golang/go"
	goSourceUpsteamGitURL = "https://go.googlesource.com/go"
)
var (
	installCmdGoHostFlag string
)
func installCmd() *cobra.Command {
	installCmd := &cobra.Command{
		Use:   "install [VERSION]",
		Short: `Install Go with a version`,
		Long: `Install Go by providing a version. If no version is provided, install
the latest Go. If the version is 'tip', an optional change list (CL)
number can be provided.`,
		Example: `
  goup install
  goup install 1.15.2
  goup install go1.15.2
  goup install tip # Compile Go tip
  goup install tip 1234 # 1234 is the CL number
`,
		PersistentPreRunE: preRunInstall,
		RunE:              runInstall,
	}
	gh := os.Getenv("GOUP_GO_HOST")
	if gh == "" {
		gh = goHost
	}
	installCmd.PersistentFlags().StringVar(&installCmdGoHostFlag, "host", gh, "host that is used to download Go. The GOUP_GO_HOST environment variable overrides this flag.")
	return installCmd
}
func preRunInstall(cmd *cobra.Command, args []string) error {
	http.DefaultTransport = &userAgentTransport{http.DefaultTransport}
	return nil
}
func runInstall(cmd *cobra.Command, args []string) error {
	var (
		ver string
		err error
	)
	if len(args) == 0 {
		ver, err = latestGoVersion()
		if err != nil {
			return err
		}
	} else {
		ver = args[0]
	}
	if !strings.HasPrefix(ver, "go") {
		ver = "go" + ver
	}
	if ver == "gotip" {
		var cl string
		if len(args) > 1 {
			cl = args[1]
		}
		err = installTip(cl)
	} else {
		err = install(ver)
	}
	if err != nil {
		return err
	}
	if err := switchVer(ver); err != nil {
		return err
	}
	return nil
}
func latestGoVersion() (string, error) {
	h, err := url.Parse(installCmdGoHostFlag)
	if err != nil {
		return "", err
	}
	if h.Scheme == "" {
		h.Scheme = "https"
	}
	resp, err := http.Get(fmt.Sprintf("%s/VERSION?m=text", h.String()))
	if err != nil {
		return "", fmt.Errorf("Getting current Go version failed: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode > 299 {
		b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 1024))
		return "", fmt.Errorf("Could not get current Go version: HTTP %d: %q", resp.StatusCode, b)
	}
	version, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(version)), nil
}
func switchVer(ver string) error {
	if !strings.HasPrefix(ver, "go") {
		ver = "go" + ver
	}
	err := symlink(ver)
	if err == nil {
		logger.Printf("Default Go is set to '%s'", ver)
	}
	return err
}
func symlink(ver string) error {
	current := GoupCurrentDir()
	version := goupVersionDir(ver)
	if _, err := os.Stat(version); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Go version %s is not installed. Install it with `goup install`.", ver)
		} else {
			return err
		}
	}
	os.Remove(current)
	return os.Symlink(version, current)
}
func install(version string) error {
	targetDir := goupVersionDir(version)
	if _, err := os.Stat(filepath.Join(targetDir, unpackedOkay)); err == nil {
		logger.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) {
			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)
	}
	logger.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
	}
	logger.Printf("Success: %s downloaded in %v", version, targetDir)
	return nil
}
func installTip(clNumber string) error {
	root := goupVersionDir("gotip")
	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", goSourceGitURL, root); err != nil {
			return fmt.Errorf("failed to clone git repository: %v", err)
		}
		if err := git("remote", "add", "upstream", goSourceUpsteamGitURL); err != nil {
			return fmt.Errorf("failed to add upstream git repository: %v", err)
		}
	}
	if clNumber != "" {
		prompt := promptui.Prompt{
			Label:     fmt.Sprintf("This will download and execute code from golang.org/cl/%s, continue", clNumber),
			IsConfirm: true,
		}
		if _, err := prompt.Run(); err != nil {
			return fmt.Errorf("interrupted")
		}
		refs, err := gitOutput("ls-remote", "upstream")
		if err != nil {
			return fmt.Errorf("failed to list remotes: %v", err)
		}
		r := regexp.MustCompile(`refs/changes/\d\d/` + clNumber + `/(\d+)`)
		match := r.FindAllStringSubmatch(string(refs), -1)
		if match == nil {
			return fmt.Errorf("CL %v not found", clNumber)
		}
		var ref string
		var patchSet int
		for _, m := range match {
			ps, _ := strconv.Atoi(m[1])
			if ps > patchSet {
				patchSet = ps
				ref = m[0]
			}
		}
		logger.Printf("Fetching CL %v, Patch Set %v...", clNumber, patchSet)
		if err := git("fetch", "upstream", ref); err != nil {
			return fmt.Errorf("failed to fetch %s: %v", ref, err)
		}
	} else {
		logger.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)
		}
	}
	if err := git("-c", "advice.detachedHead=false", "checkout", "FETCH_HEAD"); err != nil {
		return fmt.Errorf("failed to checkout git repository: %v", err)
	}
	if err := git("clean", "-i", "-d"); err != nil {
		return fmt.Errorf("failed to cleanup git repository: %v", err)
	}
	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" {
		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 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")
	}
}
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():
			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 {
					logger.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
}