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