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