#!/bin/sh # Provision the exact Android, Java, Go, and Gradle toolchain into # TOOLCHAIN_DIR. Standalone tools are installed from staging directories; # sdkmanager packages are checked for exact revisions after installation. set -eu root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) # shellcheck source=versions . "$root/versions" dir=${TOOLCHAIN_DIR:-$HOME/toolchains} sdk="$dir/android-sdk" cmdline="$sdk/cmdline-tools/$ANDROID_CMDLINE_TOOLS" gradle="$dir/gradle-$GRADLE_VERSION" goroot="$dir/go-$GO_VERSION" stage= part= trap 'if [ -n "$stage" ]; then rm -rf "$stage"; fi; if [ -n "$part" ]; then rm -f "$part"; fi' 0 1 2 3 15 if [ "$(uname -s)" != Linux ] || [ "$(uname -m)" != x86_64 ]; then echo "setup: only Linux x86_64 build hosts are supported" >&2 exit 1 fi # Temurin publishes separate x86_64 archives for glibc and musl. Detect the # host before naming the cache so an archive for one libc can never be reused # on the other. if getconf GNU_LIBC_VERSION >/dev/null 2>&1; then jdk_libc=glibc jdk_platform=linux jdk_sha256=$JDK_GLIBC_SHA256 else musl_loader= for candidate in /lib/ld-musl-x86_64.so.1 /lib64/ld-musl-x86_64.so.1 /usr/lib/ld-musl-x86_64.so.1; do if [ -e "$candidate" ]; then musl_loader=$candidate break fi done [ -n "$musl_loader" ] || { echo "setup: host libc is neither glibc nor musl" >&2 exit 1 } jdk_libc=musl jdk_platform=alpine-linux jdk_sha256=$JDK_MUSL_SHA256 fi jdk="$dir/jdk-$JDK_RELEASE-$jdk_libc" jdk_mark="$JDK_RELEASE $jdk_libc" vulncheck="$dir/govulncheck-$GOVULNCHECK_VERSION-$jdk_libc" vulncheck_mark="$GOVULNCHECK_VERSION $GO_VERSION $jdk_libc" mkdir -p "$dir/dl" download() { url=$1 file=$2 want=$3 if [ -f "$file" ]; then actual=$(sha256sum "$file") if [ "${actual%% *}" = "$want" ]; then return fi echo "setup: removing corrupt $(basename "$file")" >&2 rm -f "$file" fi part="$file.part.$$" curl --fail --silent --show-error --location --retry 3 \ --connect-timeout 30 --speed-limit 1024 --speed-time 60 \ "$url" -o "$part" actual=$(sha256sum "$part") [ "${actual%% *}" = "$want" ] || { echo "setup: checksum failed for $(basename "$file")" >&2 exit 1 } mv "$part" "$file" part= } mark_complete() { cat < "$1/.rsend-complete" $2 EOF } marked() { [ -f "$1/.rsend-complete" ] && [ "$(cat "$1/.rsend-complete")" = "$2" ] } java_works() { java_root=$1 version_output=$("$java_root/bin/java" -version 2>&1) || return 1 case "$version_output" in *"$JDK_RELEASE"*) return 0 ;; *) return 1 ;; esac } gradle_works() { gradle_root=$1 version_output=$("$gradle_root/bin/gradle" --version 2>/dev/null) || return 1 grep -Fqx "Gradle $GRADLE_VERSION" </dev/null) || return 1 case "$version_output" in *"go version go$GO_VERSION "*) return 0 ;; *) return 1 ;; esac } govulncheck_works() { vulncheck_root=$1 version_output=$("$vulncheck_root/bin/govulncheck" -version 2>/dev/null) || return 1 grep -Fqx "Scanner: govulncheck@v$GOVULNCHECK_VERSION" <&2 exit 1 fi mark_complete "$stage" "$jdk_mark" rm -rf "$jdk" mv "$stage" "$jdk" stage= fi export JAVA_HOME="$jdk" export PATH="$JAVA_HOME/bin:$PATH" # Android command-line tools. if [ ! -x "$cmdline/bin/sdkmanager" ] || ! marked "$cmdline" "$ANDROID_CMDLINE_TOOLS" || ! "$cmdline/bin/sdkmanager" --sdk_root="$sdk" --version >/dev/null 2>&1; then echo "setup: cmdline-tools $ANDROID_CMDLINE_TOOLS" download "https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS}_latest.zip" \ "$dir/dl/clt-$ANDROID_CMDLINE_TOOLS.zip" "$ANDROID_CMDLINE_TOOLS_SHA256" mkdir -p "$sdk/cmdline-tools" stage=$(mktemp -d "$sdk/cmdline-tools/.cmdline-$ANDROID_CMDLINE_TOOLS.XXXXXX") python3 -m zipfile -e "$dir/dl/clt-$ANDROID_CMDLINE_TOOLS.zip" "$stage" chmod +x "$stage/cmdline-tools/bin/"* [ -x "$stage/cmdline-tools/bin/sdkmanager" ] || { echo "setup: command-line tools archive has no sdkmanager" >&2 exit 1 } "$stage/cmdline-tools/bin/sdkmanager" --sdk_root="$sdk" --version >/dev/null mark_complete "$stage/cmdline-tools" "$ANDROID_CMDLINE_TOOLS" rm -rf "$cmdline" mv "$stage/cmdline-tools" "$cmdline" rm -rf "$stage" stage= fi # SDK packages. A complete pinned install needs no network on repeated runs. export ANDROID_HOME="$sdk" export ANDROID_SDK_ROOT="$sdk" sdk_packages_ready() { [ -f "$sdk/platforms/android-$ANDROID_PLATFORM/android.jar" ] && grep -Fqx "AndroidVersion.ApiLevel=$ANDROID_PLATFORM" \ "$sdk/platforms/android-$ANDROID_PLATFORM/source.properties" && grep -Fqx "Pkg.Revision=$ANDROID_PLATFORM_REVISION" \ "$sdk/platforms/android-$ANDROID_PLATFORM/source.properties" && platform_sum=$(sha256sum "$sdk/platforms/android-$ANDROID_PLATFORM/android.jar") && [ "${platform_sum%% *}" = "$ANDROID_PLATFORM_JAR_SHA256" ] && [ -x "$sdk/build-tools/$ANDROID_BUILD_TOOLS/aapt" ] && grep -Fqx "Pkg.Revision=$ANDROID_BUILD_TOOLS" \ "$sdk/build-tools/$ANDROID_BUILD_TOOLS/source.properties" && [ -d "$sdk/ndk/$ANDROID_NDK" ] && grep -Fqx "Pkg.Revision = $ANDROID_NDK" \ "$sdk/ndk/$ANDROID_NDK/source.properties" && [ -s "$sdk/licenses/android-sdk-license" ] } if ! sdk_packages_ready; then echo "setup: SDK packages" license_answers="$dir/dl/android-license-answers" awk 'BEGIN { for (i = 0; i < 100; i++) print "y" }' > "$license_answers" "$cmdline/bin/sdkmanager" --sdk_root="$sdk" --licenses \ < "$license_answers" >/dev/null "$cmdline/bin/sdkmanager" --sdk_root="$sdk" \ "platforms;android-${ANDROID_PLATFORM}" \ "build-tools;${ANDROID_BUILD_TOOLS}" \ "ndk;${ANDROID_NDK}" >/dev/null sdk_packages_ready || { echo "setup: Android SDK packages are incomplete" >&2 exit 1 } fi ndk_clang="$sdk/ndk/$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang" if [ ! -x "$ndk_clang" ] || ! "$ndk_clang" --version >/dev/null 2>&1; then echo "setup: Android NDK cannot run on this host (Alpine needs gcompat)" >&2 exit 1 fi # Gradle. if ! gradle_ready; then echo "setup: Gradle $GRADLE_VERSION" download "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \ "$dir/dl/gradle-$GRADLE_VERSION.zip" "$GRADLE_SHA256" stage=$(mktemp -d "$dir/.gradle-$GRADLE_VERSION.XXXXXX") python3 -m zipfile -e "$dir/dl/gradle-$GRADLE_VERSION.zip" "$stage" chmod +x "$stage/gradle-$GRADLE_VERSION/bin/gradle" [ -x "$stage/gradle-$GRADLE_VERSION/bin/gradle" ] || { echo "setup: Gradle archive has no gradle" >&2 exit 1 } gradle_works "$stage/gradle-$GRADLE_VERSION" || { echo "setup: Gradle cannot run on this host" >&2 exit 1 } mark_complete "$stage/gradle-$GRADLE_VERSION" "$GRADLE_VERSION" rm -rf "$gradle" mv "$stage/gradle-$GRADLE_VERSION" "$gradle" rm -rf "$stage" stage= fi export GRADLE_HOME="$gradle" # Go. if ! go_ready; then echo "setup: Go $GO_VERSION" download "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" \ "$dir/dl/go-$GO_VERSION.tar.gz" "$GO_SHA256" stage=$(mktemp -d "$dir/.go-$GO_VERSION.XXXXXX") tar xzf "$dir/dl/go-$GO_VERSION.tar.gz" -C "$stage" --strip-components=1 if [ ! -x "$stage/bin/go" ] || ! go_works "$stage"; then echo "setup: Go cannot run on this host" >&2 exit 1 fi mark_complete "$stage" "$GO_VERSION" rm -rf "$goroot" mv "$stage" "$goroot" stage= fi export GOROOT="$goroot" export PATH="$GOROOT/bin:$PATH" # Source-aware vulnerability scanner. It queries the public Go vulnerability # database when ci/test.sh invokes it; the source tree itself is not uploaded. if ! govulncheck_ready; then echo "setup: govulncheck $GOVULNCHECK_VERSION" stage=$(mktemp -d "$dir/.govulncheck-$GOVULNCHECK_VERSION-$jdk_libc.XXXXXX") mkdir -p "$stage/bin" GOBIN="$stage/bin" "$goroot/bin/go" install \ "golang.org/x/vuln/cmd/govulncheck@v${GOVULNCHECK_VERSION}" if [ ! -x "$stage/bin/govulncheck" ] || ! govulncheck_works "$stage"; then echo "setup: govulncheck install failed" >&2 exit 1 fi mark_complete "$stage" "$vulncheck_mark" rm -rf "$vulncheck" mv "$stage" "$vulncheck" stage= fi # Emit the environment to source before building. cat < "$dir/env.sh" export JAVA_HOME="$jdk" export GOROOT="$goroot" export ANDROID_HOME="$sdk" export ANDROID_SDK_ROOT="$sdk" export ANDROID_NDK_HOME="$sdk/ndk/${ANDROID_NDK}" export GRADLE_HOME="$gradle" export PATH="\$JAVA_HOME/bin:\$GOROOT/bin:\$GRADLE_HOME/bin:$vulncheck/bin:$cmdline/bin:$sdk/build-tools/${ANDROID_BUILD_TOOLS}:\$PATH" EOF echo "setup: done; source $dir/env.sh"