#!/usr/bin/env bash # vim:noexpandtab:ts=2:sw=2: # #+ Usage: $(basename $0) [flags] [go-version] [version-prefix] #+ - #+ Version: ${GIMME_VERSION} #+ Copyright: ${GIMME_COPYRIGHT} #+ License URL: ${GIMME_LICENSE_URL} #+ - #+ Install go! There are multiple types of installations available, with 'auto' being the default. #+ If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing #+ go installation. This behavior may be disabled by providing '-f/--force/force' as first positional #+ argument. #+ - #+ Option flags: #+ -h --help help - show this help text and exit #+ -V --version version - show the version only and exit #+ -f --force force - remove the existing go installation if present prior to install #+ -l --list list - list installed go versions and exit #+ -k --known known - list known go versions and exit #+ --force-known-update - when used with --known, ignores the cache and updates #+ -r --resolve resolve - resolve a version specifier to a version, show that and exit #+ - #+ Influential env vars: #+ - #+ GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg) #+ GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}', #+ may be given as second positional arg) #+ GIMME_ARCH - arch to install (default '${GIMME_ARCH}') #+ GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}') #+ GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}') #+ GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}') #+ GIMME_OS - os to install (default '${GIMME_OS}') #+ GIMME_TMP - temp directory (default '${GIMME_TMP}') #+ GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git') #+ (default '${GIMME_TYPE}') #+ GIMME_INSTALL_RACE - install race directory after compile if non-empty. #+ If the install type is 'binary', this option is ignored. #+ GIMME_DEBUG - enable tracing if non-empty #+ GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host #+ GIMME_SILENT_ENV - omit the 'go version' line from env file #+ GIMME_CGO_ENABLED - enable build of cgo support #+ GIMME_CC_FOR_TARGET - cross compiler for cgo support #+ GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}') #+ GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}') #+ GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}') #+ - # set -e shopt -s nullglob shopt -s dotglob shopt -s extglob set -o pipefail [[ ${GIMME_DEBUG} ]] && set -x readonly GIMME_VERSION="v1.5.4" readonly GIMME_COPYRIGHT="Copyright (c) 2015-2020 gimme contributors" readonly GIMME_LICENSE_URL="https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE" export GIMME_VERSION export GIMME_COPYRIGHT export GIMME_LICENSE_URL program_name="$(basename "$0")" # shellcheck disable=SC1117 warn() { printf >&2 "%s: %s\n" "${program_name}" "${*}"; } die() { warn "$@" exit 1 } # We don't want to go around hitting Google's servers with requests for # files named HEAD@{date}.tar so we only try binary/source downloads if # it looks like a plausible name to us. # We don't need to support 0. releases of Go. # We don't support 5 digit major-versions of Go (limit back-tracking in RE). # We don't support very long versions # (both to avoid annoying download server operators with attacks and # because regexp backtracking can be pathological). # Per _assert_version_given we do assume 2.0 not 2 ALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\.[0-9][0-9a-zA-Z_-]{0,9})+$' # # The main path which allowed these to leak upstream before has been closed # but a valid git repo tag or branch-name will still reach the point of # being _tried_ upstream. # _do_curl "url" "file" _do_curl() { mkdir -p "$(dirname "${2}")" if command -v curl >/dev/null; then curl -sSLf "${1}" -o "${2}" 2>/dev/null return fi if command -v wget >/dev/null; then wget -q "${1}" -O "${2}" 2>/dev/null return fi if command -v fetch >/dev/null; then fetch -q "${1}" -o "${2}" 2>/dev/null return fi echo >&2 'error: no curl, wget, or fetch found' exit 1 } # _sha256sum "file" _sha256sum() { if command -v sha256sum &>/dev/null; then sha256sum "$@" elif command -v gsha256sum &>/dev/null; then gsha256sum "$@" else shasum -a 256 "$@" fi } # sort versions, handling 1.10 after 1.9, not before 1.2 # FreeBSD sort has --version-sort, none of the others do # Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty if sort --version-sort /dev/null; then _version_sort() { sort --version-sort; } else _version_sort() { # If we go to four-digit minor or patch versions, then extend the padding here # (but in such a world, perhaps --version-sort will have become standard by then?) sed -E 's/\.([0-9](\.|$))/.00\1/g; s/\.([0-9][0-9](\.|$))/.0\1/g' | sort --general-numeric-sort | sed 's/\.00*/./g' } fi # _do_curls "file" "url" ["url"...] _do_curls() { f="${1}" shift if _sha256sum -c "${f}.sha256" &>/dev/null; then return 0 fi for url in "${@}"; do if _do_curl "${url}" "${f}"; then if _do_curl "${url}.sha256" "${f}.sha256"; then echo "$(cat "${f}.sha256") ${f}" >"${f}.sha256.tmp" mv "${f}.sha256.tmp" "${f}.sha256" if ! _sha256sum -c "${f}.sha256" &>/dev/null; then warn "sha256sum failed for '${f}'" warn 'continuing to next candidate URL' continue fi fi return fi done rm -f "${f}" return 1 } # _binary "version" "file.tar.gz" "arch" _binary() { local version=${1} local file=${2} local arch=${3} urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz" ) if [[ "${GIMME_OS}" == 'darwin' && "${GIMME_BINARY_OSX}" ]]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz" "${urls[@]}" ) fi if [ "${arch}" = 'arm' ]; then # attempt "armv6l" vs just "arm" first (since that's what's officially published) urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz" # go1.6beta2 & go1.6rc1 "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz" # go1.6beta1 "${urls[@]}" ) fi if [ "${GIMME_OS}" = 'windows' ]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip" ) fi _do_curls "${file}" "${urls[@]}" } # _source "version" "file.src.tar.gz" _source() { urls=( "${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz" "https://github.com/golang/go/archive/go${1}.tar.gz" ) _do_curls "${2}" "${urls[@]}" } # _fetch "dir" _fetch() { mkdir -p "$(dirname "${1}")" if [[ -d "${1}/.git" ]]; then ( cd "${1}" git remote set-url origin "${GIMME_GO_GIT_REMOTE}" git fetch -q --all && git fetch -q --tags ) return fi git clone -q "${GIMME_GO_GIT_REMOTE}" "${1}" } # _checkout "version" "dir" # NB: might emit a "renamed version" on stdout _checkout() { local spec="${1:?}" godir="${2:?}" # We are called twice, once during validation that a version was given and # later during build. We don't want to fetch twice, so we are fetching # during the validation only, in the caller. if [[ "${spec}" =~ ^[0-9a-f]{6,}$ ]]; then # We always treat this as a commit sha, whether instead of doing # branch tests etc. It looks like a commit sha and the Go maintainers # aren't daft enough to use pure hex for a tag or branch. git -C "$godir" reset -q --hard "${spec}" || return 1 return 0 fi # If spec looks like HEAD^{something} or HEAD^^^ then trying # origin/$spec would succeed but we'd write junk to the filesystem, # propagating annoying characters out. local retval probe_named disallow rev probe_named=1 disallow='[@^~:{}]' if [[ "${spec}" =~ $disallow ]]; then probe_named=0 [[ "${spec}" != "@" ]] || spec="HEAD" fi try_spec() { git -C "${godir}" reset -q --hard "$@" -- 2>/dev/null; } retval=1 if ((probe_named)); then retval=0 try_spec "origin/${spec}" || try_spec "origin/go${spec}" || { [[ "${spec}" == "tip" ]] && try_spec origin/master; } || try_spec "refs/tags/${spec}" || try_spec "refs/tags/go${spec}" || retval=1 fi if ((retval)); then retval=0 # We're about to reset anyway, if we succeed, so we should reset to a # known state before parsing what might be relative specs try_spec origin/master && rev="$(git -C "${godir}" rev-parse --verify -q "${spec}^{object}")" && try_spec "${rev}" && git -C "${godir}" rev-parse --verify -q --short=12 "${rev}" || retval=1 # that rev-parse prints to stdout, so we can affect the version seen fi unset -f try_spec return $retval } # _extract "file.tar.gz" "dir" _extract() { mkdir -p "${2}" if [[ "${1}" == *.tar.gz ]]; then tar -xf "${1}" -C "${2}" --strip-components 1 else unzip -q "${1}" -d "${2}" mv "${2}"/go/* "${2}" rmdir "${2}"/go fi } # _setup_bootstrap _setup_bootstrap() { local versions=("1.18" "1.17" "1.16" "1.15" "1.14" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8" "1.7" "1.6" "1.5" "1.4") # try existing for v in "${versions[@]}"; do for candidate in "${GIMME_ENV_PREFIX}/go${v}"*".env"; do if [ -s "${candidate}" ]; then # shellcheck source=/dev/null GOROOT_BOOTSTRAP="$(source "${candidate}" 2>/dev/null && go env GOROOT)" export GOROOT_BOOTSTRAP return 0 fi done done # try binary for v in "${versions[@]}"; do if [ -n "$(_try_binary "${v}" "${GIMME_HOSTARCH}")" ]; then export GOROOT_BOOTSTRAP="${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}" return 0 fi done echo >&2 "Unable to setup go bootstrap from existing or binary" return 1 } # _compile "dir" _compile() { ( if grep -q GOROOT_BOOTSTRAP "${1}/src/make.bash" &>/dev/null; then _setup_bootstrap || return 1 fi cd "${1}" if [[ -d .git ]]; then git clean -dfx -q fi cd src export GOOS="${GIMME_OS}" GOARCH="${GIMME_ARCH}" export CGO_ENABLED="${GIMME_CGO_ENABLED}" export CC_FOR_TARGET="${GIMME_CC_FOR_TARGET}" local make_log="${1}/make.${GOOS}.${GOARCH}.log" if [[ "${GIMME_DEBUG}" -ge "2" ]]; then ./make.bash -v 2>&1 | tee "${make_log}" 1>&2 || return 1 else ./make.bash &>"${make_log}" || return 1 fi ) } _try_install_race() { if [[ ! "${GIMME_INSTALL_RACE}" ]]; then return 0 fi "${1}/bin/go" install -race std } _can_compile() { cat >"${GIMME_TMP}/test.go" <<'EOF' package main import "os" func main() { os.Exit(0) } EOF "${1}/bin/go" run "${GIMME_TMP}/test.go" } # _env "dir" _env() { [[ -d "${1}/bin" && -x "${1}/bin/go" ]] || return 1 # if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source # automatically GOROOT="${1}" GOFLAGS="" "${1}/bin/go" version &>/dev/null || return 1 # https://twitter.com/davecheney/status/431581286918934528 # we have to GOROOT sometimes because we use official release binaries in unofficial locations :( # # Issue 87 leads to: # No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it. # The "avoid setting it" is _only_ for people using official releases in official locations. # Tools like `gimme` are the reason that GOROOT-in-env exists. echo if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" ]]; then echo 'unset GOOS;' else echo 'export GOOS="'"${GIMME_OS}"'";' fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then echo 'unset GOARCH;' else echo 'export GOARCH="'"${GIMME_ARCH}"'";' fi echo "export GOROOT='${1}';" # shellcheck disable=SC2016 echo 'export PATH="'"${1}/bin"':${PATH}";' if [[ -z "${GIMME_SILENT_ENV}" ]]; then echo 'go version >&2;' fi echo } # _env_alias "dir" "env-file" _env_alias() { if [[ "${GIMME_NO_ENV_ALIAS}" ]]; then echo "${2}" return fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" && "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then # GIMME_GO_VERSION might be a branch, which can contain '/' local dest="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\//__}.env" cp "${2}" "${dest}" ln -sf "${dest}" "${GIMME_ENV_PREFIX}/latest.env" echo "${dest}" else echo "${2}" fi } _try_existing() { case "${1}" in binary) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env" ;; source) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" ;; *) _try_existing binary || _try_existing source return $? ;; esac if [[ -x "${existing_ver}/bin/go" && -s "${existing_env}" ]]; then # newer envs have existing semi-colon at end of line, because newer gimme # puts them there; envs created before that change lack those semi-colons # and should gain them, to make it easier for people using eval without # double-quoting the command substition. sed -e 's/\([^;]\)$/\1;/' <"${existing_env}" # gimme is the corner-case where GOROOT _should_ be overriden, since if the # ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is # unset, then it will be used and the wrong golang will be picked up. # Lots of old installs won't have GOROOT; munge it from $PATH if grep -qs '^unset GOROOT' -- "${existing_env}"; then sed -n -e 's/^export PATH="\(.*\)\/bin:.*$/export GOROOT='"'"'\1'"'"';/p' <"${existing_env}" echo fi # Export the same variables whether building new or using existing echo "export GIMME_ENV='${existing_env}';" return fi return 1 } # _try_binary "version" "arch" _try_binary() { local version=${1} local arch=${2} local bin_tgz="${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz" local bin_dir="${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}" local bin_env="${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env" [[ "${version}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 if [ "${GIMME_OS}" = 'windows' ]; then bin_tgz=${bin_tgz%.tar.gz}.zip fi _binary "${version}" "${bin_tgz}" "${arch}" || return 1 _extract "${bin_tgz}" "${bin_dir}" || return 1 _env "${bin_dir}" | tee "${bin_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${bin_dir}" "${bin_env}")\"" } _try_source() { local src_tgz="${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz" local src_dir="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local src_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" [[ "${GIMME_GO_VERSION}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 _source "${GIMME_GO_VERSION}" "${src_tgz}" || return 1 _extract "${src_tgz}" "${src_dir}" || return 1 _compile "${src_dir}" || return 1 _try_install_race "${src_dir}" || return 1 _env "${src_dir}" | tee "${src_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${src_dir}" "${src_env}")\"" } # We do _not_ try to use any version caching with _try_existing(), but instead # build afresh each time. We don't want to deal with someone moving the repo # to other-version, doing an install, then resetting it back to # last-version-we-saw and thus introducing conflicts. # # If you want to re-use a built-at-spec version, then avoid moving the repo # and source the generated .env manually. # Note that the env will just refer to the 'go' directory, so it's not safe # to reuse anyway. _try_git() { local git_dir="${GIMME_VERSION_PREFIX}/go" local git_env="${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env" local resolved_sha # Any tags should have been resolved when we asserted that we were # given a version, so no need to handle that here. _checkout "${GIMME_GO_VERSION}" "${git_dir}" >/dev/null || return 1 _compile "${git_dir}" || return 1 _try_install_race "${git_dir}" || return 1 _env "${git_dir}" | tee "${git_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${git_dir}" "${git_env}")\"" } _wipe_version() { local env_file="${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env" if [[ -s "${env_file}" ]]; then rm -rf "$(awk -F\" '/GOROOT/ { print $2 }' "${env_file}")" rm -f "${env_file}" fi } _list_versions() { if [ ! -d "${GIMME_VERSION_PREFIX}" ]; then return 0 fi local current_version current_version="$(go env GOROOT 2>/dev/null)" current_version="${current_version##*/go}" current_version="${current_version%%.${GIMME_OS}.*}" # 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash # doesn't appear to have anything like that. for d in "${GIMME_VERSION_PREFIX}/go"*".${GIMME_OS}."*; do local cleaned="${d##*/go}" cleaned="${cleaned%%.${GIMME_OS}.*}" echo "${cleaned}" done | _version_sort | while read -r cleaned; do echo -en "${cleaned}" if [[ "${cleaned}" == "${current_version}" ]]; then echo -en ' <= current' >&2 fi echo done } _update_remote_known_list_if_needed() { # shellcheck disable=SC1117 local exp="go([[:alnum:]\.]*)\.src.*" # :alnum: catches beta versions too local list="${GIMME_VERSION_PREFIX}/known-versions.txt" local dlfile="${GIMME_TMP}/known-dl" if [[ -e "${list}" ]] && ! ((force_known_update)) && ! _file_older_than_secs "${list}" "${GIMME_KNOWN_CACHE_MAX}"; then echo "${list}" return 0 fi [[ -d "${GIMME_VERSION_PREFIX:?}" ]] || mkdir -p -- "${GIMME_VERSION_PREFIX}" _do_curl "${GIMME_LIST_KNOWN}" "${dlfile}" while read -r line; do if [[ "${line}" =~ ${exp} ]]; then echo "${BASH_REMATCH[1]}" fi done <"${dlfile}" | _version_sort | uniq >"${list}.new" rm -f "${list}" &>/dev/null mv "${list}.new" "${list}" rm -f "${dlfile}" echo "${list}" return 0 } _list_known() { local knownfile knownfile="$(_update_remote_known_list_if_needed)" ( _list_versions 2>/dev/null cat -- "${knownfile}" ) | grep . | _version_sort | uniq } # For the "invoked on commandline" case, we want to always pass unknown # strings through, so that we can be a uniqueness filter, but for unknown # names we want to exit with a value other than 1, so we document that # we'll exit 2. For use by other functions, 2 is as good as 1. _resolve_version() { case "${1}" in stable) _get_curr_stable return 0 ;; oldstable) _get_old_stable return 0 ;; tip) echo "tip" return 0 ;; *.x) true ;; *) echo "${1}" local GIMME_GO_VERSION="$1" local ASSERT_ABORT='return' if _assert_version_given 2>/dev/null; then return 0 fi warn "version specifier '${1}' unknown" return 2 ;; esac # We have a .x suffix local base="${1%.x}" local ver last='' known known="$(_update_remote_known_list_if_needed)" # will be version-sorted if [[ ! "${base}" =~ ^[0-9.]+$ ]]; then warn "resolve pattern '${base}.x' invalid for .x finding" return 2 fi # The `.x` is optional; "1.10" matches "1.10.x" local search="^${base//./\\.}(\\.[0-9.]+)?\$" # avoid regexp attacks while read -r ver; do [[ "${ver}" =~ $search ]] || continue last="${ver}" done <"$known" if [[ -n "${last}" ]]; then echo "${last}" return 0 fi echo "${1}" warn "given '${1}' but no release for '${base}' found" return 2 } _realpath() { # shellcheck disable=SC2005 [ -d "$1" ] && echo "$(cd "$1" && pwd)" || echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" } _get_curr_stable() { local stable="${GIMME_VERSION_PREFIX}/stable" if _file_older_than_secs "${stable}" 86400; then _update_stable "${stable}" fi cat "${stable}" } _get_old_stable() { local oldstable="${GIMME_VERSION_PREFIX}/oldstable" if _file_older_than_secs "${oldstable}" 86400; then _update_oldstable "${oldstable}" fi cat "${oldstable}" } _update_stable() { local stable="${1}" local url="https://golang.org/VERSION?m=text" _do_curl "${url}" "${stable}" sed -i.old -e 's/^go\(.*\)/\1/' "${stable}" rm -f "${stable}.old" } _update_oldstable() { local oldstable="${1}" local oldstable_x oldstable_x=$(_get_curr_stable | awk -F. '{ $2--; print $1 "." $2 "." "x" }') _resolve_version "${oldstable_x}" >"${oldstable}" } _last_mod_timestamp() { local filename="${1}" case "${GIMME_HOSTOS}" in darwin | *bsd) stat -f %m "${filename}" ;; linux) stat -c %Y "${filename}" ;; esac } _file_older_than_secs() { local file="${1}" local age_secs="${2}" local ts # if the file does not exist, we return true, as the cache needs updating ts="$(_last_mod_timestamp "${file}" 2>/dev/null)" || return 0 ((($(date +%s) - ts) > age_secs)) } _assert_version_given() { # By the time we're called, aliases such as "stable" must have been resolved # but we could be a reference in git. # # Versions can include suffices such as in "1.8beta2", so our assumption is that # there will always be a minor present; the first public release was "1.0" so # we assume "2.0" not "2". if [[ -z "${GIMME_GO_VERSION}" ]]; then echo >&2 'error: no GIMME_GO_VERSION supplied' echo >&2 " ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}" echo >&2 " ex: ${0} 1.4.1 ${*}" ${ASSERT_ABORT:-exit} 1 fi # Note: _resolve_version calls back to us (_assert_version_given), but # only for cases where the version does not end with .x, so this should # be safe. # This should be untangled. PRs accepted, good starter project. if [[ "${GIMME_GO_VERSION}" == *.x ]]; then GIMME_GO_VERSION="$(_resolve_version "${GIMME_GO_VERSION}")" || ${ASSERT_ABORT:-exit} 1 fi if [[ "${GIMME_GO_VERSION}" == +([[:digit:]]).+([[:digit:]])* ]]; then return 0 fi # Here we resolve symbolic references. If we don't, then we get some # random git tag name being accepted as valid and then we try to # curl garbage from upstream. if [[ "${GIMME_TYPE}" == "auto" || "${GIMME_TYPE}" == "git" ]]; then local git_dir="${GIMME_VERSION_PREFIX}/go" local resolved_sha _fetch "${git_dir}" if resolved_sha="$(_checkout "${GIMME_GO_VERSION}" "${git_dir}")"; then if [[ -n "${resolved_sha}" ]]; then # Break our normal silence, this one really needs to be seen on stderr # always; auditability and knowing what version of Go you got wins. warn "resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'" GIMME_GO_VERSION="${resolved_sha}" fi return 0 fi fi echo >&2 'error: GIMME_GO_VERSION not recognized as valid' echo >&2 " got: ${GIMME_GO_VERSION}" ${ASSERT_ABORT:-exit} 1 } _exclude_from_backups() { # Please avoid anything which requires elevated privileges or is obnoxious # enough to offend the invoker case "${GIMME_HOSTOS}" in darwin) # Darwin: Time Machine is "standard", we can add others. The default # mechanism is sticky, as an attribute on the dir, requires no # privileges, is idempotent (and doesn't support -- to end flags). tmutil addexclusion "$@" ;; esac } _versint() { IFS=" " read -r -a args <<<"${1//[^0-9]/ }" printf '1%03d%03d%03d%03d' "${args[@]}" } _to_goarch() { case "${1}" in aarch64) echo "arm64" ;; *) echo "${1}" ;; esac } : "${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_ARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_HOSTARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}" : "${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}" : "${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}" : "${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}" : "${GIMME_TYPE:=auto}" # 'auto', 'binary', 'source', or 'git' : "${GIMME_BINARY_OSX:=osx10.8}" : "${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}" : "${GIMME_LIST_KNOWN:=https://golang.org/dl}" : "${GIMME_KNOWN_CACHE_MAX:=10800}" # The version prefix must be an absolute path case "${GIMME_VERSION_PREFIX}" in /*) true ;; *) echo >&2 " Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX" GIMME_VERSION_PREFIX="$(pwd)/${GIMME_VERSION_PREFIX}" echo >&2 " to: $GIMME_VERSION_PREFIX" ;; esac case "${GIMME_OS}" in mingw* | msys_nt*) # Minimalist GNU for Windows GIMME_OS='windows' if [ "${GIMME_ARCH}" = 'i686' ]; then GIMME_ARCH="386" else GIMME_ARCH="amd64" fi ;; esac force_install=0 force_known_update=0 while [[ $# -gt 0 ]]; do case "${1}" in -h | --help | help | wat) _old_ifs="$IFS" IFS=';' awk '/^#\+ / { sub(/^#\+ /, "", $0) ; sub(/-$/, "", $0) ; print $0 }' "$0" | while read -r line; do eval "echo \"$line\"" done IFS="$_old_ifs" exit 0 ;; -V | --version | version) echo "${GIMME_VERSION}" exit 0 ;; -r | --resolve | resolve) # The normal mkdir of versions is below; we don't want to move it up # to where we create files just if asked our version; thus # _resolve_version has to mkdir the versions dir itself. if [[ $# -ge 2 ]]; then _resolve_version "${2}" elif [[ -n "${GIMME_GO_VERSION:-}" ]]; then _resolve_version "${GIMME_GO_VERSION}" else die "resolve must be given a version to resolve" fi exit $? ;; -l | --list | list) _list_versions exit 0 ;; -k | --known | known) _list_known exit 0 ;; -f | --force | force) force_install=1 ;; --force-known-update | force-known-update) force_known_update=1 ;; -i | install) true # ignore a dummy argument ;; *) break ;; esac shift done if [[ -n "${1}" ]]; then GIMME_GO_VERSION="${1}" fi if [[ -n "${2}" ]]; then GIMME_VERSION_PREFIX="${2}" fi case "${GIMME_ARCH}" in x86_64) GIMME_ARCH=amd64 ;; x86) GIMME_ARCH=386 ;; arm64) if [[ "${GIMME_GO_VERSION}" != master && "$(_versint "${GIMME_GO_VERSION}")" < "$(_versint 1.5)" ]]; then echo >&2 "error: ${GIMME_ARCH} is not supported by this go version" echo >&2 "try go1.5 or newer" exit 1 fi if [[ "${GIMME_HOSTOS}" == "linux" && "${GIMME_HOSTARCH}" != "${GIMME_ARCH}" ]]; then : "${GIMME_CC_FOR_TARGET:="aarch64-linux-gnu-gcc"}" fi ;; arm*) GIMME_ARCH=arm ;; esac case "${GIMME_HOSTARCH}" in x86_64) GIMME_HOSTARCH=amd64 ;; x86) GIMME_HOSTARCH=386 ;; arm64) ;; arm*) GIMME_HOSTARCH=arm ;; esac case "${GIMME_GO_VERSION}" in stable) GIMME_GO_VERSION=$(_get_curr_stable) ;; oldstable) GIMME_GO_VERSION=$(_get_old_stable) ;; esac _assert_version_given "$@" ((force_install)) && _wipe_version "${GIMME_GO_VERSION}" unset GOARCH unset GOBIN unset GOOS unset GOPATH unset GOROOT unset CGO_ENABLED unset CC_FOR_TARGET # GO111MODULE breaks build of Go itself unset GO111MODULE mkdir -p "${GIMME_VERSION_PREFIX}" "${GIMME_ENV_PREFIX}" # The envs dir stays small and provides a record of what had been installed # whereas the versions dir grows by hundreds of MB per version and is not # intended to support local modifications (as that subverts the point of gimme) # _and_ is a cache, so we're unilaterally declaring that the contents of # the versions dir should be excluded from system backups. _exclude_from_backups "${GIMME_VERSION_PREFIX}" GIMME_VERSION_PREFIX="$(_realpath "${GIMME_VERSION_PREFIX}")" GIMME_ENV_PREFIX="$(_realpath "${GIMME_ENV_PREFIX}")" if ! case "${GIMME_TYPE}" in binary) _try_existing binary || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" ;; source) _try_existing source || _try_source || _try_git ;; git) _try_git ;; auto) _try_existing || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" || _try_source || _try_git ;; *) echo >&2 "I don't know how to '${GIMME_TYPE}'." echo >&2 " Try 'auto', 'binary', 'source', or 'git'." exit 1 ;; esac; then echo >&2 "I don't have any idea what to do with '${GIMME_GO_VERSION}'." echo >&2 " (using download type '${GIMME_TYPE}')" exit 1 fi