github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/scripts/vagrant/provision/gimme.sh (about)

     1  #!/usr/bin/env bash
     2  # vim:noexpandtab:ts=2:sw=2:
     3  #
     4  #+  Usage: $(basename $0) [flags] [go-version] [version-prefix]
     5  #+  -
     6  #+  Version: ${GIMME_VERSION}
     7  #+  Copyright: ${GIMME_COPYRIGHT}
     8  #+  License URL: ${GIMME_LICENSE_URL}
     9  #+  -
    10  #+  Install go!  There are multiple types of installations available, with 'auto' being the default.
    11  #+  If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing
    12  #+  go installation.  This behavior may be disabled by providing '-f/--force/force' as first positional
    13  #+  argument.
    14  #+  -
    15  #+  Option flags:
    16  #+          -h --help help - show this help text and exit
    17  #+    -V --version version - show the version only and exit
    18  #+        -f --force force - remove the existing go installation if present prior to install
    19  #+          -l --list list - list installed go versions and exit
    20  #+        -k --known known - list known go versions and exit
    21  #+    --force-known-update - when used with --known, ignores the cache and updates
    22  #+    -r --resolve resolve - resolve a version specifier to a version, show that and exit
    23  #+  -
    24  #+  Influential env vars:
    25  #+  -
    26  #+        GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg)
    27  #+    GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}',
    28  #+                           may be given as second positional arg)
    29  #+              GIMME_ARCH - arch to install (default '${GIMME_ARCH}')
    30  #+        GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}')
    31  #+        GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}')
    32  #+     GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}')
    33  #+                GIMME_OS - os to install (default '${GIMME_OS}')
    34  #+               GIMME_TMP - temp directory (default '${GIMME_TMP}')
    35  #+              GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git')
    36  #+                           (default '${GIMME_TYPE}')
    37  #+      GIMME_INSTALL_RACE - install race directory after compile if non-empty.
    38  #+                           If the install type is 'binary', this option is ignored.
    39  #+             GIMME_DEBUG - enable tracing if non-empty
    40  #+      GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host
    41  #+        GIMME_SILENT_ENV - omit the 'go version' line from env file
    42  #+       GIMME_CGO_ENABLED - enable build of cgo support
    43  #+     GIMME_CC_FOR_TARGET - cross compiler for cgo support
    44  #+     GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}')
    45  #+        GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}')
    46  #+   GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}')
    47  #+  -
    48  #
    49  set -e
    50  shopt -s nullglob
    51  shopt -s dotglob
    52  shopt -s extglob
    53  set -o pipefail
    54  
    55  [[ ${GIMME_DEBUG} ]] && set -x
    56  
    57  readonly GIMME_VERSION="v1.5.3"
    58  readonly GIMME_COPYRIGHT="Copyright (c) 2015-2018 gimme contributors"
    59  readonly GIMME_LICENSE_URL="https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE"
    60  export GIMME_VERSION
    61  export GIMME_COPYRIGHT
    62  export GIMME_LICENSE_URL
    63  
    64  program_name="$(basename "$0")"
    65  # shellcheck disable=SC1117
    66  warn() { printf >&2 "%s: %s\n" "${program_name}" "${*}"; }
    67  die() {
    68  	warn "$@"
    69  	exit 1
    70  }
    71  
    72  # We don't want to go around hitting Google's servers with requests for
    73  # files named HEAD@{date}.tar so we only try binary/source downloads if
    74  # it looks like a plausible name to us.
    75  # We don't need to support 0. releases of Go.
    76  # We don't support 5 digit major-versions of Go (limit back-tracking in RE).
    77  # We don't support very long versions
    78  #   (both to avoid annoying download server operators with attacks and
    79  #    because regexp backtracking can be pathological).
    80  # Per _assert_version_given we do assume 2.0 not 2
    81  ALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\.[0-9][0-9a-zA-Z_-]{0,9})+$'
    82  #
    83  # The main path which allowed these to leak upstream before has been closed
    84  # but a valid git repo tag or branch-name will still reach the point of
    85  # being _tried_ upstream.
    86  
    87  # _do_curl "url" "file"
    88  _do_curl() {
    89  	mkdir -p "$(dirname "${2}")"
    90  
    91  	if command -v curl >/dev/null; then
    92  		curl -sSLf "${1}" -o "${2}" 2>/dev/null
    93  		return
    94  	fi
    95  
    96  	if command -v wget >/dev/null; then
    97  		wget -q "${1}" -O "${2}" 2>/dev/null
    98  		return
    99  	fi
   100  
   101  	if command -v fetch >/dev/null; then
   102  		fetch -q "${1}" -o "${2}" 2>/dev/null
   103  		return
   104  	fi
   105  
   106  	echo >&2 'error: no curl, wget, or fetch found'
   107  	exit 1
   108  }
   109  
   110  # _sha256sum "file"
   111  _sha256sum() {
   112  	if command -v sha256sum &>/dev/null; then
   113  		sha256sum "$@"
   114  	elif command -v gsha256sum &>/dev/null; then
   115  		gsha256sum "$@"
   116  	else
   117  		shasum -a 256 "$@"
   118  	fi
   119  }
   120  
   121  # sort versions, handling 1.10 after 1.9, not before 1.2
   122  # FreeBSD sort has --version-sort, none of the others do
   123  # Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty
   124  if sort --version-sort </dev/null &>/dev/null; then
   125  	_version_sort() { sort --version-sort; }
   126  else
   127  	_version_sort() {
   128  		# If we go to four-digit minor or patch versions, then extend the padding here
   129  		# (but in such a world, perhaps --version-sort will have become standard by then?)
   130  		sed -E 's/\.([0-9](\.|$))/.00\1/g; s/\.([0-9][0-9](\.|$))/.0\1/g' |
   131  			sort --general-numeric-sort |
   132  			sed 's/\.00*/./g'
   133  	}
   134  fi
   135  
   136  # _do_curls "file" "url" ["url"...]
   137  _do_curls() {
   138  	f="${1}"
   139  	shift
   140  	if _sha256sum -c "${f}.sha256" &>/dev/null; then
   141  		return 0
   142  	fi
   143  	for url in "${@}"; do
   144  		if _do_curl "${url}" "${f}"; then
   145  			if _do_curl "${url}.sha256" "${f}.sha256"; then
   146  				echo "$(cat "${f}.sha256")  ${f}" >"${f}.sha256.tmp"
   147  				mv "${f}.sha256.tmp" "${f}.sha256"
   148  				if ! _sha256sum -c "${f}.sha256" &>/dev/null; then
   149  					warn "sha256sum failed for '${f}'"
   150  					warn 'continuing to next candidate URL'
   151  					continue
   152  				fi
   153  			fi
   154  			return
   155  		fi
   156  	done
   157  	rm -f "${f}"
   158  	return 1
   159  }
   160  
   161  # _binary "version" "file.tar.gz" "arch"
   162  _binary() {
   163  	local version=${1}
   164  	local file=${2}
   165  	local arch=${3}
   166  	urls=(
   167  		"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz"
   168  	)
   169  	if [[ "${GIMME_OS}" == 'darwin' && "${GIMME_BINARY_OSX}" ]]; then
   170  		urls=(
   171  			"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz"
   172  			"${urls[@]}"
   173  		)
   174  	fi
   175  	if [ "${arch}" = 'arm' ]; then
   176  		# attempt "armv6l" vs just "arm" first (since that's what's officially published)
   177  		urls=(
   178  			"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz" # go1.6beta2 & go1.6rc1
   179  			"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz" # go1.6beta1
   180  			"${urls[@]}"
   181  		)
   182  	fi
   183  	if [ "${GIMME_OS}" = 'windows' ]; then
   184  		urls=(
   185  			"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip"
   186  		)
   187  	fi
   188  	_do_curls "${file}" "${urls[@]}"
   189  }
   190  
   191  # _source "version" "file.src.tar.gz"
   192  _source() {
   193  	urls=(
   194  		"${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz"
   195  		"https://github.com/golang/go/archive/go${1}.tar.gz"
   196  	)
   197  	_do_curls "${2}" "${urls[@]}"
   198  }
   199  
   200  # _fetch "dir"
   201  _fetch() {
   202  	mkdir -p "$(dirname "${1}")"
   203  
   204  	if [[ -d "${1}/.git" ]]; then
   205  		(
   206  			cd "${1}"
   207  			git remote set-url origin "${GIMME_GO_GIT_REMOTE}"
   208  			git fetch -q --all && git fetch -q --tags
   209  		)
   210  		return
   211  	fi
   212  
   213  	git clone -q "${GIMME_GO_GIT_REMOTE}" "${1}"
   214  }
   215  
   216  # _checkout "version" "dir"
   217  # NB: might emit a "renamed version" on stdout
   218  _checkout() {
   219  	local spec="${1:?}" godir="${2:?}"
   220  	# We are called twice, once during validation that a version was given and
   221  	# later during build.  We don't want to fetch twice, so we are fetching
   222  	# during the validation only, in the caller.
   223  
   224  	if [[ "${spec}" =~ ^[0-9a-f]{6,}$ ]]; then
   225  		# We always treat this as a commit sha, whether instead of doing
   226  		# branch tests etc.  It looks like a commit sha and the Go maintainers
   227  		# aren't daft enough to use pure hex for a tag or branch.
   228  		git -C "$godir" reset -q --hard "${spec}" || return 1
   229  		return 0
   230  	fi
   231  
   232  	# If spec looks like HEAD^{something} or HEAD^^^ then trying
   233  	# origin/$spec would succeed but we'd write junk to the filesystem,
   234  	# propagating annoying characters out.
   235  	local retval probe_named disallow rev
   236  
   237  	probe_named=1
   238  	disallow='[@^~:{}]'
   239  	if [[ "${spec}" =~ $disallow ]]; then
   240  		probe_named=0
   241  		[[ "${spec}" != "@" ]] || spec="HEAD"
   242  	fi
   243  
   244  	try_spec() { git -C "${godir}" reset -q --hard "$@" -- 2>/dev/null; }
   245  
   246  	retval=1
   247  	if ((probe_named)); then
   248  		retval=0
   249  		try_spec "origin/${spec}" ||
   250  			try_spec "origin/go${spec}" ||
   251  			{ [[ "${spec}" == "tip" ]] && try_spec origin/master; } ||
   252  			try_spec "refs/tags/${spec}" ||
   253  			try_spec "refs/tags/go${spec}" ||
   254  			retval=1
   255  	fi
   256  
   257  	if ((retval)); then
   258  		retval=0
   259  		# We're about to reset anyway, if we succeed, so we should reset to a
   260  		# known state before parsing what might be relative specs
   261  		try_spec origin/master &&
   262  			rev="$(git -C "${godir}" rev-parse --verify -q "${spec}^{object}")" &&
   263  			try_spec "${rev}" &&
   264  			git -C "${godir}" rev-parse --verify -q --short=12 "${rev}" ||
   265  			retval=1
   266  		# that rev-parse prints to stdout, so we can affect the version seen
   267  	fi
   268  
   269  	unset -f try_spec
   270  	return $retval
   271  }
   272  
   273  # _extract "file.tar.gz" "dir"
   274  _extract() {
   275  	mkdir -p "${2}"
   276  
   277  	if [[ "${1}" == *.tar.gz ]]; then
   278  		tar -xf "${1}" -C "${2}" --strip-components 1
   279  	else
   280  		unzip -q "${1}" -d "${2}"
   281  		mv "${2}"/go/* "${2}"
   282  		rmdir "${2}"/go
   283  	fi
   284  }
   285  
   286  # _setup_bootstrap
   287  _setup_bootstrap() {
   288  	local versions=("1.12" "1.11" "1.10" "1.9" "1.8" "1.7" "1.6" "1.5" "1.4")
   289  
   290  	# try existing
   291  	for v in "${versions[@]}"; do
   292  		for candidate in "${GIMME_ENV_PREFIX}/go${v}"*".env"; do
   293  			if [ -s "${candidate}" ]; then
   294  				# shellcheck source=/dev/null
   295  				GOROOT_BOOTSTRAP="$(source "${candidate}" 2>/dev/null && go env GOROOT)"
   296  				export GOROOT_BOOTSTRAP
   297  				return 0
   298  			fi
   299  		done
   300  	done
   301  
   302  	# try binary
   303  	for v in "${versions[@]}"; do
   304  		if [ -n "$(_try_binary "${v}" "${GIMME_HOSTARCH}")" ]; then
   305  			export GOROOT_BOOTSTRAP="${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}"
   306  			return 0
   307  		fi
   308  	done
   309  
   310  	echo >&2 "Unable to setup go bootstrap from existing or binary"
   311  	return 1
   312  }
   313  
   314  # _compile "dir"
   315  _compile() {
   316  	(
   317  		if grep -q GOROOT_BOOTSTRAP "${1}/src/make.bash" &>/dev/null; then
   318  			_setup_bootstrap || return 1
   319  		fi
   320  		cd "${1}"
   321  		if [[ -d .git ]]; then
   322  			git clean -dfx -q
   323  		fi
   324  		cd src
   325  		export GOOS="${GIMME_OS}" GOARCH="${GIMME_ARCH}"
   326  		export CGO_ENABLED="${GIMME_CGO_ENABLED}"
   327  		export CC_FOR_TARGET="${GIMME_CC_FOR_TARGET}"
   328  
   329  		local make_log="${1}/make.${GOOS}.${GOARCH}.log"
   330  		if [[ "${GIMME_DEBUG}" -ge "2" ]]; then
   331  			./make.bash -v 2>&1 | tee "${make_log}" 1>&2 || return 1
   332  		else
   333  			./make.bash &>"${make_log}" || return 1
   334  		fi
   335  	)
   336  }
   337  
   338  _try_install_race() {
   339  	if [[ ! "${GIMME_INSTALL_RACE}" ]]; then
   340  		return 0
   341  	fi
   342  	"${1}/bin/go" install -race std
   343  }
   344  
   345  _can_compile() {
   346  	cat >"${GIMME_TMP}/test.go" <<'EOF'
   347  package main
   348  import "os"
   349  func main() {
   350  	os.Exit(0)
   351  }
   352  EOF
   353  	"${1}/bin/go" run "${GIMME_TMP}/test.go"
   354  }
   355  
   356  # _env "dir"
   357  _env() {
   358  	[[ -d "${1}/bin" && -x "${1}/bin/go" ]] || return 1
   359  
   360  	# if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source
   361  	# automatically
   362  	GOROOT="${1}" "${1}/bin/go" version &>/dev/null || return 1
   363  
   364  	# https://twitter.com/davecheney/status/431581286918934528
   365  	# we have to GOROOT sometimes because we use official release binaries in unofficial locations :(
   366  	#
   367  	# Issue 87 leads to:
   368  	#   No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it.
   369  	#   The "avoid setting it" is _only_ for people using official releases in official locations.
   370  	#   Tools like `gimme` are the reason that GOROOT-in-env exists.
   371  
   372  	echo
   373  	if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" ]]; then
   374  		echo 'unset GOOS;'
   375  	else
   376  		echo 'export GOOS="'"${GIMME_OS}"'";'
   377  	fi
   378  	if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then
   379  		echo 'unset GOARCH;'
   380  	else
   381  		echo 'export GOARCH="'"${GIMME_ARCH}"'";'
   382  	fi
   383  
   384  	echo "export GOROOT='${1}';"
   385  
   386  	# shellcheck disable=SC2016
   387  	echo 'export PATH="'"${1}/bin"':${PATH}";'
   388  	if [[ -z "${GIMME_SILENT_ENV}" ]]; then
   389  		echo 'go version >&2;'
   390  	fi
   391  	echo
   392  }
   393  
   394  # _env_alias "dir" "env-file"
   395  _env_alias() {
   396  	if [[ "${GIMME_NO_ENV_ALIAS}" ]]; then
   397  		echo "${2}"
   398  		return
   399  	fi
   400  
   401  	if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" && "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then
   402  		# GIMME_GO_VERSION might be a branch, which can contain '/'
   403  		local dest="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\//__}.env"
   404  		cp "${2}" "${dest}"
   405  		ln -sf "${dest}" "${GIMME_ENV_PREFIX}/latest.env"
   406  		echo "${dest}"
   407  	else
   408  		echo "${2}"
   409  	fi
   410  }
   411  
   412  _try_existing() {
   413  	case "${1}" in
   414  	binary)
   415  		local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}"
   416  		local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env"
   417  		;;
   418  	source)
   419  		local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src"
   420  		local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env"
   421  		;;
   422  	*)
   423  		_try_existing binary || _try_existing source
   424  		return $?
   425  		;;
   426  	esac
   427  
   428  	if [[ -x "${existing_ver}/bin/go" && -s "${existing_env}" ]]; then
   429  		# newer envs have existing semi-colon at end of line, because newer gimme
   430  		# puts them there; envs created before that change lack those semi-colons
   431  		# and should gain them, to make it easier for people using eval without
   432  		# double-quoting the command substition.
   433  		sed -e 's/\([^;]\)$/\1;/' <"${existing_env}"
   434  		# gimme is the corner-case where GOROOT _should_ be overriden, since if the
   435  		# ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is
   436  		# unset, then it will be used and the wrong golang will be picked up.
   437  		# Lots of old installs won't have GOROOT; munge it from $PATH
   438  		if grep -qs '^unset GOROOT' -- "${existing_env}"; then
   439  			sed -n -e 's/^export PATH="\(.*\)\/bin:.*$/export GOROOT='"'"'\1'"'"';/p' <"${existing_env}"
   440  			echo
   441  		fi
   442  		# Export the same variables whether building new or using existing
   443  		echo "export GIMME_ENV='${existing_env}';"
   444  		return
   445  	fi
   446  
   447  	return 1
   448  }
   449  
   450  # _try_binary "version" "arch"
   451  _try_binary() {
   452  	local version=${1}
   453  	local arch=${2}
   454  	local bin_tgz="${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz"
   455  	local bin_dir="${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}"
   456  	local bin_env="${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env"
   457  
   458  	[[ "${version}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1
   459  
   460  	if [ "${GIMME_OS}" = 'windows' ]; then
   461  		bin_tgz=${bin_tgz%.tar.gz}.zip
   462  	fi
   463  
   464  	_binary "${version}" "${bin_tgz}" "${arch}" || return 1
   465  	_extract "${bin_tgz}" "${bin_dir}" || return 1
   466  	_env "${bin_dir}" | tee "${bin_env}" || return 1
   467  	echo "export GIMME_ENV=\"$(_env_alias "${bin_dir}" "${bin_env}")\""
   468  }
   469  
   470  _try_source() {
   471  	local src_tgz="${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz"
   472  	local src_dir="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src"
   473  	local src_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env"
   474  
   475  	[[ "${GIMME_GO_VERSION}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1
   476  
   477  	_source "${GIMME_GO_VERSION}" "${src_tgz}" || return 1
   478  	_extract "${src_tgz}" "${src_dir}" || return 1
   479  	_compile "${src_dir}" || return 1
   480  	_try_install_race "${src_dir}" || return 1
   481  	_env "${src_dir}" | tee "${src_env}" || return 1
   482  	echo "export GIMME_ENV=\"$(_env_alias "${src_dir}" "${src_env}")\""
   483  }
   484  
   485  # We do _not_ try to use any version caching with _try_existing(), but instead
   486  # build afresh each time.  We don't want to deal with someone moving the repo
   487  # to other-version, doing an install, then resetting it back to
   488  # last-version-we-saw and thus introducing conflicts.
   489  #
   490  # If you want to re-use a built-at-spec version, then avoid moving the repo
   491  # and source the generated .env manually.
   492  # Note that the env will just refer to the 'go' directory, so it's not safe
   493  # to reuse anyway.
   494  _try_git() {
   495  	local git_dir="${GIMME_VERSION_PREFIX}/go"
   496  	local git_env="${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env"
   497  	local resolved_sha
   498  
   499  	# Any tags should have been resolved when we asserted that we were
   500  	# given a version, so no need to handle that here.
   501  	_checkout "${GIMME_GO_VERSION}" "${git_dir}" >/dev/null || return 1
   502  	_compile "${git_dir}" || return 1
   503  	_try_install_race "${git_dir}" || return 1
   504  	_env "${git_dir}" | tee "${git_env}" || return 1
   505  	echo "export GIMME_ENV=\"$(_env_alias "${git_dir}" "${git_env}")\""
   506  }
   507  
   508  _wipe_version() {
   509  	local env_file="${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env"
   510  
   511  	if [[ -s "${env_file}" ]]; then
   512  		rm -rf "$(awk -F\" '/GOROOT/ { print $2 }' "${env_file}")"
   513  		rm -f "${env_file}"
   514  	fi
   515  }
   516  
   517  _list_versions() {
   518  	if [ ! -d "${GIMME_VERSION_PREFIX}" ]; then
   519  		return 0
   520  	fi
   521  
   522  	local current_version
   523  	current_version="$(go env GOROOT 2>/dev/null)"
   524  	current_version="${current_version##*/go}"
   525  	current_version="${current_version%%.${GIMME_OS}.*}"
   526  
   527  	# 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash
   528  	# doesn't appear to have anything like that.
   529  	for d in "${GIMME_VERSION_PREFIX}/go"*".${GIMME_OS}."*; do
   530  		local cleaned="${d##*/go}"
   531  		cleaned="${cleaned%%.${GIMME_OS}.*}"
   532  		echo "${cleaned}"
   533  	done | _version_sort | while read -r cleaned; do
   534  		echo -en "${cleaned}"
   535  		if [[ "${cleaned}" == "${current_version}" ]]; then
   536  			echo -en ' <= current' >&2
   537  		fi
   538  		echo
   539  	done
   540  }
   541  
   542  _update_remote_known_list_if_needed() {
   543  	# shellcheck disable=SC1117
   544  	local exp="go([[:alnum:]\.]*)\.src.*" # :alnum: catches beta versions too
   545  	local list="${GIMME_VERSION_PREFIX}/known-versions.txt"
   546  	local dlfile="${GIMME_TMP}/known-dl"
   547  
   548  	if [[ -e "${list}" ]] &&
   549  		! ((force_known_update)) &&
   550  		! _file_older_than_secs "${list}" "${GIMME_KNOWN_CACHE_MAX}"; then
   551  		echo "${list}"
   552  		return 0
   553  	fi
   554  
   555  	[[ -d "${GIMME_VERSION_PREFIX:?}" ]] || mkdir -p -- "${GIMME_VERSION_PREFIX}"
   556  
   557  	_do_curl "${GIMME_LIST_KNOWN}" "${dlfile}"
   558  
   559  	while read -r line; do
   560  		if [[ "${line}" =~ ${exp} ]]; then
   561  			echo "${BASH_REMATCH[1]}"
   562  		fi
   563  	done <"${dlfile}" | _version_sort | uniq >"${list}.new"
   564  	rm -f "${list}" &>/dev/null
   565  	mv "${list}.new" "${list}"
   566  
   567  	rm -f "${dlfile}"
   568  	echo "${list}"
   569  	return 0
   570  }
   571  
   572  _list_known() {
   573  	local knownfile
   574  	knownfile="$(_update_remote_known_list_if_needed)"
   575  
   576  	(
   577  		_list_versions 2>/dev/null
   578  		cat -- "${knownfile}"
   579  	) | grep . | _version_sort | uniq
   580  }
   581  
   582  # For the "invoked on commandline" case, we want to always pass unknown
   583  # strings through, so that we can be a uniqueness filter, but for unknown
   584  # names we want to exit with a value other than 1, so we document that
   585  # we'll exit 2.  For use by other functions, 2 is as good as 1.
   586  _resolve_version() {
   587  	case "${1}" in
   588  	stable)
   589  		_get_curr_stable
   590  		return 0
   591  		;;
   592  	oldstable)
   593  		_get_old_stable
   594  		return 0
   595  		;;
   596  	tip)
   597  		echo "tip"
   598  		return 0
   599  		;;
   600  	*.x)
   601  		true
   602  		;;
   603  	*)
   604  		echo "${1}"
   605  		local GIMME_GO_VERSION="$1"
   606  		local ASSERT_ABORT='return'
   607  		if _assert_version_given 2>/dev/null; then
   608  			return 0
   609  		fi
   610  		warn "version specifier '${1}' unknown"
   611  		return 2
   612  		;;
   613  	esac
   614  	# We have a .x suffix
   615  	local base="${1%.x}"
   616  	local ver last='' known
   617  	known="$(_update_remote_known_list_if_needed)" # will be version-sorted
   618  	if [[ ! "${base}" =~ ^[0-9.]+$ ]]; then
   619  		warn "resolve pattern '${base}.x' invalid for .x finding"
   620  		return 2
   621  	fi
   622  	# The `.x` is optional; "1.10" matches "1.10.x"
   623  	local search="^${base//./\\.}(\\.[0-9.]+)?\$"
   624  	# avoid regexp attacks
   625  	while read -r ver; do
   626  		[[ "${ver}" =~ $search ]] || continue
   627  		last="${ver}"
   628  	done <"$known"
   629  	if [[ -n "${last}" ]]; then
   630  		echo "${last}"
   631  		return 0
   632  	fi
   633  	echo "${1}"
   634  	warn "given '${1}' but no release for '${base}' found"
   635  	return 2
   636  }
   637  
   638  _realpath() {
   639  	# shellcheck disable=SC2005
   640  	[ -d "$1" ] && echo "$(cd "$1" && pwd)" || echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
   641  }
   642  
   643  _get_curr_stable() {
   644  	local stable="${GIMME_VERSION_PREFIX}/stable"
   645  
   646  	if _file_older_than_secs "${stable}" 86400; then
   647  		_update_stable "${stable}"
   648  	fi
   649  
   650  	cat "${stable}"
   651  }
   652  
   653  _get_old_stable() {
   654  	local oldstable="${GIMME_VERSION_PREFIX}/oldstable"
   655  
   656  	if _file_older_than_secs "${oldstable}" 86400; then
   657  		_update_oldstable "${oldstable}"
   658  	fi
   659  
   660  	cat "${oldstable}"
   661  }
   662  
   663  _update_stable() {
   664  	local stable="${1}"
   665  	local url="https://golang.org/VERSION?m=text"
   666  
   667  	_do_curl "${url}" "${stable}"
   668  	sed -i.old -e 's/^go\(.*\)/\1/' "${stable}"
   669  	rm -f "${stable}.old"
   670  }
   671  
   672  _update_oldstable() {
   673  	local oldstable="${1}"
   674  	local oldstable_x
   675  	oldstable_x=$(_get_curr_stable | awk -F. '{
   676  		$2--;
   677  		print $1 "." $2 "." "x"
   678  	}')
   679  	_resolve_version "${oldstable_x}" >"${oldstable}"
   680  }
   681  
   682  _last_mod_timestamp() {
   683  	local filename="${1}"
   684  	case "${GIMME_HOSTOS}" in
   685  	darwin | *bsd)
   686  		stat -f %m "${filename}"
   687  		;;
   688  	linux)
   689  		stat -c %Y "${filename}"
   690  		;;
   691  	esac
   692  }
   693  
   694  _file_older_than_secs() {
   695  	local file="${1}"
   696  	local age_secs="${2}"
   697  	local ts
   698  	# if the file does not exist, we return true, as the cache needs updating
   699  	ts="$(_last_mod_timestamp "${file}" 2>/dev/null)" || return 0
   700  	((($(date +%s) - ts) > age_secs))
   701  }
   702  
   703  _assert_version_given() {
   704  	# By the time we're called, aliases such as "stable" must have been resolved
   705  	# but we could be a reference in git.
   706  	#
   707  	# Versions can include suffices such as in "1.8beta2", so our assumption is that
   708  	# there will always be a minor present; the first public release was "1.0" so
   709  	# we assume "2.0" not "2".
   710  
   711  	if [[ -z "${GIMME_GO_VERSION}" ]]; then
   712  		echo >&2 'error: no GIMME_GO_VERSION supplied'
   713  		echo >&2 "  ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}"
   714  		echo >&2 "  ex: ${0} 1.4.1 ${*}"
   715  		${ASSERT_ABORT:-exit} 1
   716  	fi
   717  
   718  	# Note: _resolve_version calls back to us (_assert_version_given), but
   719  	# only for cases where the version does not end with .x, so this should
   720  	# be safe.
   721  	# This should be untangled.  PRs accepted, good starter project.
   722  	if [[ "${GIMME_GO_VERSION}" == *.x ]]; then
   723  		GIMME_GO_VERSION="$(_resolve_version "${GIMME_GO_VERSION}")" || ${ASSERT_ABORT:-exit} 1
   724  	fi
   725  
   726  	if [[ "${GIMME_GO_VERSION}" == +([[:digit:]]).+([[:digit:]])* ]]; then
   727  		return 0
   728  	fi
   729  
   730  	# Here we resolve symbolic references.  If we don't, then we get some
   731  	# random git tag name being accepted as valid and then we try to
   732  	# curl garbage from upstream.
   733  	if [[ "${GIMME_TYPE}" == "auto" || "${GIMME_TYPE}" == "git" ]]; then
   734  		local git_dir="${GIMME_VERSION_PREFIX}/go"
   735  		local resolved_sha
   736  		_fetch "${git_dir}"
   737  		if resolved_sha="$(_checkout "${GIMME_GO_VERSION}" "${git_dir}")"; then
   738  			if [[ -n "${resolved_sha}" ]]; then
   739  				# Break our normal silence, this one really needs to be seen on stderr
   740  				# always; auditability and knowing what version of Go you got wins.
   741  				warn "resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'"
   742  				GIMME_GO_VERSION="${resolved_sha}"
   743  			fi
   744  			return 0
   745  		fi
   746  	fi
   747  
   748  	echo >&2 'error: GIMME_GO_VERSION not recognized as valid'
   749  	echo >&2 "  got: ${GIMME_GO_VERSION}"
   750  	${ASSERT_ABORT:-exit} 1
   751  }
   752  
   753  _exclude_from_backups() {
   754  	# Please avoid anything which requires elevated privileges or is obnoxious
   755  	# enough to offend the invoker
   756  	case "${GIMME_HOSTOS}" in
   757  	darwin)
   758  		# Darwin: Time Machine is "standard", we can add others.  The default
   759  		# mechanism is sticky, as an attribute on the dir, requires no
   760  		# privileges, is idempotent (and doesn't support -- to end flags).
   761  		tmutil addexclusion "$@"
   762  		;;
   763  	esac
   764  }
   765  
   766  _versint() {
   767  	IFS=" " read -r -a args <<<"${1//[^0-9]/ }"
   768  	printf '1%03d%03d%03d%03d' "${args[@]}"
   769  }
   770  
   771  _to_goarch() {
   772  	case "${1}" in
   773  	aarch64) echo "arm64" ;;
   774  	*) echo "${1}" ;;
   775  	esac
   776  }
   777  
   778  : "${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}"
   779  : "${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}"
   780  : "${GIMME_ARCH:=$(_to_goarch "$(uname -m)")}"
   781  : "${GIMME_HOSTARCH:=$(_to_goarch "$(uname -m)")}"
   782  : "${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}"
   783  : "${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}"
   784  : "${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}"
   785  : "${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}"
   786  : "${GIMME_TYPE:=auto}" # 'auto', 'binary', 'source', or 'git'
   787  : "${GIMME_BINARY_OSX:=osx10.8}"
   788  : "${GIMME_DOWNLOAD_BASE:=https://storage.googleapis.com/golang}"
   789  : "${GIMME_LIST_KNOWN:=https://golang.org/dl}"
   790  : "${GIMME_KNOWN_CACHE_MAX:=10800}"
   791  
   792  # The version prefix must be an absolute path
   793  case "${GIMME_VERSION_PREFIX}" in
   794  /*) true ;;
   795  *)
   796  	echo >&2 " Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX"
   797  	GIMME_VERSION_PREFIX="$(pwd)/${GIMME_VERSION_PREFIX}"
   798  	echo >&2 " to: $GIMME_VERSION_PREFIX"
   799  	;;
   800  esac
   801  
   802  case "${GIMME_OS}" in mingw* | msys_nt*)
   803  	# Minimalist GNU for Windows
   804  	GIMME_OS='windows'
   805  
   806  	if [ "${GIMME_ARCH}" = 'i686' ]; then
   807  		GIMME_ARCH="386"
   808  	else
   809  		GIMME_ARCH="amd64"
   810  	fi
   811  	;;
   812  esac
   813  
   814  force_install=0
   815  force_known_update=0
   816  
   817  while [[ $# -gt 0 ]]; do
   818  	case "${1}" in
   819  	-h | --help | help | wat)
   820  		_old_ifs="$IFS"
   821  		IFS=';'
   822  		awk '/^#\+  / {
   823  				sub(/^#\+  /, "", $0) ;
   824  				sub(/-$/, "", $0) ;
   825  				print $0
   826  			}' "$0" | while read -r line; do
   827  			eval "echo \"$line\""
   828  		done
   829  		IFS="$_old_ifs"
   830  		exit 0
   831  		;;
   832  	-V | --version | version)
   833  		echo "${GIMME_VERSION}"
   834  		exit 0
   835  		;;
   836  	-r | --resolve | resolve)
   837  		# The normal mkdir of versions is below; we don't want to move it up
   838  		# to where we create files just if asked our version; thus
   839  		# _resolve_version has to mkdir the versions dir itself.
   840  		if [[ $# -ge 2 ]]; then
   841  			_resolve_version "${2}"
   842  		elif [[ -n "${GIMME_GO_VERSION:-}" ]]; then
   843  			_resolve_version "${GIMME_GO_VERSION}"
   844  		else
   845  			die "resolve must be given a version to resolve"
   846  		fi
   847  		exit $?
   848  		;;
   849  	-l | --list | list)
   850  		_list_versions
   851  		exit 0
   852  		;;
   853  	-k | --known | known)
   854  		_list_known
   855  		exit 0
   856  		;;
   857  	-f | --force | force)
   858  		force_install=1
   859  		;;
   860  	--force-known-update | force-known-update)
   861  		force_known_update=1
   862  		;;
   863  	-i | install)
   864  		true # ignore a dummy argument
   865  		;;
   866  	*)
   867  		break
   868  		;;
   869  	esac
   870  	shift
   871  done
   872  
   873  if [[ -n "${1}" ]]; then
   874  	GIMME_GO_VERSION="${1}"
   875  fi
   876  if [[ -n "${2}" ]]; then
   877  	GIMME_VERSION_PREFIX="${2}"
   878  fi
   879  
   880  case "${GIMME_ARCH}" in
   881  x86_64) GIMME_ARCH=amd64 ;;
   882  x86) GIMME_ARCH=386 ;;
   883  arm64)
   884  	if [[ "${GIMME_GO_VERSION}" != master && "$(_versint "${GIMME_GO_VERSION}")" < "$(_versint 1.5)" ]]; then
   885  		echo >&2 "error: ${GIMME_ARCH} is not supported by this go version"
   886  		echo >&2 "try go1.5 or newer"
   887  		exit 1
   888  	fi
   889  	if [[ "${GIMME_HOSTOS}" == "linux" && "${GIMME_HOSTARCH}" != "${GIMME_ARCH}" ]]; then
   890  		: "${GIMME_CC_FOR_TARGET:="aarch64-linux-gnu-gcc"}"
   891  	fi
   892  	;;
   893  arm*) GIMME_ARCH=arm ;;
   894  esac
   895  
   896  case "${GIMME_HOSTARCH}" in
   897  x86_64) GIMME_HOSTARCH=amd64 ;;
   898  x86) GIMME_HOSTARCH=386 ;;
   899  arm64) ;;
   900  arm*) GIMME_HOSTARCH=arm ;;
   901  esac
   902  
   903  case "${GIMME_GO_VERSION}" in
   904  stable) GIMME_GO_VERSION=$(_get_curr_stable) ;;
   905  oldstable) GIMME_GO_VERSION=$(_get_old_stable) ;;
   906  esac
   907  
   908  _assert_version_given "$@"
   909  
   910  ((force_install)) && _wipe_version "${GIMME_GO_VERSION}"
   911  
   912  unset GOARCH
   913  unset GOBIN
   914  unset GOOS
   915  unset GOPATH
   916  unset GOROOT
   917  unset CGO_ENABLED
   918  unset CC_FOR_TARGET
   919  # GO111MODULE breaks build of Go itself
   920  unset GO111MODULE
   921  
   922  mkdir -p "${GIMME_VERSION_PREFIX}" "${GIMME_ENV_PREFIX}"
   923  # The envs dir stays small and provides a record of what had been installed
   924  # whereas the versions dir grows by hundreds of MB per version and is not
   925  # intended to support local modifications (as that subverts the point of gimme)
   926  # _and_ is a cache, so we're unilaterally declaring that the contents of
   927  # the versions dir should be excluded from system backups.
   928  _exclude_from_backups "${GIMME_VERSION_PREFIX}"
   929  
   930  GIMME_VERSION_PREFIX="$(_realpath "${GIMME_VERSION_PREFIX}")"
   931  GIMME_ENV_PREFIX="$(_realpath "${GIMME_ENV_PREFIX}")"
   932  
   933  if ! case "${GIMME_TYPE}" in
   934  	binary) _try_existing binary || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" ;;
   935  	source) _try_existing source || _try_source || _try_git ;;
   936  	git) _try_git ;;
   937  	auto) _try_existing || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" || _try_source || _try_git ;;
   938  	*)
   939  		echo >&2 "I don't know how to '${GIMME_TYPE}'."
   940  		echo >&2 "  Try 'auto', 'binary', 'source', or 'git'."
   941  		exit 1
   942  		;;
   943  	esac; then
   944  	echo >&2 "I don't have any idea what to do with '${GIMME_GO_VERSION}'."
   945  	echo >&2 "  (using download type '${GIMME_TYPE}')"
   946  	exit 1
   947  fi