Skip to content

Instantly share code, notes, and snippets.

@alexjoedt
Last active April 22, 2023 11:20
Show Gist options
  • Save alexjoedt/9c61f9cc4ce211430257b2febd68be9f to your computer and use it in GitHub Desktop.
Save alexjoedt/9c61f9cc4ce211430257b2febd68be9f to your computer and use it in GitHub Desktop.
#!/bin/bash
# shellcheck disable=SC2016
function install_go_tools {
if go version >/dev/null 2>&1 && curl -fLo "/tmp/go-tools.go" https://gist.githubusercontent.com/alexjoedt/9c61f9cc4ce211430257b2febd68be9f/raw/f45048da7b29d9037579bbf726db1ff39cfaeef3/tools.go; then
go run "/tmp/go-tools.go"
fi
}
function install_go {
if [[ "$1" == "--only-tools" ]]; then
echo "Installing only Go-Tools..."
install_go_tools
exit 0
fi
[ -z "$GOROOT" ] && GOROOT="/usr/local/go"
[ -z "$GOPATH" ] && GOPATH="$HOME/go"
force="false"
if [ "$1" == "-f" ]; then
force="true"
fi
OS="$(uname -s)"
ARCH="$(uname -m)"
case $OS in
"Linux")
if ! sudo apt-get -qq install jq -y >/dev/null 2>&1; then
echo "failed to install jq. Abort."
exit 1
fi
case $ARCH in
"x86_64")
ARCH=amd64
;;
"aarch64")
ARCH=arm64
;;
"armv6" | "armv7l")
ARCH=armv6l
;;
"armv8")
ARCH=arm64
;;
.*386.*)
ARCH=386
;;
esac
PLATFORM="linux-$ARCH"
;;
"Darwin")
if ! command -v brew >/dev/null 2>&1; then
echo "please install brew and re-run the script"
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
brew install jq >/dev/null 2>&1
fi
case $ARCH in
"x86_64")
ARCH=amd64
;;
"arm64")
ARCH=arm64
;;
esac
PLATFORM="darwin-$ARCH"
;;
esac
if [ -z "$PLATFORM" ]; then
echo "Your operating system is not supported by this script."
exit 1
fi
GOLANG_INSTALLED=""
if go version >/dev/null 2>&1; then
GOLANG_INSTALLED=$(go version | {
read -r _ _ v _
echo "${v#go}"
} | cut -d. -f1,2,3)
fi
echo "Fetch latest Go version...."
GOLANG_LATEST=$(curl -s "https://go.dev/dl/?mode=json" | jq -r '.[0].version')
GO_PACKAGE="${GOLANG_LATEST}.${PLATFORM}.tar.gz"
if [ $force = "false" ] && [ "$GOLANG_INSTALLED" == "$(echo "${GOLANG_LATEST#go}" | cut -d. -f1,2,3)" ]; then
echo "Latest Go version ($GOLANG_INSTALLED) already installed...."
exit
fi
TEMP_DIRECTORY=$(mktemp -d)
echo "Downloading $GO_PACKAGE ..."
if wget "https://storage.googleapis.com/golang/$GO_PACKAGE" -O "$TEMP_DIRECTORY/$GO_PACKAGE"; then
echo "download complete"
elif curl -o "$TEMP_DIRECTORY/$GO_PACKAGE" "https://storage.googleapis.com/golang/$GO_PACKAGE"; then
echo "Download complete"
else
echo "Cant download Go package. Aborting."
echo "Please install 'wget' or 'curl' and try again."
exit 1
fi
if [ $? -ne 0 ]; then
echo "Download failed! Exiting."
exit 1
fi
echo "Create Go root directory: $GOROOT"
test -d "$GOROOT" && sudo rm -rf "$GOROOT"
test -d "$GOROOT" || sudo mkdir -p "$GOROOT"
echo "Extracting Go to $GOROOT"
if ! sudo tar -C "$(dirname $GOROOT)" -xzf "$TEMP_DIRECTORY/$GO_PACKAGE"; then
echo "Failed to extract go to $GOROOT"
exit 1
fi
rm -rf "$TEMP_DIRECTORY"
if [ -n "$($SHELL -c 'echo $ZSH_VERSION')" ]; then
shell_rc="$HOME/.zshrc"
elif [ -n "$($SHELL -c 'echo $BASH_VERSION')" ]; then
shell_rc="$HOME/.bashrc"
else
echo "Your shell is not supported within this script."
echo "Make sure to export $GOROOT in your PATH."
exit
fi
test -f "$shell_rc" || touch "$shell_rc"
if ! grep -q "/usr/local/go/bin" "$shell_rc"; then
{
echo '# Golang'
echo 'if [ -d /usr/local/go/bin ]; then'
echo -e '\texport PATH=$PATH:/usr/local/go/bin'
echo -e '\texport PATH=$PATH:$(go env GOPATH)/bin'
echo 'fi'
echo ''
} >>"$shell_rc"
echo "Export Go root in $shell_rc"
source "$shell_rc"
else
echo "Go path alredy exported in $shell_rc"
fi
install_go_tools
}
install_go $1
// 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 (
"bytes"
"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
preferPreview bool
// 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", "", true, nil},
{"github.com/acroca/go-symbols", "", false, nil},
{"github.com/cweill/gotests/gotests", "", false, nil},
{"github.com/davidrjenni/reftools/cmd/fillstruct", "", false, nil},
{"github.com/haya14busa/goplay/cmd/goplay", "", false, nil},
{"github.com/stamblerre/gocode", "gocode-gomod", false, nil},
{"github.com/mdempsky/gocode", "", false, nil},
{"github.com/ramya-rao-a/go-outline", "", false, nil},
{"github.com/rogpeppe/godef", "", false, nil},
{"github.com/sqs/goreturns", "", false, nil},
{"github.com/uudashr/gopkgs/v2/cmd/gopkgs", "", false, nil},
{"github.com/zmb3/gogetdoc", "", false, nil},
{"honnef.co/go/tools/cmd/staticcheck", "", false, []finalVersion{{16, "v0.2.2"}, {18, "v0.3.3"}}},
{"golang.org/x/tools/cmd/gorename", "", false, nil},
{"github.com/go-delve/delve/cmd/dlv", "", false, []finalVersion{{16, "v1.8.3"}, {17, "v1.9.1"}}},
}
// pickVersion returns the version to install based on the supported
// version list.
func pickVersion(goMinorVersion int, versions []finalVersion, defaultVersion string) string {
for _, v := range versions {
if goMinorVersion <= v.goMinorVersion {
return v.version
}
}
return defaultVersion
}
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, pickLatest(tool.path, tool.preferPreview))
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
}
func pickLatest(toolPath string, preferPreview bool) string {
if !preferPreview {
return "latest" // should we pick the pinned version in allTools.ts.in?
}
out, err := exec.Command("go", "list", "-m", "--versions", toolPath).Output()
if err != nil {
exitf("failed to find a suitable version for %q: %v", toolPath, err)
}
versions := bytes.Split(out, []byte(" "))
if len(versions) == 0 {
exitf("failed to find a suitable version for %q: %s", toolPath, out)
}
return string(bytes.TrimSpace(versions[len(versions)-1]))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment