#!/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 &>/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