#!/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. # The golang package that we are building. readonly KUBE_GO_PACKAGE=k8s.io/kubernetes readonly KUBE_GOPATH="${KUBE_OUTPUT}/go" # The set of server targets that we are only building for Linux # If you update this list, please also update build/BUILD. kube::golang::server_targets() { local targets=( cmd/kube-proxy cmd/kube-apiserver cmd/kube-controller-manager cmd/cloud-controller-manager cmd/kubelet cmd/kubeadm cmd/hyperkube cmd/kube-scheduler vendor/k8s.io/kube-aggregator vendor/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 that we are only building for Kubernetes nodes # If you update this list, please also update build/BUILD. kube::golang::node_targets() { local targets=( cmd/kube-proxy cmd/kubeadm cmd/kubelet ) 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}") if [[ -n "${KUBE_BUILD_PLATFORMS:-}" ]]; then IFS=" " read -ra KUBE_SERVER_PLATFORMS <<< "$KUBE_BUILD_PLATFORMS" IFS=" " read -ra KUBE_NODE_PLATFORMS <<< "$KUBE_BUILD_PLATFORMS" IFS=" " read -ra KUBE_TEST_PLATFORMS <<< "$KUBE_BUILD_PLATFORMS" IFS=" " read -ra KUBE_CLIENT_PLATFORMS <<< "$KUBE_BUILD_PLATFORMS" readonly KUBE_SERVER_PLATFORMS readonly KUBE_NODE_PLATFORMS readonly KUBE_TEST_PLATFORMS readonly KUBE_CLIENT_PLATFORMS elif [[ "${KUBE_FASTBUILD:-}" == "true" ]]; then readonly KUBE_SERVER_PLATFORMS=(linux/amd64) readonly KUBE_NODE_PLATFORMS=(linux/amd64) if [[ "${KUBE_BUILDER_OS:-}" == "darwin"* ]]; then readonly KUBE_TEST_PLATFORMS=( darwin/amd64 linux/amd64 ) readonly KUBE_CLIENT_PLATFORMS=( darwin/amd64 linux/amd64 ) else readonly KUBE_TEST_PLATFORMS=(linux/amd64) readonly KUBE_CLIENT_PLATFORMS=(linux/amd64) fi else # The server platform we are building on. readonly KUBE_SERVER_PLATFORMS=( linux/amd64 linux/arm linux/arm64 linux/s390x linux/ppc64le ) # The node platforms we build for readonly KUBE_NODE_PLATFORMS=( linux/amd64 linux/arm 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_CLIENT_PLATFORMS=( linux/amd64 linux/386 linux/arm linux/arm64 linux/s390x linux/ppc64le darwin/amd64 darwin/386 windows/amd64 windows/386 ) # Which platforms we should compile test targets for. Not all client platforms need these tests readonly KUBE_TEST_PLATFORMS=( linux/amd64 linux/arm linux/arm64 linux/s390x linux/ppc64le darwin/amd64 windows/amd64 ) fi # The set of client targets that we are building for all platforms # If you update this list, please also update build/BUILD. readonly KUBE_CLIENT_TARGETS=( cmd/kubectl ) 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 # If you update this list, please also update build/BUILD. kube::golang::test_targets() { local targets=( cmd/gendocs cmd/genkubedocs cmd/genman cmd/genyaml cmd/genswaggertypedocs cmd/linkcheck vendor/github.com/onsi/ginkgo/ginkgo test/e2e/e2e.test ) 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}") # If you update this list, please also update build/BUILD. readonly KUBE_TEST_PORTABLE=( test/e2e/testing-manifests test/kubemark hack/e2e.go 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. # If you update this list, please also update build/BUILD. kube::golang::server_test_targets() { local targets=( cmd/kubemark vendor/github.com/onsi/ginkgo/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[@]}") # Gigabytes necessary for parallel platform builds. # As of January 2018, RAM usage is exceeding 30G # Setting to 40 to provide some headroom readonly KUBE_PARALLEL_BUILD_MEMORY=40 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_LIBRARIES=( cloud-controller-manager kube-apiserver kube-controller-manager kube-scheduler kube-proxy kube-aggregator kubeadm kubectl ) # 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_LIBRARIES. IFS=" " read -ra KUBE_CGO_OVERRIDES <<< "${KUBE_CGO_OVERRIDES:-}" readonly KUBE_CGO_OVERRIDES # 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_LIBRARIES. IFS=" " read -ra KUBE_STATIC_OVERRIDES <<< "${KUBE_STATIC_OVERRIDES:-}" readonly KUBE_STATIC_OVERRIDES kube::golang::is_statically_linked_library() { 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:+x}" ]]; then for e in "${KUBE_CGO_OVERRIDES[@]}"; do [[ "$1" == *"/$e" ]] && return 1; done; fi for e in "${KUBE_STATIC_LIBRARIES[@]}"; do [[ "$1" == *"/$e" ]] && return 0; done; if [[ -n "${KUBE_STATIC_OVERRIDES:+x}" ]]; then for e in "${KUBE_STATIC_OVERRIDES[@]}"; do [[ "$1" == *"/$e" ]] && return 0; done; fi return 1; } # kube::binaries_from_targets take a list of build targets and return the # full go package to be built kube::golang::binaries_from_targets() { local target for target; do # If the target starts with what looks like a domain name, assume it has a # fully-qualified package name rather than one that needs the Kubernetes # package prepended. if [[ "${target}" =~ ^([[:alnum:]]+".")+[[:alnum:]]+"/" ]]; then echo "${target}" else echo "${KUBE_GO_PACKAGE}/${target}" fi 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 from linux/amd64 if [[ $(kube::golang::host_platform) == "linux/amd64" ]]; then # Dynamic CGO linking for other server architectures than linux/amd64 goes here # If you want to include support for more server platforms than these, add arch-specific gcc names here case "${platform}" in "linux/arm") export CGO_ENABLED=1 export CC=arm-linux-gnueabihf-gcc ;; "linux/arm64") export CGO_ENABLED=1 export CC=aarch64-linux-gnu-gcc ;; "linux/ppc64le") export CGO_ENABLED=1 export CC=powerpc64le-linux-gnu-gcc ;; "linux/s390x") export CGO_ENABLED=1 export CC=s390x-linux-gnu-gcc ;; esac fi } kube::golang::unset_platform_envs() { unset GOOS unset GOARCH unset GOROOT unset CGO_ENABLED unset CC } # Create the GOPATH tree under $KUBE_OUTPUT kube::golang::create_gopath_tree() { local go_pkg_dir="${KUBE_GOPATH}/src/${KUBE_GO_PACKAGE}" local go_pkg_basedir=$(dirname "${go_pkg_dir}") mkdir -p "${go_pkg_basedir}" # TODO: This symlink should be relative. if [[ ! -e "${go_pkg_dir}" || "$(readlink ${go_pkg_dir})" != "${KUBE_ROOT}" ]]; then ln -snf "${KUBE_ROOT}" "${go_pkg_dir}" fi cat >"${KUBE_GOPATH}/BUILD" <` only works for a single pkg. local subdir subdir=$(kube::realpath . | sed "s|$KUBE_ROOT||") cd "${KUBE_GOPATH}/src/${KUBE_GO_PACKAGE}/${subdir}" # Set GOROOT so binaries that parse code can work properly. export GOROOT=$(go env GOROOT) # Unset GOBIN in case it already exists in the current session. unset GOBIN # This seems to matter to some tools (godep, ginkgo...) export GO15VENDOREXPERIMENT=1 } # This will take binaries from $GOPATH/bin and copy them to the appropriate # place in ${KUBE_OUTPUT_BINDIR} # # Ideally this wouldn't be necessary and we could just set GOBIN to # KUBE_OUTPUT_BINDIR 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}" ln -s "${KUBE_OUTPUT_BINPATH}/${platform}" "${THIS_PLATFORM_BIN}" fi local full_binpath_src="${KUBE_GOPATH}/bin${platform_src}" if [[ -d "${full_binpath_src}" ]]; then mkdir -p "${KUBE_OUTPUT_BINPATH}/${platform}" find "${full_binpath_src}" -maxdepth 1 -type f -exec \ rsync -pc {} "${KUBE_OUTPUT_BINPATH}/${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" if [[ "$platform" != "$host_platform" ]]; then output_path="${output_path}/${platform//\//_}" fi local bin=$(basename "${binary}") if [[ ${GOOS} == "windows" ]]; then bin="${bin}.exe" fi echo "${output_path}/${bin}" } kube::golang::build_binaries_for_platform() { local platform=$1 local -a statics=() local -a nonstatics=() local -a tests=() V=2 kube::log::info "Env for ${platform}: GOOS=${GOOS-} GOARCH=${GOARCH-} GOROOT=${GOROOT-} CGO_ENABLED=${CGO_ENABLED-} CC=${CC-}" for binary in "${binaries[@]}"; do if [[ "${binary}" =~ ".test"$ ]]; then tests+=($binary) elif kube::golang::is_statically_linked_library "${binary}"; then statics+=($binary) else nonstatics+=($binary) fi done if [[ "${#statics[@]}" != 0 ]]; then CGO_ENABLED=0 go install -installsuffix static "${goflags[@]:+${goflags[@]}}" \ -gcflags "${gogcflags}" \ -ldflags "${goldflags}" \ "${statics[@]:+${statics[@]}}" fi if [[ "${#nonstatics[@]}" != 0 ]]; then go install "${goflags[@]:+${goflags[@]}}" \ -gcflags "${gogcflags}" \ -ldflags "${goldflags}" \ "${nonstatics[@]:+${nonstatics[@]}}" fi for test in "${tests[@]:+${tests[@]}}"; do local outfile=$(kube::golang::outfile_for_binary "${test}" "${platform}") local testpkg="$(dirname ${test})" mkdir -p "$(dirname ${outfile})" go test -c \ "${goflags[@]:+${goflags[@]}}" \ -gcflags "${gogcflags}" \ -ldflags "${goldflags}" \ -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: $(go version)" local host_platform host_platform=$(kube::golang::host_platform) # Use eval to preserve embedded quoted strings. local goflags goldflags gogcflags eval "goflags=(${GOFLAGS:-})" goldflags="${GOLDFLAGS:-} $(kube::version::ldflags)" gogcflags="${GOGCFLAGS:-}" 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 if [[ ${#targets[@]} -eq 0 ]]; then targets=("${KUBE_ALL_TARGETS[@]}") fi local -a platforms IFS=" " read -ra platforms <<< "${KUBE_BUILD_PLATFORMS:-}" if [[ ${#platforms[@]} -eq 0 ]]; then platforms=("${host_platform}") fi local binaries binaries=($(kube::golang::binaries_from_targets "${targets[@]}")) 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} || let "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}:" "${targets[@]}" ( kube::golang::set_platform_envs "${platform}" kube::golang::build_binaries_for_platform ${platform} ) done fi ) }