1#!/bin/bash -eu 2 3set -o pipefail 4 5# This test exercises the bootstrapping process of the build system 6# in a source tree that only contains enough files for Bazel and Soong to work. 7 8source "$(dirname "$0")/lib.sh" 9 10readonly GENERATED_BUILD_FILE_NAME="BUILD.bazel" 11 12readonly target_product="${TARGET_PRODUCT:-aosp_arm}" 13 14function test_smoke { 15 setup 16 run_soong 17} 18 19function test_null_build() { 20 setup 21 run_soong 22 local -r bootstrap_mtime1=$(stat -c "%y" out/soong/bootstrap.ninja) 23 local -r output_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 24 run_soong 25 local -r bootstrap_mtime2=$(stat -c "%y" out/soong/bootstrap.ninja) 26 local -r output_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 27 28 if [[ "$bootstrap_mtime1" == "$bootstrap_mtime2" ]]; then 29 # Bootstrapping is always done. It doesn't take a measurable amount of time. 30 fail "Bootstrap Ninja file did not change on null build" 31 fi 32 33 if [[ "$output_mtime1" != "$output_mtime2" ]]; then 34 fail "Output Ninja file changed on null build" 35 fi 36} 37 38function test_soong_build_rebuilt_if_blueprint_changes() { 39 setup 40 run_soong 41 local -r mtime1=$(stat -c "%y" out/soong/bootstrap.ninja) 42 43 sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go 44 45 run_soong 46 local -r mtime2=$(stat -c "%y" out/soong/bootstrap.ninja) 47 48 if [[ "$mtime1" == "$mtime2" ]]; then 49 fail "Bootstrap Ninja file did not change" 50 fi 51} 52 53function test_change_android_bp() { 54 setup 55 mkdir -p a 56 cat > a/Android.bp <<'EOF' 57python_binary_host { 58 name: "my_little_binary_host", 59 srcs: ["my_little_binary_host.py"] 60} 61EOF 62 touch a/my_little_binary_host.py 63 run_soong 64 65 grep -q "^# Module:.*my_little_binary_host" out/soong/build."${target_product}".ninja || fail "module not found" 66 67 cat > a/Android.bp <<'EOF' 68python_binary_host { 69 name: "my_great_binary_host", 70 srcs: ["my_great_binary_host.py"] 71} 72EOF 73 touch a/my_great_binary_host.py 74 run_soong 75 76 grep -q "^# Module:.*my_little_binary_host" out/soong/build."${target_product}".ninja && fail "old module found" 77 grep -q "^# Module:.*my_great_binary_host" out/soong/build."${target_product}".ninja || fail "new module not found" 78} 79 80function test_add_android_bp() { 81 setup 82 run_soong 83 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 84 85 mkdir -p a 86 cat > a/Android.bp <<'EOF' 87python_binary_host { 88 name: "my_little_binary_host", 89 srcs: ["my_little_binary_host.py"] 90} 91EOF 92 touch a/my_little_binary_host.py 93 run_soong 94 95 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 96 if [[ "$mtime1" == "$mtime2" ]]; then 97 fail "Output Ninja file did not change" 98 fi 99 100 grep -q "^# Module:.*my_little_binary_host$" out/soong/build."${target_product}".ninja || fail "New module not in output" 101 102 run_soong 103} 104 105function test_delete_android_bp() { 106 setup 107 mkdir -p a 108 cat > a/Android.bp <<'EOF' 109python_binary_host { 110 name: "my_little_binary_host", 111 srcs: ["my_little_binary_host.py"] 112} 113EOF 114 touch a/my_little_binary_host.py 115 run_soong 116 117 grep -q "^# Module:.*my_little_binary_host$" out/soong/build."${target_product}".ninja || fail "Module not in output" 118 119 rm a/Android.bp 120 run_soong 121 122 if grep -q "^# Module:.*my_little_binary_host$" out/soong/build."${target_product}".ninja; then 123 fail "Old module in output" 124 fi 125} 126 127# Test that an incremental build with a glob doesn't rerun soong_build, and 128# only regenerates the globs on the first but not the second incremental build. 129function test_glob_noop_incremental() { 130 setup 131 132 # This test needs to start from a clean build, but setup creates an 133 # initialized tree that has already been built once. Clear the out 134 # directory to start from scratch (see b/185591972) 135 rm -rf out 136 137 mkdir -p a 138 cat > a/Android.bp <<'EOF' 139python_binary_host { 140 name: "my_little_binary_host", 141 srcs: ["*.py"], 142} 143EOF 144 touch a/my_little_binary_host.py 145 run_soong 146 local -r ninja_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 147 148 run_soong 149 local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 150 151 if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then 152 fail "Ninja file rewritten on null incremental build" 153 fi 154 155 run_soong 156 local -r ninja_mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja) 157 158 if [[ "$ninja_mtime2" != "$ninja_mtime3" ]]; then 159 fail "Ninja file rewritten on null incremental build" 160 fi 161} 162 163function test_add_file_to_glob() { 164 setup 165 166 mkdir -p a 167 cat > a/Android.bp <<'EOF' 168python_binary_host { 169 name: "my_little_binary_host", 170 srcs: ["*.py"], 171} 172EOF 173 touch a/my_little_binary_host.py 174 run_soong 175 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 176 177 touch a/my_little_library.py 178 run_soong 179 180 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 181 if [[ "$mtime1" == "$mtime2" ]]; then 182 fail "Output Ninja file did not change" 183 fi 184 185 grep -q my_little_library.py out/soong/build."${target_product}".ninja || fail "new file is not in output" 186} 187 188function test_soong_build_rerun_iff_environment_changes() { 189 setup 190 191 mkdir -p build/soong/cherry 192 cat > build/soong/cherry/Android.bp <<'EOF' 193bootstrap_go_package { 194 name: "cherry", 195 pkgPath: "android/soong/cherry", 196 deps: [ 197 "blueprint", 198 "soong", 199 "soong-android", 200 ], 201 srcs: [ 202 "cherry.go", 203 ], 204 pluginFor: ["soong_build"], 205} 206EOF 207 208 cat > build/soong/cherry/cherry.go <<'EOF' 209package cherry 210 211import ( 212 "android/soong/android" 213 "github.com/google/blueprint" 214) 215 216var ( 217 pctx = android.NewPackageContext("cherry") 218) 219 220func init() { 221 android.RegisterSingletonType("cherry", CherrySingleton) 222} 223 224func CherrySingleton() android.Singleton { 225 return &cherrySingleton{} 226} 227 228type cherrySingleton struct{} 229 230func (p *cherrySingleton) GenerateBuildActions(ctx android.SingletonContext) { 231 cherryRule := ctx.Rule(pctx, "cherry", 232 blueprint.RuleParams{ 233 Command: "echo CHERRY IS " + ctx.Config().Getenv("CHERRY") + " > ${out}", 234 CommandDeps: []string{}, 235 Description: "Cherry", 236 }) 237 238 outputFile := android.PathForOutput(ctx, "cherry", "cherry.txt") 239 var deps android.Paths 240 241 ctx.Build(pctx, android.BuildParams{ 242 Rule: cherryRule, 243 Output: outputFile, 244 Inputs: deps, 245 }) 246} 247EOF 248 249 export CHERRY=TASTY 250 run_soong 251 grep -q "CHERRY IS TASTY" out/soong/build."${target_product}".ninja \ 252 || fail "first value of environment variable is not used" 253 254 export CHERRY=RED 255 run_soong 256 grep -q "CHERRY IS RED" out/soong/build."${target_product}".ninja \ 257 || fail "second value of environment variable not used" 258 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 259 260 run_soong 261 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 262 if [[ "$mtime1" != "$mtime2" ]]; then 263 fail "Output Ninja file changed when environment variable did not" 264 fi 265 266} 267 268function test_create_global_include_directory() { 269 setup 270 run_soong 271 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 272 273 # Soong needs to know if top level directories like hardware/ exist for use 274 # as global include directories. Make sure that doesn't cause regens for 275 # unrelated changes to the top level directory. 276 mkdir -p system/core 277 278 run_soong 279 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 280 if [[ "$mtime1" != "$mtime2" ]]; then 281 fail "Output Ninja file changed when top level directory changed" 282 fi 283 284 # Make sure it does regen if a missing directory in the path of a global 285 # include directory is added. 286 mkdir -p system/core/include 287 288 run_soong 289 local -r mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja) 290 if [[ "$mtime2" = "$mtime3" ]]; then 291 fail "Output Ninja file did not change when global include directory created" 292 fi 293 294} 295 296function test_add_file_to_soong_build() { 297 setup 298 run_soong 299 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 300 301 mkdir -p vendor/foo/picard 302 cat > vendor/foo/picard/Android.bp <<'EOF' 303bootstrap_go_package { 304 name: "picard-soong-rules", 305 pkgPath: "android/soong/picard", 306 deps: [ 307 "blueprint", 308 "soong", 309 "soong-android", 310 ], 311 srcs: [ 312 "picard.go", 313 ], 314 pluginFor: ["soong_build"], 315} 316EOF 317 318 cat > vendor/foo/picard/picard.go <<'EOF' 319package picard 320 321import ( 322 "android/soong/android" 323 "github.com/google/blueprint" 324) 325 326var ( 327 pctx = android.NewPackageContext("picard") 328) 329 330func init() { 331 android.RegisterSingletonType("picard", PicardSingleton) 332} 333 334func PicardSingleton() android.Singleton { 335 return &picardSingleton{} 336} 337 338type picardSingleton struct{} 339 340func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) { 341 picardRule := ctx.Rule(pctx, "picard", 342 blueprint.RuleParams{ 343 Command: "echo Make it so. > ${out}", 344 CommandDeps: []string{}, 345 Description: "Something quotable", 346 }) 347 348 outputFile := android.PathForOutput(ctx, "picard", "picard.txt") 349 var deps android.Paths 350 351 ctx.Build(pctx, android.BuildParams{ 352 Rule: picardRule, 353 Output: outputFile, 354 Inputs: deps, 355 }) 356} 357 358EOF 359 360 run_soong 361 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 362 if [[ "$mtime1" == "$mtime2" ]]; then 363 fail "Output Ninja file did not change" 364 fi 365 366 grep -q "Make it so" out/soong/build."${target_product}".ninja || fail "New action not present" 367} 368 369# Tests a glob in a build= statement in an Android.bp file, which is interpreted 370# during bootstrapping. 371function test_glob_during_bootstrapping() { 372 setup 373 374 mkdir -p build/soong/picard 375 cat > build/soong/picard/Android.bp <<'EOF' 376build=["foo*.bp"] 377EOF 378 cat > build/soong/picard/fooa.bp <<'EOF' 379bootstrap_go_package { 380 name: "picard-soong-rules", 381 pkgPath: "android/soong/picard", 382 deps: [ 383 "blueprint", 384 "soong", 385 "soong-android", 386 ], 387 srcs: [ 388 "picard.go", 389 ], 390 pluginFor: ["soong_build"], 391} 392EOF 393 394 cat > build/soong/picard/picard.go <<'EOF' 395package picard 396 397import ( 398 "android/soong/android" 399 "github.com/google/blueprint" 400) 401 402var ( 403 pctx = android.NewPackageContext("picard") 404) 405 406func init() { 407 android.RegisterSingletonType("picard", PicardSingleton) 408} 409 410func PicardSingleton() android.Singleton { 411 return &picardSingleton{} 412} 413 414type picardSingleton struct{} 415 416var Message = "Make it so." 417 418func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) { 419 picardRule := ctx.Rule(pctx, "picard", 420 blueprint.RuleParams{ 421 Command: "echo " + Message + " > ${out}", 422 CommandDeps: []string{}, 423 Description: "Something quotable", 424 }) 425 426 outputFile := android.PathForOutput(ctx, "picard", "picard.txt") 427 var deps android.Paths 428 429 ctx.Build(pctx, android.BuildParams{ 430 Rule: picardRule, 431 Output: outputFile, 432 Inputs: deps, 433 }) 434} 435 436EOF 437 438 run_soong 439 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 440 441 grep -q "Make it so" out/soong/build."${target_product}".ninja || fail "Original action not present" 442 443 cat > build/soong/picard/foob.bp <<'EOF' 444bootstrap_go_package { 445 name: "worf-soong-rules", 446 pkgPath: "android/soong/worf", 447 deps: [ 448 "blueprint", 449 "soong", 450 "soong-android", 451 "picard-soong-rules", 452 ], 453 srcs: [ 454 "worf.go", 455 ], 456 pluginFor: ["soong_build"], 457} 458EOF 459 460 cat > build/soong/picard/worf.go <<'EOF' 461package worf 462 463import "android/soong/picard" 464 465func init() { 466 picard.Message = "Engage." 467} 468EOF 469 470 run_soong 471 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 472 if [[ "$mtime1" == "$mtime2" ]]; then 473 fail "Output Ninja file did not change" 474 fi 475 476 grep -q "Engage" out/soong/build."${target_product}".ninja || fail "New action not present" 477 478 if grep -q "Make it so" out/soong/build."${target_product}".ninja; then 479 fail "Original action still present" 480 fi 481} 482 483function test_soong_docs_smoke() { 484 setup 485 486 run_soong soong_docs 487 488 [[ -e "out/soong/docs/soong_build.html" ]] || fail "Documentation for main page not created" 489 [[ -e "out/soong/docs/cc.html" ]] || fail "Documentation for C++ modules not created" 490} 491 492function test_null_build_after_soong_docs() { 493 setup 494 495 run_soong 496 local -r ninja_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 497 498 run_soong soong_docs 499 local -r docs_mtime1=$(stat -c "%y" out/soong/docs/soong_build.html) 500 501 run_soong soong_docs 502 local -r docs_mtime2=$(stat -c "%y" out/soong/docs/soong_build.html) 503 504 if [[ "$docs_mtime1" != "$docs_mtime2" ]]; then 505 fail "Output Ninja file changed on null build" 506 fi 507 508 run_soong 509 local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 510 511 if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then 512 fail "Output Ninja file changed on null build" 513 fi 514} 515 516function test_write_to_source_tree { 517 setup 518 mkdir -p a 519 cat > a/Android.bp <<EOF 520genrule { 521 name: "write_to_source_tree", 522 out: ["write_to_source_tree"], 523 cmd: "touch file_in_source_tree && touch \$(out)", 524} 525EOF 526 readonly EXPECTED_OUT=out/soong/.intermediates/a/write_to_source_tree/gen/write_to_source_tree 527 readonly ERROR_LOG=${MOCK_TOP}/out/error.log 528 readonly ERROR_MSG="Read-only file system" 529 readonly ERROR_HINT_PATTERN="BUILD_BROKEN_SRC_DIR" 530 # Test in ReadOnly source tree 531 run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=false ${EXPECTED_OUT} &> /dev/null && \ 532 fail "Write to source tree should not work in a ReadOnly source tree" 533 534 if grep -q "${ERROR_MSG}" "${ERROR_LOG}" && grep -q "${ERROR_HINT_PATTERN}" "${ERROR_LOG}" ; then 535 echo Error message and error hint found in logs >/dev/null 536 else 537 fail "Did not find Read-only error AND error hint in error.log" 538 fi 539 540 # Test in ReadWrite source tree 541 run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=true ${EXPECTED_OUT} &> /dev/null || \ 542 fail "Write to source tree did not succeed in a ReadWrite source tree" 543 544 if grep -q "${ERROR_MSG}\|${ERROR_HINT_PATTERN}" "${ERROR_LOG}" ; then 545 fail "Found read-only error OR error hint in error.log" 546 fi 547} 548 549function test_dump_json_module_graph() { 550 setup 551 run_soong json-module-graph 552 if [[ ! -r "out/soong/module-graph.json" ]]; then 553 fail "JSON file was not created" 554 fi 555} 556 557function test_json_module_graph_back_and_forth_null_build() { 558 setup 559 560 run_soong 561 local -r ninja_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 562 563 run_soong json-module-graph 564 local -r json_mtime1=$(stat -c "%y" out/soong/module-graph.json) 565 566 run_soong 567 local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 568 if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then 569 fail "Output Ninja file changed after writing JSON module graph" 570 fi 571 572 run_soong json-module-graph 573 local -r json_mtime2=$(stat -c "%y" out/soong/module-graph.json) 574 if [[ "$json_mtime1" != "$json_mtime2" ]]; then 575 fail "JSON module graph file changed after writing Ninja file" 576 fi 577 578} 579 580# This test verifies that adding a new glob to a blueprint file only 581# causes build."${target_product}".ninja to be regenerated on the *next* build, and *not* 582# the build after. (This is a regression test for a bug where globs 583# resulted in two successive regenerations.) 584function test_new_glob_incrementality { 585 setup 586 587 run_soong nothing 588 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 589 590 mkdir -p globdefpkg/ 591 cat > globdefpkg/Android.bp <<'EOF' 592filegroup { 593 name: "fg_with_glob", 594 srcs: ["*.txt"], 595} 596EOF 597 598 run_soong nothing 599 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 600 601 if [[ "$mtime1" == "$mtime2" ]]; then 602 fail "Ninja file was not regenerated, despite a new bp file" 603 fi 604 605 run_soong nothing 606 local -r mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja) 607 608 if [[ "$mtime2" != "$mtime3" ]]; then 609 fail "Ninja file was regenerated despite no previous bp changes" 610 fi 611} 612 613scan_and_run_tests 614