Tell Go to build for GOOS=android when running on Android.
This is a speculative fix for the CI flakiness on Android. It seems
Android and Linux ARM ABIs may differ slightly in handling of thread
locals, so we should build for GOOS=android. That requires cgo and
pointing CC at a suitable target-specific compiler from the NDK. Detect
those values from CMakeCache.txt.
Change-Id: I2da75bf9ca6df3e5e677c2151ece8c5e20740fc3
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/39504
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/util/run_android_tests.go b/util/run_android_tests.go
index 60009a1..7c0abf8 100644
--- a/util/run_android_tests.go
+++ b/util/run_android_tests.go
@@ -15,8 +15,10 @@
package main
import (
+ "bufio"
"bytes"
"encoding/json"
+ "errors"
"flag"
"fmt"
"io"
@@ -24,6 +26,7 @@
"os"
"os/exec"
"path/filepath"
+ "runtime"
"strconv"
"strings"
)
@@ -31,9 +34,10 @@
var (
buildDir = flag.String("build-dir", "build", "Specifies the build directory to push.")
adbPath = flag.String("adb", "adb", "Specifies the adb binary to use. Defaults to looking in PATH.")
+ ndkPath = flag.String("ndk", "", "Specifies the path to the NDK installation. Defaults to detecting from the build directory.")
device = flag.String("device", "", "Specifies the device or emulator. See adb's -s argument.")
- aarch64 = flag.Bool("aarch64", false, "Build the test runners for aarch64 instead of arm.")
- arm = flag.Int("arm", 7, "Which arm revision to build for.")
+ abi = flag.String("abi", "", "Specifies the Android ABI to use when building Go tools. Defaults to detecting from the build directory.")
+ apiLevel = flag.Int("api-level", 0, "Specifies the Android API level to use when building Go tools. Defaults to detecting from the build directory.")
suite = flag.String("suite", "all", "Specifies the test suites to run (all, unit, or ssl).")
allTestsArgs = flag.String("all-tests-args", "", "Specifies space-separated arguments to pass to all_tests.go")
runnerArgs = flag.String("runner-args", "", "Specifies space-separated arguments to pass to ssl/test/runner")
@@ -113,12 +117,47 @@
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
- if *aarch64 {
- cmd.Env = append(cmd.Env, "GOARCH=arm64")
- } else {
- cmd.Env = append(cmd.Env, "GOARCH=arm")
- cmd.Env = append(cmd.Env, fmt.Sprintf("GOARM=%d", *arm))
+
+ // The NDK includes the host platform in the toolchain path.
+ var ndkOS, ndkArch string
+ switch runtime.GOOS {
+ case "linux":
+ ndkOS = "linux"
+ default:
+ return fmt.Errorf("unknown host OS: %q", runtime.GOOS)
}
+ switch runtime.GOARCH {
+ case "amd64":
+ ndkArch = "x86_64"
+ default:
+ return fmt.Errorf("unknown host architecture: %q", runtime.GOARCH)
+ }
+ ndkHost := ndkOS + "-" + ndkArch
+
+ // Use the NDK's target-prefixed clang wrappers, so cgo gets the right
+ // flags. See https://developer.android.com/ndk/guides/cmake#android_abi for
+ // Android ABIs.
+ var targetPrefix string
+ switch *abi {
+ case "armeabi-v7a", "armeabi-v7a with NEON":
+ targetPrefix = fmt.Sprintf("armv7a-linux-androideabi%d-", *apiLevel)
+ cmd.Env = append(cmd.Env, "GOARCH=arm")
+ cmd.Env = append(cmd.Env, "GOARM=7")
+ case "arm64-v8a":
+ targetPrefix = fmt.Sprintf("aarch64-linux-android%d-", *apiLevel)
+ cmd.Env = append(cmd.Env, "GOARCH=arm64")
+ default:
+ fmt.Errorf("unknown Android ABI: %q", *abi)
+ }
+
+ // Go's Android support requires cgo and compilers from the NDK. See
+ // https://golang.org/misc/android/README, though note CC_FOR_TARGET only
+ // works when building Go itself. go build only looks at CC.
+ cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
+ cmd.Env = append(cmd.Env, "GOOS=android")
+ toolchainDir := filepath.Join(*ndkPath, "toolchains", "llvm", "prebuilt", ndkHost, "bin")
+ cmd.Env = append(cmd.Env, fmt.Sprintf("CC=%s", filepath.Join(toolchainDir, targetPrefix+"clang")))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("CXX=%s", filepath.Join(toolchainDir, targetPrefix+"clang++")))
return cmd.Run()
}
@@ -135,9 +174,75 @@
panic("Couldn't find BUILDING.md in a parent directory!")
}
+func detectOptionsFromCMake() error {
+ if len(*ndkPath) != 0 && len(*abi) != 0 && *apiLevel != 0 {
+ // No need to parse options from CMake.
+ return nil
+ }
+
+ cmakeCache, err := os.Open(filepath.Join(*buildDir, "CMakeCache.txt"))
+ if err != nil {
+ return err
+ }
+ defer cmakeCache.Close()
+
+ cmakeVars := make(map[string]string)
+ scanner := bufio.NewScanner(cmakeCache)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if idx := strings.IndexByte(line, '#'); idx >= 0 {
+ line = line[:idx]
+ }
+ if idx := strings.Index(line, "//"); idx >= 0 {
+ line = line[:idx]
+ }
+ // The syntax for each line is KEY:TYPE=VALUE.
+ equals := strings.IndexByte(line, '=')
+ if equals < 0 {
+ continue
+ }
+ name := line[:equals]
+ value := line[equals+1:]
+ if idx := strings.IndexByte(name, ':'); idx >= 0 {
+ name = name[:idx]
+ }
+ cmakeVars[name] = value
+ }
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+
+ if len(*ndkPath) == 0 {
+ var ok bool
+ if *ndkPath, ok = cmakeVars["ANDROID_NDK"]; !ok {
+ return errors.New("ANDROID_NDK not found in CMakeCache.txt")
+ }
+ fmt.Printf("Detected NDK path %q from CMakeCache.txt.\n", *ndkPath)
+ }
+ if len(*abi) == 0 {
+ var ok bool
+ if *abi, ok = cmakeVars["ANDROID_ABI"]; !ok {
+ return errors.New("ANDROID_ABI not found in CMakeCache.txt")
+ }
+ fmt.Printf("Detected ABI %q from CMakeCache.txt.\n", *abi)
+ }
+ if *apiLevel == 0 {
+ apiLevelStr, ok := cmakeVars["ANDROID_NATIVE_API_LEVEL"]
+ if !ok {
+ return errors.New("ANDROID_NATIVE_API_LEVEL not found in CMakeCache.txt")
+ }
+ var err error
+ if *apiLevel, err = strconv.Atoi(apiLevelStr); err != nil {
+ return fmt.Errorf("error parsing ANDROID_NATIVE_API_LEVEL: %s", err)
+ }
+ fmt.Printf("Detected API level %d from CMakeCache.txt.\n", *apiLevel)
+ }
+ return nil
+}
+
type test struct {
args []string
- env []string
+ env []string
}
func parseTestConfig(filename string) ([]test, error) {
@@ -202,6 +307,10 @@
}
setWorkingDirectory()
+ if err := detectOptionsFromCMake(); err != nil {
+ fmt.Printf("Error reading options from CMake: %s.\n", err)
+ os.Exit(1)
+ }
// Clear the target directory.
if err := adb("shell", "rm -Rf /data/local/tmp/boringssl-tmp"); err != nil {