diff --git a/LICENSES/third_party/gimme/LICENSE b/LICENSES/third_party/gimme/LICENSE new file mode 100644 index 000000000000..243158d1e811 --- /dev/null +++ b/LICENSES/third_party/gimme/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2018 gimme contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/build/common.sh b/build/common.sh index 7448a898ec7a..5ad0f1c929c4 100755 --- a/build/common.sh +++ b/build/common.sh @@ -526,6 +526,8 @@ function kube::build::run_build_command_ex() { --env "KUBE_BUILD_WITH_COVERAGE=${KUBE_BUILD_WITH_COVERAGE:-}" --env "KUBE_BUILD_PLATFORMS=${KUBE_BUILD_PLATFORMS:-}" --env "KUBE_CGO_OVERRIDES=' ${KUBE_CGO_OVERRIDES[*]:-} '" + --env "FORCE_HOST_GO=${FORCE_HOST_GO:-}" + --env "GO_VERSION=${GO_VERSION:-}" --env "GOFLAGS=${GOFLAGS:-}" --env "GOGCFLAGS=${GOGCFLAGS:-}" --env "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-}" diff --git a/hack/.spelling_failures b/hack/.spelling_failures index 22d931ebdc9d..ec90f74ff7d3 100644 --- a/hack/.spelling_failures +++ b/hack/.spelling_failures @@ -1,5 +1,6 @@ CHANGELOG go.mod go.sum +third_party/ translations/ vendor/ \ No newline at end of file diff --git a/hack/jenkins/test-dockerized.sh b/hack/jenkins/test-dockerized.sh index b2e84778e87c..12e8515dda7c 100755 --- a/hack/jenkins/test-dockerized.sh +++ b/hack/jenkins/test-dockerized.sh @@ -48,7 +48,6 @@ export LOG_LEVEL=4 cd "${GOPATH}/src/k8s.io/kubernetes" make generated_files -go install ./cmd/... ./hack/install-etcd.sh make test-cmd diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index 232c17b36c16..84b10268981e 100755 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -470,7 +470,21 @@ kube::golang::create_gopath_tree() { } # Ensure the go tool exists and is a viable version. +# Inputs: +# env-var GO_VERSION is the desired go version to use, downloading it if needed (defaults to content of .go-version) +# env-var FORCE_HOST_GO set to a non-empty value uses the go version in the $PATH and skips ensuring $GO_VERSION is used kube::golang::verify_go_version() { + # default GO_VERSION to content of .go-version + GO_VERSION="${GO_VERSION:-"$(cat "${KUBE_ROOT}/.go-version")"}" + # only setup go if we haven't set FORCE_HOST_GO, or `go version` doesn't match GO_VERSION + if ! ([ -n "${FORCE_HOST_GO:-}" ] || \ + (command -v go >/dev/null && [ "$(go version | cut -d' ' -f3)" = "go${GO_VERSION}" ])); then + export GIMME_ENV_PREFIX=${GIMME_ENV_PREFIX:-"${KUBE_OUTPUT}/.gimme/envs"} + export GIMME_VERSION_PREFIX=${GIMME_VERSION_PREFIX:-"${KUBE_OUTPUT}/.gimme/versions"} + # eval because the output of this is shell to set PATH etc. + eval "$("${KUBE_ROOT}/third_party/gimme/gimme" "${GO_VERSION}")" + fi + if [[ -z "$(command -v go)" ]]; then kube::log::usage_from_stdin <&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