blob: eaf20a0dc5f3d011aa138a087014b7d2413d4d57 [file] [log] [blame]
// Copyright 2021 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.
// Binary installtools is a helper that installs Go tools extension tests depend on.
package main
import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
)
// finalVersion encodes the fact that the specified tool version
// is the known last version that can be buildable with goMinorVersion.
type finalVersion struct {
goMinorVersion int
version string
}
var tools = []struct {
path string
dest string
// versions is a list of supportedVersions sorted by
// goMinorVersion. If we want to pin a tool's version
// add a fake entry with a large goMinorVersion
// value and the pinned tool version as the last entry.
// Nil of empty list indicates we can use the `latest` version.
versions []finalVersion
}{
// TODO: auto-generate based on allTools.ts.in.
{"golang.org/x/tools/gopls", "", nil},
{"github.com/acroca/go-symbols", "", nil},
{"github.com/cweill/gotests/gotests", "", nil},
{"github.com/davidrjenni/reftools/cmd/fillstruct", "", nil},
{"github.com/haya14busa/goplay/cmd/goplay", "", nil},
{"github.com/stamblerre/gocode", "gocode-gomod", nil},
{"github.com/mdempsky/gocode", "", nil},
{"github.com/ramya-rao-a/go-outline", "", nil},
{"github.com/rogpeppe/godef", "", nil},
{"github.com/sqs/goreturns", "", nil},
{"github.com/uudashr/gopkgs/v2/cmd/gopkgs", "", nil},
{"github.com/zmb3/gogetdoc", "", nil},
{"honnef.co/go/tools/cmd/staticcheck", "", []finalVersion{{16, "v0.2.2"}}},
{"golang.org/x/tools/cmd/gorename", "", nil},
{"github.com/go-delve/delve/cmd/dlv", "", nil},
}
// pickVersion returns the version to install based on the supported
// version list.
func pickVersion(goMinorVersion int, versions []finalVersion) string {
for _, v := range versions {
if goMinorVersion <= v.goMinorVersion {
return v.version
}
}
return "latest"
}
func main() {
ver, err := goVersion()
if err != nil {
exitf("failed to find go version: %v", err)
}
if ver < 1 {
exitf("unsupported go version: 1.%v", ver)
}
bin, err := goBin()
if err != nil {
exitf("failed to determine go tool installation directory: %v", err)
}
err = installTools(bin, ver)
if err != nil {
exitf("failed to install tools: %v", err)
}
}
func exitf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args...)
os.Exit(1)
}
// goVersion returns an integer N if go's version is 1.N.
func goVersion() (int, error) {
cmd := exec.Command("go", "list", "-e", "-f", `{{context.ReleaseTags}}`, "--", "unsafe")
cmd.Env = append(os.Environ(), "GO111MODULE=off")
out, err := cmd.Output()
if err != nil {
return 0, fmt.Errorf("go list error: %v", err)
}
result := string(out)
if len(result) < 3 {
return 0, fmt.Errorf("bad ReleaseTagsOutput: %q", result)
}
// Split up "[go1.1 go1.15]"
tags := strings.Fields(result[1 : len(result)-2])
for i := len(tags) - 1; i >= 0; i-- {
var version int
if _, err := fmt.Sscanf(tags[i], "go1.%d", &version); err != nil {
continue
}
return version, nil
}
return 0, fmt.Errorf("no parseable ReleaseTags in %v", tags)
}
// goBin returns the directory where the go command will install binaries.
func goBin() (string, error) {
if gobin := os.Getenv("GOBIN"); gobin != "" {
return gobin, nil
}
out, err := exec.Command("go", "env", "GOPATH").Output()
if err != nil {
return "", err
}
gopaths := filepath.SplitList(strings.TrimSpace(string(out)))
if len(gopaths) == 0 {
return "", fmt.Errorf("invalid GOPATH: %s", out)
}
return filepath.Join(gopaths[0], "bin"), nil
}
func installTools(binDir string, goMinorVersion int) error {
installCmd := "install"
if goMinorVersion < 16 {
installCmd = "get"
}
dir := ""
if installCmd == "get" { // run `go get` command from an empty directory.
dir = os.TempDir()
}
env := append(os.Environ(), "GO111MODULE=on")
for _, tool := range tools {
ver := pickVersion(goMinorVersion, tool.versions)
path := tool.path + "@" + ver
cmd := exec.Command("go", installCmd, path)
cmd.Env = env
cmd.Dir = dir
fmt.Println("go", installCmd, path)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("installing %v: %s\n%v", path, out, err)
}
loc := filepath.Join(binDir, binName(tool.path))
if tool.dest != "" {
newLoc := filepath.Join(binDir, binName(tool.dest))
if err := os.Rename(loc, newLoc); err != nil {
return fmt.Errorf("copying %v to %v: %v", loc, newLoc, err)
}
loc = newLoc
}
fmt.Println("\tinstalled", loc)
}
return nil
}
func binName(toolPath string) string {
b := path.Base(toolPath)
if runtime.GOOS == "windows" {
return b + ".exe"
}
return b
}