1// Copyright (c) 2016, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15//go:build ignore 16 17package main 18 19import ( 20 "bufio" 21 "bytes" 22 "encoding/json" 23 "errors" 24 "flag" 25 "fmt" 26 "io" 27 "os" 28 "os/exec" 29 "path/filepath" 30 "runtime" 31 "strconv" 32 "strings" 33 34 "boringssl.googlesource.com/boringssl/util/build" 35 "boringssl.googlesource.com/boringssl/util/testconfig" 36) 37 38var ( 39 buildDir = flag.String("build-dir", "build", "Specifies the build directory to push.") 40 adbPath = flag.String("adb", "adb", "Specifies the adb binary to use. Defaults to looking in PATH.") 41 ndkPath = flag.String("ndk", "", "Specifies the path to the NDK installation. Defaults to detecting from the build directory.") 42 device = flag.String("device", "", "Specifies the device or emulator. See adb's -s argument.") 43 abi = flag.String("abi", "", "Specifies the Android ABI to use when building Go tools. Defaults to detecting from the build directory.") 44 apiLevel = flag.Int("api-level", 0, "Specifies the Android API level to use when building Go tools. Defaults to detecting from the build directory.") 45 suite = flag.String("suite", "all", "Specifies the test suites to run (all, unit, or ssl).") 46 allTestsArgs = flag.String("all-tests-args", "", "Specifies space-separated arguments to pass to all_tests.go") 47 runnerArgs = flag.String("runner-args", "", "Specifies space-separated arguments to pass to ssl/test/runner") 48 jsonOutput = flag.String("json-output", "", "The file to output JSON results to.") 49) 50 51func enableUnitTests() bool { 52 return *suite == "all" || *suite == "unit" 53} 54 55func enableSSLTests() bool { 56 return *suite == "all" || *suite == "ssl" 57} 58 59func adb(args ...string) error { 60 if len(*device) > 0 { 61 args = append([]string{"-s", *device}, args...) 62 } 63 cmd := exec.Command(*adbPath, args...) 64 cmd.Stdout = os.Stdout 65 cmd.Stderr = os.Stderr 66 return cmd.Run() 67} 68 69func adbShell(shellCmd string) (int, error) { 70 var args []string 71 if len(*device) > 0 { 72 args = append([]string{"-s", *device}, args...) 73 } 74 args = append(args, "shell") 75 76 const delimiter = "___EXIT_CODE___" 77 78 // Older versions of adb and Android do not preserve the exit 79 // code, so work around this. 80 // https://code.google.com/p/android/issues/detail?id=3254 81 shellCmd += "; echo " + delimiter + " $?" 82 args = append(args, shellCmd) 83 84 cmd := exec.Command(*adbPath, args...) 85 stdout, err := cmd.StdoutPipe() 86 if err != nil { 87 return 0, err 88 } 89 cmd.Stderr = os.Stderr 90 if err := cmd.Start(); err != nil { 91 return 0, err 92 } 93 94 var stdoutBytes bytes.Buffer 95 for { 96 var buf [1024]byte 97 n, err := stdout.Read(buf[:]) 98 stdoutBytes.Write(buf[:n]) 99 os.Stdout.Write(buf[:n]) 100 if err != nil { 101 break 102 } 103 } 104 105 if err := cmd.Wait(); err != nil { 106 return 0, err 107 } 108 109 stdoutStr := stdoutBytes.String() 110 idx := strings.LastIndex(stdoutStr, delimiter) 111 if idx < 0 { 112 return 0, fmt.Errorf("Could not find delimiter in output.") 113 } 114 115 return strconv.Atoi(strings.TrimSpace(stdoutStr[idx+len(delimiter):])) 116} 117 118func goTool(args ...string) error { 119 cmd := exec.Command("go", args...) 120 cmd.Stdout = os.Stdout 121 cmd.Stderr = os.Stderr 122 123 cmd.Env = os.Environ() 124 125 // The NDK includes the host platform in the toolchain path. 126 var ndkOS, ndkArch string 127 switch runtime.GOOS { 128 case "linux": 129 ndkOS = "linux" 130 default: 131 return fmt.Errorf("unknown host OS: %q", runtime.GOOS) 132 } 133 switch runtime.GOARCH { 134 case "amd64": 135 ndkArch = "x86_64" 136 default: 137 return fmt.Errorf("unknown host architecture: %q", runtime.GOARCH) 138 } 139 ndkHost := ndkOS + "-" + ndkArch 140 141 // Use the NDK's target-prefixed clang wrappers, so cgo gets the right 142 // flags. See https://developer.android.com/ndk/guides/cmake#android_abi for 143 // Android ABIs. 144 var targetPrefix string 145 switch *abi { 146 case "armeabi-v7a", "armeabi-v7a with NEON": 147 targetPrefix = fmt.Sprintf("armv7a-linux-androideabi%d-", *apiLevel) 148 cmd.Env = append(cmd.Env, "GOARCH=arm") 149 cmd.Env = append(cmd.Env, "GOARM=7") 150 case "arm64-v8a": 151 targetPrefix = fmt.Sprintf("aarch64-linux-android%d-", *apiLevel) 152 cmd.Env = append(cmd.Env, "GOARCH=arm64") 153 default: 154 return fmt.Errorf("unknown Android ABI: %q", *abi) 155 } 156 157 // Go's Android support requires cgo and compilers from the NDK. See 158 // https://golang.org/misc/android/README, though note CC_FOR_TARGET only 159 // works when building Go itself. go build only looks at CC. 160 cmd.Env = append(cmd.Env, "CGO_ENABLED=1") 161 cmd.Env = append(cmd.Env, "GOOS=android") 162 toolchainDir := filepath.Join(*ndkPath, "toolchains", "llvm", "prebuilt", ndkHost, "bin") 163 cmd.Env = append(cmd.Env, fmt.Sprintf("CC=%s", filepath.Join(toolchainDir, targetPrefix+"clang"))) 164 cmd.Env = append(cmd.Env, fmt.Sprintf("CXX=%s", filepath.Join(toolchainDir, targetPrefix+"clang++"))) 165 return cmd.Run() 166} 167 168// setWorkingDirectory walks up directories as needed until the current working 169// directory is the top of a BoringSSL checkout. 170func setWorkingDirectory() { 171 for i := 0; i < 64; i++ { 172 if _, err := os.Stat("BUILDING.md"); err == nil { 173 return 174 } 175 os.Chdir("..") 176 } 177 178 panic("Couldn't find BUILDING.md in a parent directory!") 179} 180 181func detectOptionsFromCMake() error { 182 if len(*ndkPath) != 0 && len(*abi) != 0 && *apiLevel != 0 { 183 // No need to parse options from CMake. 184 return nil 185 } 186 187 cmakeCache, err := os.Open(filepath.Join(*buildDir, "CMakeCache.txt")) 188 if err != nil { 189 return err 190 } 191 defer cmakeCache.Close() 192 193 cmakeVars := make(map[string]string) 194 scanner := bufio.NewScanner(cmakeCache) 195 for scanner.Scan() { 196 line := scanner.Text() 197 if idx := strings.IndexByte(line, '#'); idx >= 0 { 198 line = line[:idx] 199 } 200 if idx := strings.Index(line, "//"); idx >= 0 { 201 line = line[:idx] 202 } 203 // The syntax for each line is KEY:TYPE=VALUE. 204 equals := strings.IndexByte(line, '=') 205 if equals < 0 { 206 continue 207 } 208 name := line[:equals] 209 value := line[equals+1:] 210 if idx := strings.IndexByte(name, ':'); idx >= 0 { 211 name = name[:idx] 212 } 213 cmakeVars[name] = value 214 } 215 if err := scanner.Err(); err != nil { 216 return err 217 } 218 219 if len(*ndkPath) == 0 { 220 if ndk, ok := cmakeVars["ANDROID_NDK"]; ok { 221 *ndkPath = ndk 222 } else if toolchainFile, ok := cmakeVars["CMAKE_TOOLCHAIN_FILE"]; ok { 223 // The toolchain is at build/cmake/android.toolchain.cmake under the NDK. 224 *ndkPath = filepath.Dir(filepath.Dir(filepath.Dir(toolchainFile))) 225 } else { 226 return errors.New("Neither CMAKE_TOOLCHAIN_FILE nor ANDROID_NDK found in CMakeCache.txt") 227 } 228 fmt.Printf("Detected NDK path %q from CMakeCache.txt.\n", *ndkPath) 229 } 230 if len(*abi) == 0 { 231 var ok bool 232 if *abi, ok = cmakeVars["ANDROID_ABI"]; !ok { 233 return errors.New("ANDROID_ABI not found in CMakeCache.txt") 234 } 235 fmt.Printf("Detected ABI %q from CMakeCache.txt.\n", *abi) 236 } 237 if *apiLevel == 0 { 238 apiLevelStr, ok := cmakeVars["ANDROID_PLATFORM"] 239 if !ok { 240 return errors.New("ANDROID_PLATFORM not found in CMakeCache.txt") 241 } 242 apiLevelStr = strings.TrimPrefix(apiLevelStr, "android-") 243 var err error 244 if *apiLevel, err = strconv.Atoi(apiLevelStr); err != nil { 245 return fmt.Errorf("error parsing ANDROID_PLATFORM: %s", err) 246 } 247 fmt.Printf("Detected API level %d from CMakeCache.txt.\n", *apiLevel) 248 } 249 return nil 250} 251 252func copyFile(dst, src string) error { 253 srcFile, err := os.Open(src) 254 if err != nil { 255 return err 256 } 257 defer srcFile.Close() 258 259 srcInfo, err := srcFile.Stat() 260 if err != nil { 261 return err 262 } 263 264 dir := filepath.Dir(dst) 265 if err := os.MkdirAll(dir, 0777); err != nil { 266 return err 267 } 268 269 dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, srcInfo.Mode()) 270 if err != nil { 271 return err 272 } 273 defer dstFile.Close() 274 275 _, err = io.Copy(dstFile, srcFile) 276 return err 277} 278 279func main() { 280 flag.Parse() 281 282 if *suite == "all" && *jsonOutput != "" { 283 fmt.Printf("To use -json-output flag, select only one test suite with -suite.\n") 284 os.Exit(1) 285 } 286 287 setWorkingDirectory() 288 if err := detectOptionsFromCMake(); err != nil { 289 fmt.Printf("Error reading options from CMake: %s.\n", err) 290 os.Exit(1) 291 } 292 293 targetsJSON, err := os.ReadFile("gen/sources.json") 294 if err != nil { 295 fmt.Printf("Error reading sources.json: %s.\n", err) 296 os.Exit(1) 297 } 298 var targets map[string]build.Target 299 if err := json.Unmarshal(targetsJSON, &targets); err != nil { 300 fmt.Printf("Error reading sources.json: %s.\n", err) 301 os.Exit(1) 302 } 303 304 // Clear the target directory. 305 if err := adb("shell", "rm -Rf /data/local/tmp/boringssl-tmp"); err != nil { 306 fmt.Printf("Failed to clear target directory: %s\n", err) 307 os.Exit(1) 308 } 309 310 // Stage everything in a temporary directory. 311 tmpDir, err := os.MkdirTemp("", "boringssl-android") 312 if err != nil { 313 fmt.Printf("Error making temporary directory: %s\n", err) 314 os.Exit(1) 315 } 316 defer os.RemoveAll(tmpDir) 317 318 var binaries, files []string 319 320 if enableUnitTests() { 321 files = append(files, 322 "util/all_tests.json", 323 "BUILDING.md", 324 ) 325 for _, target := range targets { 326 files = append(files, target.Data...) 327 } 328 329 tests, err := testconfig.ParseTestConfig("util/all_tests.json") 330 if err != nil { 331 fmt.Printf("Failed to parse input: %s\n", err) 332 os.Exit(1) 333 } 334 335 seenBinary := make(map[string]struct{}) 336 for _, test := range tests { 337 if _, ok := seenBinary[test.Cmd[0]]; !ok { 338 binaries = append(binaries, test.Cmd[0]) 339 seenBinary[test.Cmd[0]] = struct{}{} 340 } 341 for _, arg := range test.Cmd[1:] { 342 if strings.Contains(arg, "/") { 343 files = append(files, arg) 344 } 345 } 346 } 347 348 fmt.Printf("Building all_tests...\n") 349 if err := goTool("build", "-o", filepath.Join(tmpDir, "util/all_tests"), "util/all_tests.go"); err != nil { 350 fmt.Printf("Error building all_tests.go: %s\n", err) 351 os.Exit(1) 352 } 353 } 354 355 if enableSSLTests() { 356 binaries = append(binaries, "ssl/test/bssl_shim") 357 fmt.Printf("Building runner...\n") 358 if err := goTool("test", "-c", "-o", filepath.Join(tmpDir, "ssl/test/runner/runner"), "./ssl/test/runner/"); err != nil { 359 fmt.Printf("Error building runner: %s\n", err) 360 os.Exit(1) 361 } 362 } 363 364 var libraries []string 365 if _, err := os.Stat(filepath.Join(*buildDir, "libcrypto.so")); err == nil { 366 libraries = []string{ 367 "libboringssl_gtest.so", 368 "libcrypto.so", 369 "libdecrepit.so", 370 "libpki.so", 371 "libssl.so", 372 } 373 } else if !os.IsNotExist(err) { 374 fmt.Printf("Failed to stat libcrypto.so: %s\n", err) 375 os.Exit(1) 376 } 377 378 fmt.Printf("Copying test binaries...\n") 379 for _, binary := range binaries { 380 if err := copyFile(filepath.Join(tmpDir, "build", binary), filepath.Join(*buildDir, binary)); err != nil { 381 fmt.Printf("Failed to copy %s: %s\n", binary, err) 382 os.Exit(1) 383 } 384 } 385 386 var envPrefix string 387 if len(libraries) > 0 { 388 fmt.Printf("Copying libraries...\n") 389 for _, library := range libraries { 390 // Place all the libraries in a common directory so they 391 // can be passed to LD_LIBRARY_PATH once. 392 if err := copyFile(filepath.Join(tmpDir, "build", "lib", filepath.Base(library)), filepath.Join(*buildDir, library)); err != nil { 393 fmt.Printf("Failed to copy %s: %s\n", library, err) 394 os.Exit(1) 395 } 396 } 397 envPrefix = "env LD_LIBRARY_PATH=/data/local/tmp/boringssl-tmp/build/lib " 398 } 399 400 fmt.Printf("Copying data files...\n") 401 for _, file := range files { 402 if err := copyFile(filepath.Join(tmpDir, file), file); err != nil { 403 fmt.Printf("Failed to copy %s: %s\n", file, err) 404 os.Exit(1) 405 } 406 } 407 408 fmt.Printf("Uploading files...\n") 409 if err := adb("push", "-p", tmpDir, "/data/local/tmp/boringssl-tmp"); err != nil { 410 fmt.Printf("Failed to push runner: %s\n", err) 411 os.Exit(1) 412 } 413 414 var unitTestExit int 415 if enableUnitTests() { 416 fmt.Printf("Running unit tests...\n") 417 unitTestExit, err = adbShell(fmt.Sprintf("cd /data/local/tmp/boringssl-tmp && %s./util/all_tests -json-output results.json %s", envPrefix, *allTestsArgs)) 418 if err != nil { 419 fmt.Printf("Failed to run unit tests: %s\n", err) 420 os.Exit(1) 421 } 422 } 423 424 var sslTestExit int 425 if enableSSLTests() { 426 fmt.Printf("Running SSL tests...\n") 427 sslTestExit, err = adbShell(fmt.Sprintf("cd /data/local/tmp/boringssl-tmp/ssl/test/runner && %s./runner -json-output ../../../results.json %s", envPrefix, *runnerArgs)) 428 if err != nil { 429 fmt.Printf("Failed to run SSL tests: %s\n", err) 430 os.Exit(1) 431 } 432 } 433 434 if *jsonOutput != "" { 435 if err := adb("pull", "-p", "/data/local/tmp/boringssl-tmp/results.json", *jsonOutput); err != nil { 436 fmt.Printf("Failed to extract results.json: %s\n", err) 437 os.Exit(1) 438 } 439 } 440 441 if unitTestExit != 0 { 442 os.Exit(unitTestExit) 443 } 444 445 if sslTestExit != 0 { 446 os.Exit(sslTestExit) 447 } 448} 449