1#!/usr/bin/env bash
2
3# Copyright 2014 The Kubernetes Authors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17# shellcheck disable=SC2034 # Variables sourced in other scripts.
18
19readonly KUBE_GOPATH="${KUBE_GOPATH:-"${KUBE_OUTPUT}/go"}"
20export KUBE_GOPATH
21
22# The server platform we are building on.
23readonly KUBE_SUPPORTED_SERVER_PLATFORMS=(
24 linux/amd64
25 linux/arm64
26 linux/s390x
27 linux/ppc64le
28)
29
30# The node platforms we build for
31readonly KUBE_SUPPORTED_NODE_PLATFORMS=(
32 linux/amd64
33 linux/arm64
34 linux/s390x
35 linux/ppc64le
36 windows/amd64
37)
38
39# If we update this we should also update the set of platforms whose standard
40# library is precompiled for in build/build-image/cross/Dockerfile
41readonly KUBE_SUPPORTED_CLIENT_PLATFORMS=(
42 linux/amd64
43 linux/386
44 linux/arm
45 linux/arm64
46 linux/s390x
47 linux/ppc64le
48 darwin/amd64
49 darwin/arm64
50 windows/amd64
51 windows/386
52 windows/arm64
53)
54
55# Which platforms we should compile test targets for.
56# Not all client platforms need these tests
57readonly KUBE_SUPPORTED_TEST_PLATFORMS=(
58 linux/amd64
59 linux/arm64
60 linux/s390x
61 linux/ppc64le
62 darwin/amd64
63 darwin/arm64
64 windows/amd64
65 windows/arm64
66)
67
68# The set of server targets that we are only building for Linux
69kube::golang::server_targets() {
70 local targets=(
71 cmd/kube-proxy
72 cmd/kube-apiserver
73 cmd/kube-controller-manager
74 cmd/kubelet
75 cmd/kubeadm
76 cmd/kube-scheduler
77 staging/src/k8s.io/component-base/logs/kube-log-runner
78 staging/src/k8s.io/kube-aggregator
79 staging/src/k8s.io/apiextensions-apiserver
80 cluster/gce/gci/mounter
81 )
82 echo "${targets[@]}"
83}
84
85IFS=" " read -ra KUBE_SERVER_TARGETS <<< "$(kube::golang::server_targets)"
86readonly KUBE_SERVER_TARGETS
87readonly KUBE_SERVER_BINARIES=("${KUBE_SERVER_TARGETS[@]##*/}")
88
89# The set of server targets we build docker images for
90kube::golang::server_image_targets() {
91 # NOTE: this contains cmd targets for kube::build::get_docker_wrapped_binaries
92 local targets=(
93 cmd/kube-apiserver
94 cmd/kube-controller-manager
95 cmd/kube-scheduler
96 cmd/kube-proxy
97 cmd/kubectl
98 )
99 echo "${targets[@]}"
100}
101
102IFS=" " read -ra KUBE_SERVER_IMAGE_TARGETS <<< "$(kube::golang::server_image_targets)"
103readonly KUBE_SERVER_IMAGE_TARGETS
104readonly KUBE_SERVER_IMAGE_BINARIES=("${KUBE_SERVER_IMAGE_TARGETS[@]##*/}")
105
106# The set of conformance targets we build docker image for
107kube::golang::conformance_image_targets() {
108 # NOTE: this contains cmd targets for kube::release::build_conformance_image
109 local targets=(
110 ginkgo
111 test/e2e/e2e.test
112 test/conformance/image/go-runner
113 cmd/kubectl
114 )
115 echo "${targets[@]}"
116}
117
118IFS=" " read -ra KUBE_CONFORMANCE_IMAGE_TARGETS <<< "$(kube::golang::conformance_image_targets)"
119readonly KUBE_CONFORMANCE_IMAGE_TARGETS
120
121# The set of server targets that we are only building for Kubernetes nodes
122kube::golang::node_targets() {
123 local targets=(
124 cmd/kube-proxy
125 cmd/kubeadm
126 cmd/kubelet
127 staging/src/k8s.io/component-base/logs/kube-log-runner
128 )
129 echo "${targets[@]}"
130}
131
132IFS=" " read -ra KUBE_NODE_TARGETS <<< "$(kube::golang::node_targets)"
133readonly KUBE_NODE_TARGETS
134readonly KUBE_NODE_BINARIES=("${KUBE_NODE_TARGETS[@]##*/}")
135readonly KUBE_NODE_BINARIES_WIN=("${KUBE_NODE_BINARIES[@]/%/.exe}")
136
137# ------------
138# NOTE: All functions that return lists should use newlines.
139# bash functions can't return arrays, and spaces are tricky, so newline
140# separators are the preferred pattern.
141# To transform a string of newline-separated items to an array, use kube::util::read-array:
142# kube::util::read-array FOO < <(kube::golang::dups a b c a)
143#
144# ALWAYS remember to quote your subshells. Not doing so will break in
145# bash 4.3, and potentially cause other issues.
146# ------------
147
148# Returns a sorted newline-separated list containing only duplicated items.
149kube::golang::dups() {
150 # We use printf to insert newlines, which are required by sort.
151 printf "%s\n" "$@" | sort | uniq -d
152}
153
154# Returns a sorted newline-separated list with duplicated items removed.
155kube::golang::dedup() {
156 # We use printf to insert newlines, which are required by sort.
157 printf "%s\n" "$@" | sort -u
158}
159
160# Depends on values of user-facing KUBE_BUILD_PLATFORMS, KUBE_FASTBUILD,
161# and KUBE_BUILDER_OS.
162# Configures KUBE_SERVER_PLATFORMS, KUBE_NODE_PLATFOMRS,
163# KUBE_TEST_PLATFORMS, and KUBE_CLIENT_PLATFORMS, then sets them
164# to readonly.
165# The configured vars will only contain platforms allowed by the
166# KUBE_SUPPORTED* vars at the top of this file.
167declare -a KUBE_SERVER_PLATFORMS
168declare -a KUBE_CLIENT_PLATFORMS
169declare -a KUBE_NODE_PLATFORMS
170declare -a KUBE_TEST_PLATFORMS
171kube::golang::setup_platforms() {
172 if [[ -n "${KUBE_BUILD_PLATFORMS:-}" ]]; then
173 # KUBE_BUILD_PLATFORMS needs to be read into an array before the next
174 # step, or quoting treats it all as one element.
175 local -a platforms
176 IFS=" " read -ra platforms <<< "${KUBE_BUILD_PLATFORMS}"
177
178 # Deduplicate to ensure the intersection trick with kube::golang::dups
179 # is not defeated by duplicates in user input.
180 kube::util::read-array platforms < <(kube::golang::dedup "${platforms[@]}")
181
182 # Use kube::golang::dups to restrict the builds to the platforms in
183 # KUBE_SUPPORTED_*_PLATFORMS. Items should only appear at most once in each
184 # set, so if they appear twice after the merge they are in the intersection.
185 kube::util::read-array KUBE_SERVER_PLATFORMS < <(kube::golang::dups \
186 "${platforms[@]}" \
187 "${KUBE_SUPPORTED_SERVER_PLATFORMS[@]}" \
188 )
189 readonly KUBE_SERVER_PLATFORMS
190
191 kube::util::read-array KUBE_NODE_PLATFORMS < <(kube::golang::dups \
192 "${platforms[@]}" \
193 "${KUBE_SUPPORTED_NODE_PLATFORMS[@]}" \
194 )
195 readonly KUBE_NODE_PLATFORMS
196
197 kube::util::read-array KUBE_TEST_PLATFORMS < <(kube::golang::dups \
198 "${platforms[@]}" \
199 "${KUBE_SUPPORTED_TEST_PLATFORMS[@]}" \
200 )
201 readonly KUBE_TEST_PLATFORMS
202
203 kube::util::read-array KUBE_CLIENT_PLATFORMS < <(kube::golang::dups \
204 "${platforms[@]}" \
205 "${KUBE_SUPPORTED_CLIENT_PLATFORMS[@]}" \
206 )
207 readonly KUBE_CLIENT_PLATFORMS
208
209 elif [[ "${KUBE_FASTBUILD:-}" == "true" ]]; then
210 host_arch=$(kube::util::host_arch)
211 if [[ "${host_arch}" != "amd64" && "${host_arch}" != "arm64" && "${host_arch}" != "ppc64le" && "${host_arch}" != "s390x" ]]; then
212 # on any platform other than amd64, arm64, ppc64le and s390x, we just default to amd64
213 host_arch="amd64"
214 fi
215 KUBE_SERVER_PLATFORMS=("linux/${host_arch}")
216 readonly KUBE_SERVER_PLATFORMS
217 KUBE_NODE_PLATFORMS=("linux/${host_arch}")
218 readonly KUBE_NODE_PLATFORMS
219 if [[ "${KUBE_BUILDER_OS:-}" == "darwin"* ]]; then
220 KUBE_TEST_PLATFORMS=(
221 "darwin/${host_arch}"
222 "linux/${host_arch}"
223 )
224 readonly KUBE_TEST_PLATFORMS
225 KUBE_CLIENT_PLATFORMS=(
226 "darwin/${host_arch}"
227 "linux/${host_arch}"
228 )
229 readonly KUBE_CLIENT_PLATFORMS
230 else
231 KUBE_TEST_PLATFORMS=("linux/${host_arch}")
232 readonly KUBE_TEST_PLATFORMS
233 KUBE_CLIENT_PLATFORMS=("linux/${host_arch}")
234 readonly KUBE_CLIENT_PLATFORMS
235 fi
236 else
237 KUBE_SERVER_PLATFORMS=("${KUBE_SUPPORTED_SERVER_PLATFORMS[@]}")
238 readonly KUBE_SERVER_PLATFORMS
239
240 KUBE_NODE_PLATFORMS=("${KUBE_SUPPORTED_NODE_PLATFORMS[@]}")
241 readonly KUBE_NODE_PLATFORMS
242
243 KUBE_CLIENT_PLATFORMS=("${KUBE_SUPPORTED_CLIENT_PLATFORMS[@]}")
244 readonly KUBE_CLIENT_PLATFORMS
245
246 KUBE_TEST_PLATFORMS=("${KUBE_SUPPORTED_TEST_PLATFORMS[@]}")
247 readonly KUBE_TEST_PLATFORMS
248 fi
249}
250
251kube::golang::setup_platforms
252
253# The set of client targets that we are building for all platforms
254readonly KUBE_CLIENT_TARGETS=(
255 cmd/kubectl
256 cmd/kubectl-convert
257)
258readonly KUBE_CLIENT_BINARIES=("${KUBE_CLIENT_TARGETS[@]##*/}")
259readonly KUBE_CLIENT_BINARIES_WIN=("${KUBE_CLIENT_BINARIES[@]/%/.exe}")
260
261# The set of test targets that we are building for all platforms
262kube::golang::test_targets() {
263 local targets=(
264 ginkgo
265 test/e2e/e2e.test
266 test/conformance/image/go-runner
267 )
268 echo "${targets[@]}"
269}
270IFS=" " read -ra KUBE_TEST_TARGETS <<< "$(kube::golang::test_targets)"
271readonly KUBE_TEST_TARGETS
272readonly KUBE_TEST_BINARIES=("${KUBE_TEST_TARGETS[@]##*/}")
273readonly KUBE_TEST_BINARIES_WIN=("${KUBE_TEST_BINARIES[@]/%/.exe}")
274readonly KUBE_TEST_PORTABLE=(
275 test/e2e/testing-manifests
276 test/kubemark
277 hack/e2e-internal
278 hack/get-build.sh
279 hack/ginkgo-e2e.sh
280 hack/lib
281)
282
283# Test targets which run on the Kubernetes clusters directly, so we only
284# need to target server platforms.
285# These binaries will be distributed in the kubernetes-test tarball.
286kube::golang::server_test_targets() {
287 local targets=(
288 cmd/kubemark
289 ginkgo
290 )
291
292 if [[ "${OSTYPE:-}" == "linux"* ]]; then
293 targets+=( test/e2e_node/e2e_node.test )
294 fi
295
296 echo "${targets[@]}"
297}
298
299IFS=" " read -ra KUBE_TEST_SERVER_TARGETS <<< "$(kube::golang::server_test_targets)"
300readonly KUBE_TEST_SERVER_TARGETS
301readonly KUBE_TEST_SERVER_BINARIES=("${KUBE_TEST_SERVER_TARGETS[@]##*/}")
302readonly KUBE_TEST_SERVER_PLATFORMS=("${KUBE_SERVER_PLATFORMS[@]:+"${KUBE_SERVER_PLATFORMS[@]}"}")
303
304# Gigabytes necessary for parallel platform builds.
305# As of March 2021 (go 1.16/amd64), the RSS usage is 2GiB by using cached
306# memory of 15GiB.
307# This variable can be overwritten at your own risk.
308# It's defaulting to 20G to provide some headroom.
309readonly KUBE_PARALLEL_BUILD_MEMORY=${KUBE_PARALLEL_BUILD_MEMORY:-20}
310
311readonly KUBE_ALL_TARGETS=(
312 "${KUBE_SERVER_TARGETS[@]}"
313 "${KUBE_CLIENT_TARGETS[@]}"
314 "${KUBE_TEST_TARGETS[@]}"
315 "${KUBE_TEST_SERVER_TARGETS[@]}"
316)
317readonly KUBE_ALL_BINARIES=("${KUBE_ALL_TARGETS[@]##*/}")
318
319readonly KUBE_STATIC_BINARIES=(
320 apiextensions-apiserver
321 kube-aggregator
322 kube-apiserver
323 kube-controller-manager
324 kube-scheduler
325 kube-proxy
326 kube-log-runner
327 kubeadm
328 kubectl
329 kubectl-convert
330 kubemark
331 mounter
332)
333
334# Fully-qualified package names that we want to instrument for coverage information.
335readonly KUBE_COVERAGE_INSTRUMENTED_PACKAGES=(
336 k8s.io/kubernetes/cmd/kube-apiserver
337 k8s.io/kubernetes/cmd/kube-controller-manager
338 k8s.io/kubernetes/cmd/kube-scheduler
339 k8s.io/kubernetes/cmd/kube-proxy
340 k8s.io/kubernetes/cmd/kubelet
341)
342
343# KUBE_CGO_OVERRIDES is a space-separated list of binaries which should be built
344# with CGO enabled, assuming CGO is supported on the target platform.
345# This overrides any entry in KUBE_STATIC_BINARIES.
346IFS=" " read -ra KUBE_CGO_OVERRIDES_LIST <<< "${KUBE_CGO_OVERRIDES:-}"
347readonly KUBE_CGO_OVERRIDES_LIST
348# KUBE_STATIC_OVERRIDES is a space-separated list of binaries which should be
349# built with CGO disabled. This is in addition to the list in
350# KUBE_STATIC_BINARIES.
351IFS=" " read -ra KUBE_STATIC_OVERRIDES_LIST <<< "${KUBE_STATIC_OVERRIDES:-}"
352readonly KUBE_STATIC_OVERRIDES_LIST
353
354kube::golang::is_statically_linked() {
355 local e
356 # Explicitly enable cgo when building kubectl for darwin from darwin.
357 [[ "$(go env GOHOSTOS)" == "darwin" && "$(go env GOOS)" == "darwin" &&
358 "$1" == *"/kubectl" ]] && return 1
359 if [[ -n "${KUBE_CGO_OVERRIDES_LIST:+x}" ]]; then
360 for e in "${KUBE_CGO_OVERRIDES_LIST[@]}"; do [[ "${1}" == *"/${e}" ]] && return 1; done;
361 fi
362 for e in "${KUBE_STATIC_BINARIES[@]}"; do [[ "${1}" == *"/${e}" ]] && return 0; done;
363 if [[ -n "${KUBE_STATIC_OVERRIDES_LIST:+x}" ]]; then
364 for e in "${KUBE_STATIC_OVERRIDES_LIST[@]}"; do [[ "${1}" == *"/${e}" ]] && return 0; done;
365 fi
366 return 1;
367}
368
369# kube::golang::best_guess_go_targets takes a list of build targets, which might
370# be Go-style names (e.g. example.com/foo/bar or ./foo/bar) or just local paths
371# (e.g. foo/bar) and produces a respective list (on stdout) of our best guess at
372# Go target names.
373kube::golang::best_guess_go_targets() {
374 local target
375 for target; do
376 if [ "${target}" = "ginkgo" ] ||
377 [ "${target}" = "github.com/onsi/ginkgo/ginkgo" ] ||
378 [ "${target}" = "vendor/github.com/onsi/ginkgo/ginkgo" ]; then
379 # Aliases that build the ginkgo CLI for hack/ginkgo-e2e.sh.
380 # "ginkgo" is the one that is documented in the Makefile. The others
381 # are for backwards compatibility.
382 echo "github.com/onsi/ginkgo/v2/ginkgo"
383 continue
384 fi
385
386 if [[ "${target}" =~ ^([[:alnum:]]+".")+[[:alnum:]]+"/" ]]; then
387 # If the target starts with what looks like a domain name, assume it has a
388 # fully-qualified Go package name.
389 echo "${target}"
390 continue
391 fi
392
393 if [[ "${target}" =~ ^vendor/ ]]; then
394 # Strip vendor/ prefix, since we're building in gomodule mode. This is
395 # for backwards compatibility.
396 echo "${target#"vendor/"}"
397 continue
398 fi
399
400 # If the target starts with "./", assume it is a local path which qualifies
401 # as a Go target name.
402 if [[ "${target}" =~ ^\./ ]]; then
403 echo "${target}"
404 continue
405 fi
406
407 # Otherwise assume it's a relative path (e.g. foo/bar or foo/bar/bar.test).
408 # We probably SHOULDN'T accept this, but we did in the past and it would be
409 # rude to break things if we don't NEED to. We can't really test if it
410 # exists or not, because the last element might be an output file (e.g.
411 # bar.test) or even "...".
412 echo "./${target}"
413 done
414}
415
416# kube::golang::normalize_go_targets takes a list of build targets, which might
417# be Go-style names (e.g. example.com/foo/bar or ./foo/bar) or just local paths
418# (e.g. foo/bar) and produces a respective list (on stdout) of Go package
419# names.
420#
421# If this cannot find (go list -find -e) one or more inputs, it will emit the
422# them on stdout, so callers can at least get a useful error.
423kube::golang::normalize_go_targets() {
424 local targets=()
425 kube::util::read-array targets < <(kube::golang::best_guess_go_targets "$@")
426 kube::util::read-array targets < <(kube::golang::dedup "${targets[@]}")
427 set -- "${targets[@]}"
428
429 for target; do
430 if [[ "${target}" =~ ".test"$ ]]; then
431 local dir
432 dir="$(dirname "${target}")"
433 local tst
434 tst="$(basename "${target}")"
435 local pkg
436 pkg="$(go list -find -e "${dir}")"
437 echo "${pkg}/${tst}"
438 continue
439 fi
440 if [[ "${target}" =~ "/..."$ ]]; then
441 local dir
442 dir="$(dirname "${target}")"
443 local pkg
444 pkg="$(go list -find -e "${dir}")"
445 echo "${pkg}/..."
446 continue
447 fi
448 go list -find -e "${target}"
449 done
450}
451
452# Asks golang what it thinks the host platform is. The go tool chain does some
453# slightly different things when the target platform matches the host platform.
454kube::golang::host_platform() {
455 echo "$(go env GOHOSTOS)/$(go env GOHOSTARCH)"
456}
457
458# Takes the platform name ($1) and sets the appropriate golang env variables
459# for that platform.
460kube::golang::set_platform_envs() {
461 [[ -n ${1-} ]] || {
462 kube::log::error_exit "!!! Internal error. No platform set in kube::golang::set_platform_envs"
463 }
464
465 export GOOS=${platform%/*}
466 export GOARCH=${platform##*/}
467
468 # Do not set CC when building natively on a platform, only if cross-compiling
469 if [[ $(kube::golang::host_platform) != "$platform" ]]; then
470 # Dynamic CGO linking for other server architectures than host architecture goes here
471 # If you want to include support for more server platforms than these, add arch-specific gcc names here
472 case "${platform}" in
473 "linux/amd64")
474 export CGO_ENABLED=1
475 export CC=${KUBE_LINUX_AMD64_CC:-x86_64-linux-gnu-gcc}
476 ;;
477 "linux/arm")
478 export CGO_ENABLED=1
479 export CC=${KUBE_LINUX_ARM_CC:-arm-linux-gnueabihf-gcc}
480 ;;
481 "linux/arm64")
482 export CGO_ENABLED=1
483 export CC=${KUBE_LINUX_ARM64_CC:-aarch64-linux-gnu-gcc}
484 ;;
485 "linux/ppc64le")
486 export CGO_ENABLED=1
487 export CC=${KUBE_LINUX_PPC64LE_CC:-powerpc64le-linux-gnu-gcc}
488 ;;
489 "linux/s390x")
490 export CGO_ENABLED=1
491 export CC=${KUBE_LINUX_S390X_CC:-s390x-linux-gnu-gcc}
492 ;;
493 esac
494 fi
495
496 # if CC is defined for platform then always enable it
497 ccenv=$(echo "$platform" | awk -F/ '{print "KUBE_" toupper($1) "_" toupper($2) "_CC"}')
498 if [ -n "${!ccenv-}" ]; then
499 export CGO_ENABLED=1
500 export CC="${!ccenv}"
501 fi
502}
503
504# Ensure the go tool exists and is a viable version.
505# Inputs:
506# env-var GO_VERSION is the desired go version to use, downloading it if needed (defaults to content of .go-version)
507# 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
508kube::golang::internal::verify_go_version() {
509 # default GO_VERSION to content of .go-version
510 GO_VERSION="${GO_VERSION:-"$(cat "${KUBE_ROOT}/.go-version")"}"
511 if [ "${GOTOOLCHAIN:-auto}" != 'auto' ]; then
512 # no-op, just respect GOTOOLCHAIN
513 :
514 elif [ -n "${FORCE_HOST_GO:-}" ]; then
515 # ensure existing host version is used, like before GOTOOLCHAIN existed
516 export GOTOOLCHAIN='local'
517 else
518 # otherwise, we want to ensure the go version matches GO_VERSION
519 GOTOOLCHAIN="go${GO_VERSION}"
520 export GOTOOLCHAIN
521 # if go is either not installed or too old to respect GOTOOLCHAIN then use gimme
522 if ! (command -v go >/dev/null && [ "$(go version | cut -d' ' -f3)" = "${GOTOOLCHAIN}" ]); then
523 export GIMME_ENV_PREFIX=${GIMME_ENV_PREFIX:-"${KUBE_OUTPUT}/.gimme/envs"}
524 export GIMME_VERSION_PREFIX=${GIMME_VERSION_PREFIX:-"${KUBE_OUTPUT}/.gimme/versions"}
525 # eval because the output of this is shell to set PATH etc.
526 eval "$("${KUBE_ROOT}/third_party/gimme/gimme" "${GO_VERSION}")"
527 fi
528 fi
529
530 if [[ -z "$(command -v go)" ]]; then
531 kube::log::usage_from_stdin <<EOF
532Can't find 'go' in PATH, please fix and retry.
533See http://golang.org/doc/install for installation instructions.
534EOF
535 return 2
536 fi
537
538 local go_version
539 IFS=" " read -ra go_version <<< "$(GOFLAGS='' go version)"
540 local minimum_go_version
541 minimum_go_version=go1.22
542 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
543 kube::log::usage_from_stdin <<EOF
544Detected go version: ${go_version[*]}.
545Kubernetes requires ${minimum_go_version} or greater.
546Please install ${minimum_go_version} or later.
547EOF
548 return 2
549 fi
550}
551
552# kube::golang::setup_env will check that the `go` commands is available in
553# ${PATH}. It will also check that the Go version is good enough for the
554# Kubernetes build.
555#
556# Outputs:
557# env-var GOPATH points to our local output dir
558# env-var GOBIN is unset (we want binaries in a predictable place)
559# env-var PATH includes the local GOPATH
560kube::golang::setup_env() {
561 # Even in module mode, we need to set GOPATH for `go build` and `go install`
562 # to work. We build various tools (usually via `go install`) from a lot of
563 # scripts.
564 # * We can't just set GOBIN because that does not work on cross-compiles.
565 # * We could always use `go build -o <something>`, but it's subtle wrt
566 # cross-compiles and whether the <something> is a file or a directory,
567 # and EVERY caller has to get it *just* right.
568 # * We could leave GOPATH alone and let `go install` write binaries
569 # wherever the user's GOPATH says (or doesn't say).
570 #
571 # Instead we set it to a phony local path and process the results ourselves.
572 # In particular, GOPATH[0]/bin will be used for `go install`, with
573 # cross-compiles adding an extra directory under that.
574 export GOPATH="${KUBE_GOPATH}"
575
576 # If these are not set, set them now. This ensures that any subsequent
577 # scripts we run (which may call this function again) use the same values.
578 export GOCACHE="${GOCACHE:-"${KUBE_GOPATH}/cache/build"}"
579 export GOMODCACHE="${GOMODCACHE:-"${KUBE_GOPATH}/cache/mod"}"
580
581 # Make sure our own Go binaries are in PATH.
582 export PATH="${KUBE_GOPATH}/bin:${PATH}"
583
584 # Unset GOBIN in case it already exists in the current session.
585 # Cross-compiles will not work with it set.
586 unset GOBIN
587
588 # Turn on modules and workspaces (both are default-on).
589 unset GO111MODULE
590 unset GOWORK
591
592 # This may try to download our specific Go version. Do it last so it uses
593 # the above-configured environment.
594 kube::golang::internal::verify_go_version
595}
596
597kube::golang::setup_gomaxprocs() {
598 # GOMAXPROCS by default does not reflect the number of cpu(s) available
599 # when running in a container, please see https://github.com/golang/go/issues/33803
600 if [[ -z "${GOMAXPROCS:-}" ]]; then
601 if ! command -v ncpu >/dev/null 2>&1; then
602 go -C "${KUBE_ROOT}/hack/tools" install ./ncpu || echo "Will not automatically set GOMAXPROCS"
603 fi
604 if command -v ncpu >/dev/null 2>&1; then
605 GOMAXPROCS=$(ncpu)
606 export GOMAXPROCS
607 kube::log::status "Set GOMAXPROCS automatically to ${GOMAXPROCS}"
608 fi
609 fi
610}
611
612# This will take binaries from $GOPATH/bin and copy them to the appropriate
613# place in ${KUBE_OUTPUT_BIN}
614#
615# Ideally this wouldn't be necessary and we could just set GOBIN to
616# KUBE_OUTPUT_BIN but that won't work in the face of cross compilation. 'go
617# install' will place binaries that match the host platform directly in $GOBIN
618# while placing cross compiled binaries into `platform_arch` subdirs. This
619# complicates pretty much everything else we do around packaging and such.
620kube::golang::place_bins() {
621 local host_platform
622 host_platform=$(kube::golang::host_platform)
623
624 V=2 kube::log::status "Placing binaries"
625
626 local platform
627 for platform in "${KUBE_CLIENT_PLATFORMS[@]}"; do
628 # The substitution on platform_src below will replace all slashes with
629 # underscores. It'll transform darwin/amd64 -> darwin_amd64.
630 local platform_src="/${platform//\//_}"
631 if [[ "${platform}" == "${host_platform}" ]]; then
632 platform_src=""
633 rm -f "${THIS_PLATFORM_BIN}"
634 mkdir -p "$(dirname "${THIS_PLATFORM_BIN}")"
635 ln -s "${KUBE_OUTPUT_BIN}/${platform}" "${THIS_PLATFORM_BIN}"
636 fi
637
638 local full_binpath_src="${KUBE_GOPATH}/bin${platform_src}"
639 if [[ -d "${full_binpath_src}" ]]; then
640 mkdir -p "${KUBE_OUTPUT_BIN}/${platform}"
641 find "${full_binpath_src}" -maxdepth 1 -type f -exec \
642 rsync -pc {} "${KUBE_OUTPUT_BIN}/${platform}" \;
643 fi
644 done
645}
646
647# Try and replicate the native binary placement of go install without
648# calling go install.
649kube::golang::outfile_for_binary() {
650 local binary=$1
651 local platform=$2
652 local output_path="${KUBE_GOPATH}/bin"
653 local bin
654 bin=$(basename "${binary}")
655 if [[ "${platform}" != "${host_platform}" ]]; then
656 output_path="${output_path}/${platform//\//_}"
657 fi
658 if [[ ${GOOS} == "windows" ]]; then
659 bin="${bin}.exe"
660 fi
661 echo "${output_path}/${bin}"
662}
663
664# Argument: the name of a Kubernetes package.
665# Returns 0 if the binary can be built with coverage, 1 otherwise.
666# NB: this ignores whether coverage is globally enabled or not.
667kube::golang::is_instrumented_package() {
668 if kube::util::array_contains "$1" "${KUBE_COVERAGE_INSTRUMENTED_PACKAGES[@]}"; then
669 return 0
670 fi
671 # Some cases, like `make kubectl`, pass $1 as "./cmd/kubectl" rather than
672 # "k8s.io/kubernetes/kubectl". Try to normalize and handle that. We don't
673 # do this always because it is a bit slow.
674 pkg=$(go list -find "$1")
675 if kube::util::array_contains "${pkg}" "${KUBE_COVERAGE_INSTRUMENTED_PACKAGES[@]}"; then
676 return 0
677 fi
678 return 1
679}
680
681# Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler)
682# Echos the path to a dummy test used for coverage information.
683kube::golang::path_for_coverage_dummy_test() {
684 local package="$1"
685 local path
686 path=$(go list -find -f '{{.Dir}}' "${package}")
687 local name
688 name=$(basename "${package}")
689 echo "${path}/zz_generated_${name}_test.go"
690}
691
692# Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler).
693# Creates a dummy unit test on disk in the source directory for the given package.
694# This unit test will invoke the package's standard entry point when run.
695kube::golang::create_coverage_dummy_test() {
696 local package="$1"
697 local name
698 name="$(basename "${package}")"
699 cat <<EOF > "$(kube::golang::path_for_coverage_dummy_test "${package}")"
700package main
701import (
702 "testing"
703 "k8s.io/kubernetes/pkg/util/coverage"
704)
705
706func TestMain(m *testing.M) {
707 // Get coverage running
708 coverage.InitCoverage("${name}")
709
710 // Go!
711 main()
712
713 // Make sure we actually write the profiling information to disk, if we make it here.
714 // On long-running services, or anything that calls os.Exit(), this is insufficient,
715 // so we also flush periodically with a default period of five seconds (configurable by
716 // the KUBE_COVERAGE_FLUSH_INTERVAL environment variable).
717 coverage.FlushCoverage()
718}
719EOF
720}
721
722# Argument: the name of a Kubernetes package (e.g. k8s.io/kubernetes/cmd/kube-scheduler).
723# Deletes a test generated by kube::golang::create_coverage_dummy_test.
724# It is not an error to call this for a nonexistent test.
725kube::golang::delete_coverage_dummy_test() {
726 local package="$1"
727 rm -f "$(kube::golang::path_for_coverage_dummy_test "${package}")"
728}
729
730# Arguments: a list of kubernetes packages to build.
731# Expected variables: ${build_args} should be set to an array of Go build arguments.
732# In addition, ${package} and ${platform} should have been set earlier, and if
733# ${KUBE_BUILD_WITH_COVERAGE} is set, coverage instrumentation will be enabled.
734#
735# Invokes Go to actually build some packages. If coverage is disabled, simply invokes
736# go install. If coverage is enabled, builds covered binaries using go test, temporarily
737# producing the required unit test files and then cleaning up after itself.
738# Non-covered binaries are then built using go install as usual.
739#
740# See comments in kube::golang::setup_env regarding where built binaries go.
741kube::golang::build_some_binaries() {
742 if [[ -n "${KUBE_BUILD_WITH_COVERAGE:-}" ]]; then
743 local -a uncovered=()
744 for package in "$@"; do
745 if kube::golang::is_instrumented_package "${package}"; then
746 V=2 kube::log::info "Building ${package} with coverage..."
747
748 kube::golang::create_coverage_dummy_test "${package}"
749 kube::util::trap_add "kube::golang::delete_coverage_dummy_test \"${package}\"" EXIT
750
751 go test -c -o "$(kube::golang::outfile_for_binary "${package}" "${platform}")" \
752 -covermode count \
753 -coverpkg k8s.io/... \
754 "${build_args[@]}" \
755 -tags coverage \
756 "${package}"
757 else
758 uncovered+=("${package}")
759 fi
760 done
761 if [[ "${#uncovered[@]}" != 0 ]]; then
762 V=2 kube::log::info "Building ${uncovered[*]} without coverage..."
763 GOPROXY=off go install "${build_args[@]}" "${uncovered[@]}"
764 else
765 V=2 kube::log::info "Nothing to build without coverage."
766 fi
767 else
768 V=2 kube::log::info "Coverage is disabled."
769 GOPROXY=off go install "${build_args[@]}" "$@"
770 fi
771}
772
773# Args:
774# $1: platform (e.g. darwin/amd64)
775kube::golang::build_binaries_for_platform() {
776 # This is for sanity. Without it, user umasks can leak through.
777 umask 0022
778
779 local platform=$1
780
781 local -a statics=()
782 local -a nonstatics=()
783 local -a tests=()
784
785 for binary in "${binaries[@]}"; do
786 if [[ "${binary}" =~ ".test"$ ]]; then
787 tests+=("${binary}")
788 kube::log::info " ${binary} (test)"
789 elif kube::golang::is_statically_linked "${binary}"; then
790 statics+=("${binary}")
791 kube::log::info " ${binary} (static)"
792 else
793 nonstatics+=("${binary}")
794 kube::log::info " ${binary} (non-static)"
795 fi
796 done
797
798 V=2 kube::log::info "Env for ${platform}: GOOS=${GOOS-} GOARCH=${GOARCH-} GOROOT=${GOROOT-} CGO_ENABLED=${CGO_ENABLED-} CC=${CC-}"
799 V=3 kube::log::info "Building binaries with GCFLAGS=${gogcflags} LDFLAGS=${goldflags}"
800
801 local -a build_args
802 if [[ "${#statics[@]}" != 0 ]]; then
803 build_args=(
804 -installsuffix=static
805 ${goflags:+"${goflags[@]}"}
806 -gcflags="${gogcflags}"
807 -ldflags="${goldflags}"
808 -tags="${gotags:-}"
809 )
810 CGO_ENABLED=0 kube::golang::build_some_binaries "${statics[@]}"
811 fi
812
813 if [[ "${#nonstatics[@]}" != 0 ]]; then
814 build_args=(
815 ${goflags:+"${goflags[@]}"}
816 -gcflags="${gogcflags}"
817 -ldflags="${goldflags}"
818 -tags="${gotags:-}"
819 )
820 kube::golang::build_some_binaries "${nonstatics[@]}"
821 fi
822
823 for test in "${tests[@]:+${tests[@]}}"; do
824 local outfile testpkg
825 outfile=$(kube::golang::outfile_for_binary "${test}" "${platform}")
826 testpkg=$(dirname "${test}")
827
828 mkdir -p "$(dirname "${outfile}")"
829 go test -c \
830 ${goflags:+"${goflags[@]}"} \
831 -gcflags="${gogcflags}" \
832 -ldflags="${goldflags}" \
833 -tags="${gotags:-}" \
834 -o "${outfile}" \
835 "${testpkg}"
836 done
837}
838
839# Return approximate physical memory available in gigabytes.
840kube::golang::get_physmem() {
841 local mem
842
843 # Linux kernel version >=3.14, in kb
844 if mem=$(grep MemAvailable /proc/meminfo | awk '{ print $2 }'); then
845 echo $(( mem / 1048576 ))
846 return
847 fi
848
849 # Linux, in kb
850 if mem=$(grep MemTotal /proc/meminfo | awk '{ print $2 }'); then
851 echo $(( mem / 1048576 ))
852 return
853 fi
854
855 # OS X, in bytes. Note that get_physmem, as used, should only ever
856 # run in a Linux container (because it's only used in the multiple
857 # platform case, which is a Dockerized build), but this is provided
858 # for completeness.
859 if mem=$(sysctl -n hw.memsize 2>/dev/null); then
860 echo $(( mem / 1073741824 ))
861 return
862 fi
863
864 # If we can't infer it, just give up and assume a low memory system
865 echo 1
866}
867
868# Build binaries targets specified
869#
870# Input:
871# $@ - targets and go flags. If no targets are set then all binaries targets
872# are built.
873# KUBE_BUILD_PLATFORMS - Incoming variable of targets to build for. If unset
874# then just the host architecture is built.
875kube::golang::build_binaries() {
876 # Create a sub-shell so that we don't pollute the outer environment
877 (
878 # Check for `go` binary and set ${GOPATH}.
879 kube::golang::setup_env
880 V=2 kube::log::info "Go version: $(GOFLAGS='' go version)"
881
882 local host_platform
883 host_platform=$(kube::golang::host_platform)
884
885 # These are "local" but are visible to and relied on by functions this
886 # function calls. They are effectively part of the calling API to
887 # build_binaries_for_platform.
888 local goflags goldflags gogcflags gotags
889
890 goflags=()
891 gogcflags="${GOGCFLAGS:-}"
892 goldflags="all=$(kube::version::ldflags) ${GOLDFLAGS:-}"
893
894 if [[ "${DBG:-}" == 1 ]]; then
895 # Debugging - disable optimizations and inlining and trimPath
896 gogcflags="${gogcflags} all=-N -l"
897 else
898 # Not debugging - disable symbols and DWARF, trim embedded paths
899 goldflags="${goldflags} -s -w"
900 goflags+=("-trimpath")
901 fi
902
903 # Extract tags if any specified in GOFLAGS
904 gotags="selinux,notest,$(echo "${GOFLAGS:-}" | sed -ne 's|.*-tags=\([^-]*\).*|\1|p')"
905
906 local -a targets=()
907 local arg
908
909 for arg; do
910 if [[ "${arg}" == -* ]]; then
911 # Assume arguments starting with a dash are flags to pass to go.
912 goflags+=("${arg}")
913 else
914 targets+=("${arg}")
915 fi
916 done
917
918 local -a platforms
919 IFS=" " read -ra platforms <<< "${KUBE_BUILD_PLATFORMS:-}"
920 if [[ ${#platforms[@]} -eq 0 ]]; then
921 platforms=("${host_platform}")
922 fi
923
924 if [[ ${#targets[@]} -eq 0 ]]; then
925 targets=("${KUBE_ALL_TARGETS[@]}")
926 fi
927 kube::util::read-array targets < <(kube::golang::dedup "${targets[@]}")
928
929 local -a binaries
930 kube::util::read-array binaries < <(kube::golang::normalize_go_targets "${targets[@]}")
931 kube::util::read-array binaries < <(kube::golang::dedup "${binaries[@]}")
932
933 local parallel=false
934 if [[ ${#platforms[@]} -gt 1 ]]; then
935 local gigs
936 gigs=$(kube::golang::get_physmem)
937
938 if [[ ${gigs} -ge ${KUBE_PARALLEL_BUILD_MEMORY} ]]; then
939 kube::log::status "Multiple platforms requested and available ${gigs}G >= threshold ${KUBE_PARALLEL_BUILD_MEMORY}G, building platforms in parallel"
940 parallel=true
941 else
942 kube::log::status "Multiple platforms requested, but available ${gigs}G < threshold ${KUBE_PARALLEL_BUILD_MEMORY}G, building platforms in serial"
943 parallel=false
944 fi
945 fi
946
947 if [[ "${parallel}" == "true" ]]; then
948 kube::log::status "Building go targets for {${platforms[*]}} in parallel (output will appear in a burst when complete):" "${targets[@]}"
949 local platform
950 for platform in "${platforms[@]}"; do (
951 kube::golang::set_platform_envs "${platform}"
952 kube::log::status "${platform}: build started"
953 kube::golang::build_binaries_for_platform "${platform}"
954 kube::log::status "${platform}: build finished"
955 ) &> "/tmp//${platform//\//_}.build" &
956 done
957
958 local fails=0
959 for job in $(jobs -p); do
960 wait "${job}" || (( fails+=1 ))
961 done
962
963 for platform in "${platforms[@]}"; do
964 cat "/tmp//${platform//\//_}.build"
965 done
966
967 exit "${fails}"
968 else
969 for platform in "${platforms[@]}"; do
970 kube::log::status "Building go targets for ${platform}"
971 (
972 kube::golang::set_platform_envs "${platform}"
973 kube::golang::build_binaries_for_platform "${platform}"
974 )
975 done
976 fi
977 )
978}
View as plain text