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 {