| #!/usr/bin/env bash |
| |
| # Copyright 2014 The Kubernetes Authors. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # shellcheck disable=SC2034 # Variables sourced in other scripts. |
| |
| readonly KUBE_GOPATH="${KUBE_GOPATH:-"${KUBE_OUTPUT}/go"}" |
| export KUBE_GOPATH |
| |
| # The server platform we are building on. |
| readonly KUBE_SUPPORTED_SERVER_PLATFORMS=( |
| linux/amd64 |
| linux/arm64 |
| linux/s390x |
| linux/ppc64le |
| ) |
| |
| # The node platforms we build for |
| readonly KUBE_SUPPORTED_NODE_PLATFORMS=( |
| linux/amd64 |
| linux/arm64 |
| linux/s390x |
| linux/ppc64le |
| windows/amd64 |
| ) |
| |
| # If we update this we should also update the set of platforms whose standard |
| # library is precompiled for in build/build-image/cross/Dockerfile |
| readonly KUBE_SUPPORTED_CLIENT_PLATFORMS=( |
| linux/amd64 |
| linux/386 |
| linux/arm |
| linux/arm64 |
| linux/s390x |
| linux/ppc64le |
| darwin/amd64 |
| darwin/arm64 |
| windows/amd64 |
| windows/386 |
| windows/arm64 |
| ) |
| |
| # Which platforms we should compile test targets for. |
| # Not all client platforms need these tests |
| readonly KUBE_SUPPORTED_TEST_PLATFORMS=( |
| linux/amd64 |
| linux/arm64 |
| linux/s390x |
| linux/ppc64le |
| darwin/amd64 |
| darwin/arm64 |
| windows/amd64 |
| windows/arm64 |
| ) |
| |
| # The set of server targets that we are only building for Linux |
| kube::golang::server_targets() { |
| local targets=( |
| cmd/kube-proxy |
| cmd/kube-apiserver |
| cmd/kube-controller-manager |
| cmd/kubelet |
| cmd/kubeadm |
| cmd/kube-scheduler |
| staging/src/k8s.io/component-base/logs/kube-log-runner |
| staging/src/k8s.io/kube-aggregator |
| staging/src/k8s.io/apiextensions-apiserver |
| cluster/gce/gci/mounter |
| ) |
| echo "${targets[@]}" |
| } |
| |
| IFS=" " read -ra KUBE_SERVER_TARGETS <<< "$(kube::golang::server_targets)" |
| readonly KUBE_SERVER_TARGETS |
| readonly KUBE_SERVER_BINARIES=("${KUBE_SERVER_TARGETS[@]##*/}") |
| |
| # The set of server targets we build docker images for |
| kube::golang::server_image_targets() { |
| # NOTE: this contains cmd targets for kube::build::get_docker_wrapped_binaries |
| local targets=( |
| cmd/kube-apiserver |
| cmd/kube-controller-manager |
| cmd/kube-scheduler |
| cmd/kube-proxy |
| cmd/kubectl |
| ) |
| echo "${targets[@]}" |
| } |
| |
| IFS=" " read -ra KUBE_SERVER_IMAGE_TARGETS <<< "$(kube::golang::server_image_targets)" |
| readonly KUBE_SERVER_IMAGE_TARGETS |
| readonly KUBE_SERVER_IMAGE_BINARIES=("${KUBE_SERVER_IMAGE_TARGETS[@]##*/}") |
| |
| # The set of conformance targets we build docker image for |
| kube::golang::conformance_image_targets() { |
| # NOTE: this contains cmd targets for kube::release::build_conformance_image |
| local targets=( |
| ginkgo |
| test/e2e/e2e.test |
| test/conformance/image/go-runner |
| cmd/kubectl |
| ) |
| echo "${targets[@]}" |
| } |
| |
| IFS=" " read -ra KUBE_CONFORMANCE_IMAGE_TARGETS <<< "$(kube::golang::conformance_image_targets)" |
| readonly KUBE_CONFORMANCE_IMAGE_TARGETS |
| |
| # The set of server targets that we are only building for Kubernetes nodes |
| kube::golang::node_targets() { |
| local targets=( |
| cmd/kube-proxy |
| cmd/kubeadm |
| cmd/kubelet |
| staging/src/k8s.io/component-base/logs/kube-log-runner |
| ) |
| echo "${targets[@]}" |
| } |
| |
| IFS=" " read -ra KUBE_NODE_TARGETS <<< "$(kube::golang::node_targets)" |
| readonly KUBE_NODE_TARGETS |
| readonly KUBE_NODE_BINARIES=("${KUBE_NODE_TARGETS[@]##*/}") |
| readonly KUBE_NODE_BINARIES_WIN=("${KUBE_NODE_BINARIES[@]/%/.exe}") |
| |
| # ------------ |
| # NOTE: All functions that return lists should use newlines. |
| # bash functions can't return arrays, and spaces are tricky, so newline |
| # separators are the preferred pattern. |
| # To transform a string of newline-separated items to an array, use kube::util::read-array: |
| # kube::util::read-array FOO < <(kube::golang::dups a b c a) |
| # |
| # ALWAYS remember to quote your subshells. Not doing so will break in |
| # bash 4.3, and potentially cause other issues. |
| # ------------ |
| |
| # Returns a sorted newline-separated list containing only duplicated items. |
| kube::golang::dups() { |
| # We use printf to insert newlines, which are required by sort. |
| printf "%s\n" "$@" | sort | uniq -d |
| } |
| |
| # Returns a sorted newline-separated list with duplicated items removed. |
| kube::golang::dedup() { |
| # We use printf to insert newlines, which are required by sort. |
| printf "%s\n" "$@" | sort -u |
| } |
| |
| # Depends on values of user-facing KUBE_BUILD_PLATFORMS, KUBE_FASTBUILD, |
| # and KUBE_BUILDER_OS. |
| # Configures KUBE_SERVER_PLATFORMS, KUBE_NODE_PLATFOMRS, |
| # KUBE_TEST_PLATFORMS, and KUBE_CLIENT_PLATFORMS, then sets them |
| # to readonly. |
| # The configured vars will only contain platforms allowed by the |
| # KUBE_SUPPORTED* vars at the top of this file. |
| declare -a KUBE_SERVER_PLATFORMS |
| declare -a KUBE_CLIENT_PLATFORMS |
| declare -a KUBE_NODE_PLATFORMS |
| declare -a KUBE_TEST_PLATFORMS |
| kube::golang::setup_platforms() { |
| if [[ -n "${KUBE_BUILD_PLATFORMS:-}" ]]; then |
| # KUBE_BUILD_PLATFORMS needs to be read into an array before the next |
| # step, or quoting treats it all as one element. |
| local -a platforms |
| IFS=" " read -ra platforms <<< "${KUBE_BUILD_PLATFORMS}" |
| |
| # Deduplicate to ensure the intersection trick with kube::golang::dups |
| # is not defeated by duplicates in user input. |
| kube::util::read-array platforms < <(kube::golang::dedup "${platforms[@]}") |
| |
| # Use kube::golang::dups to restrict the builds to the platforms in |
| # KUBE_SUPPORTED_*_PLATFORMS. Items should only appear at most once in each |
| # set, so if they appear twice after the merge they are in the intersection. |
| kube::util::read-array KUBE_SERVER_PLATFORMS < <(kube::golang::dups \ |
| "${platforms[@]}" \ |
| "${KUBE_SUPPORTED_SERVER_PLATFORMS[@]}" \ |
| ) |
| readonly KUBE_SERVER_PLATFORMS |
| |
| kube::util::read-array KUBE_NODE_PLATFORMS < <(kube::golang::dups \ |
| "${platforms[@]}" \ |
| "${KUBE_SUPPORTED_NODE_PLATFORMS[@]}" \ |
| ) |
| readonly KUBE_NODE_PLATFORMS |
| |
| kube::util::read-array KUBE_TEST_PLATFORMS < <(kube::golang::dups \ |
| "${platforms[@]}" \ |
| "${KUBE_SUPPORTED_TEST_PLATFORMS[@]}" \ |
| ) |
| readonly KUBE_TEST_PLATFORMS |
| |
| kube::util::read-array KUBE_CLIENT_PLATFORMS < <(kube::golang::dups \ |
| "${platforms[@]}" \ |
| "${KUBE_SUPPORTED_CLIENT_PLATFORMS[@]}" \ |
| ) |
| readonly KUBE_CLIENT_PLATFORMS |
| |
| elif [[ "${KUBE_FASTBUILD:-}" == "true" ]]; then |
| host_arch=$(kube::util::host_arch) |
| if [[ "${host_arch}" != "amd64" && "${host_arch}" != "arm64" && "${host_arch}" != "ppc64le" && "${host_arch}" != "s390x" ]]; then |
| # on any platform other than amd64, arm64, ppc64le and s390x, we just default to amd64 |
| host_arch="amd64" |
| fi |
| KUBE_SERVER_PLATFORMS=("linux/${host_arch}") |
| readonly KUBE_SERVER_PLATFORMS |
| KUBE_NODE_PLATFORMS=("linux/${host_arch}") |
| readonly KUBE_NODE_PLATFORMS |
| if [[ "${KUBE_BUILDER_OS:-}" == "darwin"* ]]; then |
| KUBE_TEST_PLATFORMS=( |
| "darwin/${host_arch}" |
| "linux/${host_arch}" |
| ) |
| readonly KUBE_TEST_PLATFORMS |
| KUBE_CLIENT_PLATFORMS=( |
| "darwin/${host_arch}" |
| "linux/${host_arch}" |
| ) |
| readonly KUBE_CLIENT_PLATFORMS |
| else |
| KUBE_TEST_PLATFORMS=("linux/${host_arch}") |
| readonly KUBE_TEST_PLATFORMS |
| KUBE_CLIENT_PLATFORMS=("linux/${host_arch}") |
| readonly KUBE_CLIENT_PLATFORMS |
| fi |
| else |
| KUBE_SERVER_PLATFORMS=("${KUBE_SUPPORTED_SERVER_PLATFORMS[@]}") |
| readonly KUBE_SERVER_PLATFORMS |
| |
| KUBE_NODE_PLATFORMS=("${KUBE_SUPPORTED_NODE_PLATFORMS[@]}") |
| readonly KUBE_NODE_PLATFORMS |
| |
| KUBE_CLIENT_PLATFORMS=("${KUBE_SUPPORTED_CLIENT_PLATFORMS[@]}") |
| readonly KUBE_CLIENT_PLATFORMS |
| |
| KUBE_TEST_PLATFORMS=("${KUBE_SUPPORTED_TEST_PLATFORMS[@]}") |
| readonly KUBE_TEST_PLATFORMS |
| fi |
| } |
| |
| kube::golang::setup_platforms |
| |
| # The set of client targets that we are building for all platforms |
| readonly KUBE_CLIENT_TARGETS=( |
| cmd/kubectl |
| cmd/kubectl-convert |
| ) |
| readonly KUBE_CLIENT_BINARIES=("${KUBE_CLIENT_TARGETS[@]##*/}") |
| readonly KUBE_CLIENT_BINARIES_WIN=("${KUBE_CLIENT_BINARIES[@]/%/.exe}") |
| |
| # The set of test targets that we are building for all platforms |
| kube::golang::test_targets() { |
| local targets=( |
| ginkgo |
| test/e2e/e2e.test |
| test/conformance/image/go-runner |
| ) |
| echo "${targets[@]}" |
| } |
| IFS=" " read -ra KUBE_TEST_TARGETS <<< "$(kube::golang::test_targets)" |
| readonly KUBE_TEST_TARGETS |
| readonly KUBE_TEST_BINARIES=("${KUBE_TEST_TARGETS[@]##*/}") |
| readonly KUBE_TEST_BINARIES_WIN=("${KUBE_TEST_BINARIES[@]/%/.exe}") |
| readonly KUBE_TEST_PORTABLE=( |
| test/e2e/testing-manifests |
| test/kubemark |
| hack/e2e-internal |
| hack/get-build.sh |
| hack/ginkgo-e2e.sh |
| hack/lib |
| ) |
| |
| # Test targets which run on the Kubernetes clusters directly, so we only |
| # need to target server platforms. |
| # These binaries will be distributed in the kubernetes-test tarball. |
| kube::golang::server_test_targets() { |
| local targets=( |
| cmd/kubemark |
| ginkgo |
| ) |
| |
| if [[ "${OSTYPE:-}" == "linux"* ]]; then |
| targets+=( test/e2e_node/e2e_node.test ) |
| fi |
| |
| echo "${targets[@]}" |
| } |
| |
| IFS=" " read -ra KUBE_TEST_SERVER_TARGETS <<< "$(kube::golang::server_test_targets)" |
| readonly KUBE_TEST_SERVER_TARGETS |
| readonly KUBE_TEST_SERVER_BINARIES=("${KUBE_TEST_SERVER_TARGETS[@]##*/}") |
| readonly KUBE_TEST_SERVER_PLATFORMS=("${KUBE_SERVER_PLATFORMS[@]:+"${KUBE_SERVER_PLATFORMS[@]}"}") |
| |
| # Gigabytes necessary for parallel platform builds. |
| # As of March 2021 (go 1.16/amd64), the RSS usage is 2GiB by using cached |
| # memory of 15GiB. |
| # This variable can be overwritten at your own risk. |
| # It's defaulting to 20G to provide some headroom. |
| readonly KUBE_PARALLEL_BUILD_MEMORY=${KUBE_PARALLEL_BUILD_MEMORY:-20} |
| |
| readonly KUBE_ALL_TARGETS=( |
| "${KUBE_SERVER_TARGETS[@]}" |
| "${KUBE_CLIENT_TARGETS[@]}" |
| "${KUBE_TEST_TARGETS[@]}" |
| "${KUBE_TEST_SERVER_TARGETS[@]}" |
| ) |
| readonly KUBE_ALL_BINARIES=("${KUBE_ALL_TARGETS[@]##*/}") |
| |
| readonly KUBE_STATIC_BINARIES=( |
| apiextensions-apiserver |
| kube-aggregator |
| kube-apiserver |
| kube-controller-manager |
| kube-scheduler |
| kube-proxy |
| kube-log-runner |
| kubeadm |
| kubectl |
| kubectl-convert |
| kubemark |
| mounter |
| ) |
| |
| # Fully-qualified package names that we want to instrument for coverage information. |
| readonly KUBE_COVERAGE_INSTRUMENTED_PACKAGES=( |
| k8s.io/kubernetes/cmd/kube-apiserver |
| k8s.io/kubernetes/cmd/kube-controller-manager |
| k8s.io/kubernetes/cmd/kube-scheduler |
| k8s.io/kubernetes/cmd/kube-proxy |
| k8s.io/kubernetes/cmd/kubelet |
| ) |
| |
| # KUBE_CGO_OVERRIDES is a space-separated list of binaries which should be built |
| # with CGO enabled, assuming CGO is supported on the target platform. |
| # This overrides any entry in KUBE_STATIC_BINARIES. |
| IFS=" " read -ra KUBE_CGO_OVERRIDES_LIST <<< "${KUBE_CGO_OVERRIDES:-}" |
| readonly KUBE_CGO_OVERRIDES_LIST |
| # KUBE_STATIC_OVERRIDES is a space-separated list of binaries which should be |
| # built with CGO disabled. This is in addition to the list in |
| # KUBE_STATIC_BINARIES. |
| IFS=" " read -ra KUBE_STATIC_OVERRIDES_LIST <<< "${KUBE_STATIC_OVERRIDES:-}" |
| readonly KUBE_STATIC_OVERRIDES_LIST |
| |
| kube::golang::is_statically_linked() { |
| local e |
| # Explicitly enable cgo when building kubectl for darwin from darwin. |
| [[ "$(go env GOHOSTOS)" == "darwin" && "$(go env GOOS)" == "darwin" && |
| "$1" == *"/kubectl" ]] && return 1 |
| if [[ -n "${KUBE_CGO_OVERRIDES_LIST:+x}" ]]; then |
| for e in "${KUBE_CGO_OVERRIDES_LIST[@]}"; do [[ "${1}" == *"/${e}" ]] && return 1; done; |
| fi |
| for e in "${KUBE_STATIC_BINARIES[@]}"; do [[ "${1}" == *"/${e}" ]] && return 0; done; |
| if [[ -n "${KUBE_STATIC_OVERRIDES_LIST:+x}" ]]; then |
| for e in "${KUBE_STATIC_OVERRIDES_LIST[@]}"; do [[ "${1}" == *"/${e}" ]] && return 0; done; |
| fi |
| return 1; |
| } |
| |
| # kube::golang::best_guess_go_targets takes a list of build targets, which might |
| # be Go-style names (e.g. example.com/foo/bar or ./foo/bar) or just local paths |
| # (e.g. foo/bar) and produces a respective list (on stdout) of our best guess at |
| # Go target names. |
| kube::golang::best_guess_go_targets() { |
| local target |
| for target; do |
| if [ "${target}" = "ginkgo" ] || |
| [ "${target}" = "github.com/onsi/ginkgo/ginkgo" ] || |
| [ "${target}" = "vendor/github.com/onsi/ginkgo/ginkgo" ]; then |
| # Aliases that build the ginkgo CLI for hack/ginkgo-e2e.sh. |
| # "ginkgo" is the one that is documented in the Makefile. The others |
| # are for backwards compatibility. |
| echo "github.com/onsi/ginkgo/v2/ginkgo" |
| continue |
| fi |
| |
| if [[ "${target}" =~ ^([[:alnum:]]+".")+[[:alnum:]]+"/" ]]; then |
| # If the target starts with what looks like a domain name, assume it has a |
| # fully-qualified Go package name. |
| echo "${target}" |
| continue |
| fi |
| |
| if [[ "${target}" =~ ^vendor/ ]]; then |
| # Strip vendor/ prefix, since we're building in gomodule mode. This is |
| # for backwards compatibility. |
| echo "${target#"vendor/"}" |
| continue |
| fi |
| |
| # If the target starts with "./", assume it is a local path which qualifies |
| # as a Go target name. |
| if [[ "${target}" =~ ^\./ ]]; then |
| echo "${target}" |
| continue |
| fi |
| |
| # Otherwise assume it's a relative path (e.g. foo/bar or foo/bar/bar.test). |
| # We probably SHOULDN'T accept this, but we did in the past and it would be |
| # rude to break things if we don't NEED to. We can't really test if it |
| # exists or not, because the last element might be an output file (e.g. |
| # bar.test) or even "...". |
| echo "./${target}" |
| done |
| } |
| |
| # kube::golang::normalize_go_targets takes a list of build targets, which might |
| # be Go-style names (e.g. example.com/foo/bar or ./foo/bar) or just local paths |
| # (e.g. foo/bar) and produces a respective list (on stdout) of Go package |
| # names. |
| # |
| # If this cannot find (go list -find -e) one or more inputs, it will emit the |
| # them on stdout, so callers can at least get a useful error. |
| kube::golang::normalize_go_targets() { |
| local targets=() |
| kube::util::read-array targets < <(kube::golang::best_guess_go_targets "$@") |
| kube::util::read-array targets < <(kube::golang::dedup "${targets[@]}") |
| set -- "${targets[@]}" |
| |
| for target; do |
| if [[ "${target}" =~ ".test"$ ]]; then |
| local dir |
| dir="$(dirname "${target}")" |
| local tst |
| tst="$(basename "${target}")" |
| local pkg |
| pkg="$(go list -find -e "${dir}")" |
| echo "${pkg}/${tst}" |
| continue |
| fi |
| if [[ "${target}" =~ "/..."$ ]]; then |
| local dir |
| dir="$(dirname "${target}")" |
| local pkg |
| pkg="$(go list -find -e "${dir}")" |
| echo "${pkg}/..." |
| continue |
| fi |
| go list -find -e "${target}" |
| done |
| } |
| |
| # Asks golang what it thinks the host platform is. The go tool chain does some |
| # slightly different things when the target platform matches the host platform. |
| kube::golang::host_platform() { |
| echo "$(go env GOHOSTOS)/$(go env GOHOSTARCH)" |
| } |
| |
| # Takes the platform name ($1) and sets the appropriate golang env variables |
| # for that platform. |
| kube::golang::set_platform_envs() { |
| [[ -n ${1-} ]] || { |
| kube::log::error_exit "!!! Internal error. No platform set in kube::golang::set_platform_envs" |
| } |
| |
| export GOOS=${platform%/*} |
| export GOARCH=${platform##*/} |
| |
| # Do not set CC when building natively on a platform, only if cross-compiling |
| if [[ $(kube::golang::host_platform) != "$platform" ]]; then |
| # Dynamic CGO linking for other server architectures than host architecture goes here |
| # If you want to include support for more server platforms than these, add arch-specific gcc names here |
| case "${platform}" in |
| "linux/amd64") |
| export CGO_ENABLED=1 |
| export CC=${KUBE_LINUX_AMD64_CC:-x86_64-linux-gnu-gcc} |
| ;; |
| "linux/arm") |
| export CGO_ENABLED=1 |
| export CC=${KUBE_LINUX_ARM_CC:-arm-linux-gnueabihf-gcc} |
| ;; |
| "linux/arm64") |
| export CGO_ENABLED=1 |
| export CC=${KUBE_LINUX_ARM64_CC:-aarch64-linux-gnu-gcc} |
| ;; |
| "linux/ppc64le") |
| export CGO_ENABLED=1 |
| export CC=${KUBE_LINUX_PPC64LE_CC:-powerpc64le-linux-gnu-gcc} |
| ;; |
| "linux/s390x") |
| export CGO_ENABLED=1 |
| export CC=${KUBE_LINUX_S390X_CC:-s390x-linux-gnu-gcc} |
| ;; |
| esac |
| fi |
| |
| # if CC is defined for platform then always enable it |
| ccenv=$(echo "$platform" | awk -F/ '{print "KUBE_" toupper($1) "_" toupper($2) "_CC"}') |
| if [ -n "${!ccenv-}" ]; then |
| export CGO_ENABLED=1 |
| export CC="${!ccenv}" |
| fi |
| } |
| |
| # 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::internal::verify_go_version() { |
| # default GO_VERSION to content of .go-version |
| GO_VERSION="${GO_VERSION:-"$(cat "${KUBE_ROOT}/.go-version")"}" |
| if [ "${GOTOOLCHAIN:-auto}" != 'auto' ]; then |
| # no-op, just respect GOTOOLCHAIN |
| : |
| elif [ -n "${FORCE_HOST_GO:-}" ]; then |
| # ensure existing host version is used, like before GOTOOLCHAIN existed |
| export GOTOOLCHAIN='local' |
| else |
| # otherwise, we want to ensure the go version matches GO_VERSION |
| GOTOOLCHAIN="go${GO_VERSION}" |
| export GOTOOLCHAIN |
| # if go is either not installed or too old to respect GOTOOLCHAIN then use gimme |
| if ! (command -v go >/dev/null && [ "$(go version | cut -d' ' -f3)" = "${GOTOOLCHAIN}" ]); 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 |
| fi |
| |
| if [[ -z "$(command -v go)" ]]; then |
| kube::log::usage_from_stdin <<EOF |
| Can't find 'go' in PATH, please fix and retry. |
| See http://golang.org/doc/install for installation instructions. |
| EOF |
| return 2 |
| fi |
| |
| local go_version |
| IFS=" " read -ra go_version <<< "$(GOFLAGS='' go version)" |
| local minimum_go_version |
| minimum_go_version=go1.22 |
| if [[ "${minimum_go_version}" != $(echo -e "${minimum_go_version}\n${go_version[2]}" | sort -s -t. -k 1,1 -k 2,2n -k 3,3n | head -n1) && "${go_version[2]}" != "devel" ]]; then |
| kube::log::usage_from_stdin <<EOF |
| Detected go version: ${go_version[*]}. |
| Kubernetes requires ${minimum_go_version} or greater. |
| Please install ${minimum_go_version} or later. |
| EOF |
| return 2 |
| fi |
| } |
| |
| # kube::golang::setup_env will check that the `go` commands is available in |
| # ${PATH}. It will also check that the Go version is good enough for the |
| # Kubernetes build. |
| # |
| # Outputs: |
| # env-var GOPATH points to our local output dir |
| # env-var GOBIN is unset (we want binaries in a predictable place) |
| # env-var PATH includes the local GOPATH |
| kube::golang::setup_env() { |
| # Even in module mode, we need to set GOPATH for `go build` and `go install` |
| # to work. We build various tools (usually via `go install`) from a lot of |
| # scripts. |
| # * We can't just set GOBIN because that does not work on cross-compiles. |
| # * We could always use `go build -o <something>`, but it's subtle wrt |
| # cross-compiles and whether the <something> is a file or a directory, |
| # and EVERY caller has to get it *just* right. |
| # * We could leave GOPATH alone and let `go install` write binaries |
| # wherever the user's GOPATH says (or doesn't say). |
| # |
| # Instead we set it to a phony local path and process the results ourselves. |
| # In particular, GOPATH[0]/bin will be used for `go install`, with |
| # cross-compiles adding an extra directory under that. |
| export GOPATH="${KUBE_GOPATH}" |
| |
| # If these are not set, set them now. This ensures that any subsequent |
| # scripts we run (which may call this function again) use the same values. |
| export GOCACHE="${GOCACHE:-"${KUBE_GOPATH}/cache/build"}" |
| export GOMODCACHE="${GOMODCACHE:-"${KUBE_GOPATH}/cache/mod"}" |
| |
| # Make sure our own Go binaries are in PATH. |
| export PATH="${KUBE_GOPATH}/bin:${PATH}" |
| |
| # Unset GOBIN in case it already exists in the current session. |
| # Cross-compiles will not work with it set. |
| unset GOBIN |
| |
| # Turn on modules and workspaces (both are default-on). |
| unset GO111MODULE |
| unset GOWORK |
| |
| # This may try to download our specific Go version. Do it last so it uses |
| # the above-configured environment. |
| kube::golang::internal::verify_go_version |
| } |
| |
| kube::golang::setup_gomaxprocs() { |
| # GOMAXPROCS by default does not reflect the number of cpu(s) available |
| # when running in a container, please see https://github.com/golang/go/issues/33803 |
| if [[ -z "${GOMAXPROCS:-}" ]]; then |
| if ! command -v ncpu >/dev/null 2>&1; then |
| go -C "${KUBE_ROOT}/hack/tools" install ./ncpu || echo "Will not automatically set GOMAXPROCS" |
| fi |
| if command -v ncpu >/dev/null 2>&1; then |
| GOMAXPROCS=$(ncpu) |
| export GOMAXPROCS |
| kube::log::status "Set GOMAXPROCS automatically to ${GOMAXPROCS}" |
| fi |
| fi |
| } |
| |
| # This will take binaries from $GOPATH/bin and copy them to the appropriate |
| # place in ${KUBE_OUTPUT_BIN} |
| # |
| # Ideally this wouldn't be necessary and we could just set GOBIN to |
| # KUBE_OUTPUT_BIN but that won't work in the face of cross compilation. 'go |
| # install' will place binaries that match the host platform directly in $GOBIN |
| # while placing cross compiled binaries into `platform_arch` subdirs. This |
| # complicates pretty much everything else we do around packaging and such. |
| kube::golang::place_bins() { |
| local host_platform |
| host_platform=$(kube::golang::host_platform) |
| |
| V=2 kube::log::status "Placing binaries" |
| |
| local platform |
| for platform in "${KUBE_CLIENT_PLATFORMS[@]}"; do |
| # The substitution on platform_src below will replace all slashes with |
| # underscores. It'll transform darwin/amd64 -> darwin_amd64. |
| local platform_src="/${platform//\//_}" |
| if [[ "${platform}" == "${host_platform}" ]]; then |
| platform_src="" |
| rm -f "${THIS_PLATFORM_BIN}" |
| mkdir -p "$(dirname "${THIS_PLATFORM_BIN}")" |
| ln -s "${KUBE_OUTPUT_BIN}/${platform}" "${THIS_PLATFORM_BIN}" |
| fi |
| |
| local full_binpath_src="${KUBE_GOPATH}/bin${platform_src}" |
| if [[ -d "${full_binpath_src}" ]]; then |
| mkdir -p "${KUBE_OUTPUT_BIN}/${platform}" |
| find "${full_binpath_src}" -maxdepth 1 -type f -exec \ |
| rsync -pc {} "${KUBE_OUTPUT_BIN}/${platform}" \; |
| fi |
| done |
| } |
| |
| # Try and replicate the native binary placement of go install without |
| # calling go install. |
| kube::golang::outfile_for_binary() { |
| local binary=$1 |
| local platform=$2 |
| local output_path="${KUBE_GOPATH}/bin" |
| local bin |
| bin=$(basename "${binary}") |
| if [[ "${platform}" != "${host_platform}" ]]; then |
| output_path="${output_path}/${platform//\//_}" |
| fi |
| if [[ ${GOOS} == "windows" ]]; then |
| bin="${bin}.exe" |
| fi |
| echo "${output_path}/${bin}" |
| } |
| |
| # Argument: the name of a Kubernetes package. |
| # Returns 0 if the binary can be built with coverage, 1 otherwise. |
| # NB: this ignores whether coverage is globally enabled or not. |
| kube::golang::is_instrumented_package() { |
| if kube::util::array_contains "$1" "${KUBE_COVERAGE_INSTRUMENTED_PACKAGES[@]}"; then |
| return 0 |
| fi |
| # Some cases, like `make kubectl`, pass $1 as "./cmd/kubectl" rather than |
| # "k8s.io/kubernetes/kubectl". Try to normalize and handle that. We don't |
| # do this always because it is a bit slow. |
| pkg=$(go list -find "$1") |
| if kube::util::array_contains "${pkg}" "${KUBE_COVERAGE_INSTRUMENTED_PACKAGES[@]}"; then |
| return 0 |
| fi |
| return 1 |
| } |
| |
| # Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler) |
| # Echos the path to a dummy test used for coverage information. |
| kube::golang::path_for_coverage_dummy_test() { |
| local package="$1" |
| local path |
| path=$(go list -find -f '{{.Dir}}' "${package}") |
| local name |
| name=$(basename "${package}") |
| echo "${path}/zz_generated_${name}_test.go" |
| } |
| |
| # Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler). |
| # Creates a dummy unit test on disk in the source directory for the given package. |
| # This unit test will invoke the package's standard entry point when run. |
| kube::golang::create_coverage_dummy_test() { |
| local package="$1" |
| local name |
| name="$(basename "${package}")" |
| cat <<EOF > "$(kube::golang::path_for_coverage_dummy_test "${package}")" |
| package main |
| import ( |
| "testing" |
| "k8s.io/kubernetes/pkg/util/coverage" |
| ) |
| |
| func TestMain(m *testing.M) { |
| // Get coverage running |
| coverage.InitCoverage("${name}") |
| |
| // Go! |
| main() |
| |
| // Make sure we actually write the profiling information to disk, if we make it here. |
| // On long-running services, or anything that calls os.Exit(), this is insufficient, |
| // so we also flush periodically with a default period of five seconds (configurable by |
| // the KUBE_COVERAGE_FLUSH_INTERVAL environment variable). |
| coverage.FlushCoverage() |
| } |
| EOF |
| } |
| |
| # Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler). |
| # Deletes a test generated by kube::golang::create_coverage_dummy_test. |
| # It is not an error to call this for a nonexistent test. |
| kube::golang::delete_coverage_dummy_test() { |
| local package="$1" |
| rm -f "$(kube::golang::path_for_coverage_dummy_test "${package}")" |
| } |
| |
| # Arguments: a list of kubernetes packages to build. |
| # Expected variables: ${build_args} should be set to an array of Go build arguments. |
| # In addition, ${package} and ${platform} should have been set earlier, and if |
| # ${KUBE_BUILD_WITH_COVERAGE} is set, coverage instrumentation will be enabled. |
| # |
| # Invokes Go to actually build some packages. If coverage is disabled, simply invokes |
| # go install. If coverage is enabled, builds covered binaries using go test, temporarily |
| # producing the required unit test files and then cleaning up after itself. |
| # Non-covered binaries are then built using go install as usual. |
| # |
| # See comments in kube::golang::setup_env regarding where built binaries go. |
| kube::golang::build_some_binaries() { |
| if [[ -n "${KUBE_BUILD_WITH_COVERAGE:-}" ]]; then |
| local -a uncovered=() |
| for package in "$@"; do |
| if kube::golang::is_instrumented_package "${package}"; then |
| V=2 kube::log::info "Building ${package} with coverage..." |
| |
| kube::golang::create_coverage_dummy_test "${package}" |
| kube::util::trap_add "kube::golang::delete_coverage_dummy_test \"${package}\"" EXIT |
| |
| go test -c -o "$(kube::golang::outfile_for_binary "${package}" "${platform}")" \ |
| -covermode count \ |
| -coverpkg k8s.io/... \ |
| "${build_args[@]}" \ |
| -tags coverage \ |
| "${package}" |
| else |
| uncovered+=("${package}") |
| fi |
| done |
| if [[ "${#uncovered[@]}" != 0 ]]; then |
| V=2 kube::log::info "Building ${uncovered[*]} without coverage..." |
| GOPROXY=off go install "${build_args[@]}" "${uncovered[@]}" |
| else |
| V=2 kube::log::info "Nothing to build without coverage." |
| fi |
| else |
| V=2 kube::log::info "Coverage is disabled." |
| GOPROXY=off go install "${build_args[@]}" "$@" |
| fi |
| } |
| |
| # Args: |
| # $1: platform (e.g. darwin/amd64) |
| kube::golang::build_binaries_for_platform() { |
| # This is for sanity. Without it, user umasks can leak through. |
| umask 0022 |
| |
| local platform=$1 |
| |
| local -a statics=() |
| local -a nonstatics=() |
| local -a tests=() |
| |
| for binary in "${binaries[@]}"; do |
| if [[ "${binary}" =~ ".test"$ ]]; then |
| tests+=("${binary}") |
| kube::log::info " ${binary} (test)" |
| elif kube::golang::is_statically_linked "${binary}"; then |
| statics+=("${binary}") |
| kube::log::info " ${binary} (static)" |
| else |
| nonstatics+=("${binary}") |
| kube::log::info " ${binary} (non-static)" |
| fi |
| done |
| |
| V=2 kube::log::info "Env for ${platform}: GOOS=${GOOS-} GOARCH=${GOARCH-} GOROOT=${GOROOT-} CGO_ENABLED=${CGO_ENABLED-} CC=${CC-}" |
| V=3 kube::log::info "Building binaries with GCFLAGS=${gogcflags} LDFLAGS=${goldflags}" |
| |
| local -a build_args |
| if [[ "${#statics[@]}" != 0 ]]; then |
| build_args=( |
| -installsuffix=static |
| ${goflags:+"${goflags[@]}"} |
| -gcflags="${gogcflags}" |
| -ldflags="${goldflags}" |
| -tags="${gotags:-}" |
| ) |
| CGO_ENABLED=0 kube::golang::build_some_binaries "${statics[@]}" |
| fi |
| |
| if [[ "${#nonstatics[@]}" != 0 ]]; then |
| build_args=( |
| ${goflags:+"${goflags[@]}"} |
| -gcflags="${gogcflags}" |
| -ldflags="${goldflags}" |
| -tags="${gotags:-}" |
| ) |
| kube::golang::build_some_binaries "${nonstatics[@]}" |
| fi |
| |
| for test in "${tests[@]:+${tests[@]}}"; do |
| local outfile testpkg |
| outfile=$(kube::golang::outfile_for_binary "${test}" "${platform}") |
| testpkg=$(dirname "${test}") |
| |
| mkdir -p "$(dirname "${outfile}")" |
| go test -c \ |
| ${goflags:+"${goflags[@]}"} \ |
| -gcflags="${gogcflags}" \ |
| -ldflags="${goldflags}" \ |
| -tags="${gotags:-}" \ |
| -o "${outfile}" \ |
| "${testpkg}" |
| done |
| } |
| |
| # Return approximate physical memory available in gigabytes. |
| kube::golang::get_physmem() { |
| local mem |
| |
| # Linux kernel version >=3.14, in kb |
| if mem=$(grep MemAvailable /proc/meminfo | awk '{ print $2 }'); then |
| echo $(( mem / 1048576 )) |
| return |
| fi |
| |
| # Linux, in kb |
| if mem=$(grep MemTotal /proc/meminfo | awk '{ print $2 }'); then |
| echo $(( mem / 1048576 )) |
| return |
| fi |
| |
| # OS X, in bytes. Note that get_physmem, as used, should only ever |
| # run in a Linux container (because it's only used in the multiple |
| # platform case, which is a Dockerized build), but this is provided |
| # for completeness. |
| if mem=$(sysctl -n hw.memsize 2>/dev/null); then |
| echo $(( mem / 1073741824 )) |
| return |
| fi |
| |
| # If we can't infer it, just give up and assume a low memory system |
| echo 1 |
| } |
| |
| # Build binaries targets specified |
| # |
| # Input: |
| # $@ - targets and go flags. If no targets are set then all binaries targets |
| # are built. |
| # KUBE_BUILD_PLATFORMS - Incoming variable of targets to build for. If unset |
| # then just the host architecture is built. |
| kube::golang::build_binaries() { |
| # Create a sub-shell so that we don't pollute the outer environment |
| ( |
| # Check for `go` binary and set ${GOPATH}. |
| kube::golang::setup_env |
| V=2 kube::log::info "Go version: $(GOFLAGS='' go version)" |
| |
| local host_platform |
| host_platform=$(kube::golang::host_platform) |
| |
| # These are "local" but are visible to and relied on by functions this |
| # function calls. They are effectively part of the calling API to |
| # build_binaries_for_platform. |
| local goflags goldflags gogcflags gotags |
| |
| goflags=() |
| gogcflags="${GOGCFLAGS:-}" |
| goldflags="all=$(kube::version::ldflags) ${GOLDFLAGS:-}" |
| |
| if [[ "${DBG:-}" == 1 ]]; then |
| # Debugging - disable optimizations and inlining and trimPath |
| gogcflags="${gogcflags} all=-N -l" |
| else |
| # Not debugging - disable symbols and DWARF, trim embedded paths |
| goldflags="${goldflags} -s -w" |
| goflags+=("-trimpath") |
| fi |
| |
| # Extract tags if any specified in GOFLAGS |
| gotags="selinux,notest,$(echo "${GOFLAGS:-}" | sed -ne 's|.*-tags=\([^-]*\).*|\1|p')" |
| |
| local -a targets=() |
| local arg |
| |
| for arg; do |
| if [[ "${arg}" == -* ]]; then |
| # Assume arguments starting with a dash are flags to pass to go. |
| goflags+=("${arg}") |
| else |
| targets+=("${arg}") |
| fi |
| done |
| |
| local -a platforms |
| IFS=" " read -ra platforms <<< "${KUBE_BUILD_PLATFORMS:-}" |
| if [[ ${#platforms[@]} -eq 0 ]]; then |
| platforms=("${host_platform}") |
| fi |
| |
| if [[ ${#targets[@]} -eq 0 ]]; then |
| targets=("${KUBE_ALL_TARGETS[@]}") |
| fi |
| kube::util::read-array targets < <(kube::golang::dedup "${targets[@]}") |
| |
| local -a binaries |
| kube::util::read-array binaries < <(kube::golang::normalize_go_targets "${targets[@]}") |
| kube::util::read-array binaries < <(kube::golang::dedup "${binaries[@]}") |
| |
| local parallel=false |
| if [[ ${#platforms[@]} -gt 1 ]]; then |
| local gigs |
| gigs=$(kube::golang::get_physmem) |
| |
| if [[ ${gigs} -ge ${KUBE_PARALLEL_BUILD_MEMORY} ]]; then |
| kube::log::status "Multiple platforms requested and available ${gigs}G >= threshold ${KUBE_PARALLEL_BUILD_MEMORY}G, building platforms in parallel" |
| parallel=true |
| else |
| kube::log::status "Multiple platforms requested, but available ${gigs}G < threshold ${KUBE_PARALLEL_BUILD_MEMORY}G, building platforms in serial" |
| parallel=false |
| fi |
| fi |
| |
| if [[ "${parallel}" == "true" ]]; then |
| kube::log::status "Building go targets for {${platforms[*]}} in parallel (output will appear in a burst when complete):" "${targets[@]}" |
| local platform |
| for platform in "${platforms[@]}"; do ( |
| kube::golang::set_platform_envs "${platform}" |
| kube::log::status "${platform}: build started" |
| kube::golang::build_binaries_for_platform "${platform}" |
| kube::log::status "${platform}: build finished" |
| ) &> "/tmp//${platform//\//_}.build" & |
| done |
| |
| local fails=0 |
| for job in $(jobs -p); do |
| wait "${job}" || (( fails+=1 )) |
| done |
| |
| for platform in "${platforms[@]}"; do |
| cat "/tmp//${platform//\//_}.build" |
| done |
| |
| exit "${fails}" |
| else |
| for platform in "${platforms[@]}"; do |
| kube::log::status "Building go targets for ${platform}" |
| ( |
| kube::golang::set_platform_envs "${platform}" |
| kube::golang::build_binaries_for_platform "${platform}" |
| ) |
| done |
| fi |
| ) |
| } |