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 local glob_deps_file=out/soong/globs/"${target_product}"/0.d 149 150 run_soong 151 local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 152 153 # There is an ineffiencency in glob that requires bpglob to rerun once for each glob to update 154 # the entry in the .ninja_log. It doesn't update the output file, but we can detect the rerun 155 # by checking if the deps file was created. 156 if [ ! -e "$glob_deps_file" ]; then 157 fail "Glob deps file missing after second build" 158 fi 159 160 local -r glob_deps_mtime2=$(stat -c "%y" "$glob_deps_file") 161 162 if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then 163 fail "Ninja file rewritten on null incremental build" 164 fi 165 166 run_soong 167 local -r ninja_mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja) 168 local -r glob_deps_mtime3=$(stat -c "%y" "$glob_deps_file") 169 170 if [[ "$ninja_mtime2" != "$ninja_mtime3" ]]; then 171 fail "Ninja file rewritten on null incremental build" 172 fi 173 174 # The bpglob commands should not rerun after the first incremental build. 175 if [[ "$glob_deps_mtime2" != "$glob_deps_mtime3" ]]; then 176 fail "Glob deps file rewritten on second null incremental build" 177 fi 178} 179 180function test_add_file_to_glob() { 181 setup 182 183 mkdir -p a 184 cat > a/Android.bp <<'EOF' 185python_binary_host { 186 name: "my_little_binary_host", 187 srcs: ["*.py"], 188} 189EOF 190 touch a/my_little_binary_host.py 191 run_soong 192 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 193 194 touch a/my_little_library.py 195 run_soong 196 197 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 198 if [[ "$mtime1" == "$mtime2" ]]; then 199 fail "Output Ninja file did not change" 200 fi 201 202 grep -q my_little_library.py out/soong/build."${target_product}".ninja || fail "new file is not in output" 203} 204 205function test_soong_build_rerun_iff_environment_changes() { 206 setup 207 208 mkdir -p build/soong/cherry 209 cat > build/soong/cherry/Android.bp <<'EOF' 210bootstrap_go_package { 211 name: "cherry", 212 pkgPath: "android/soong/cherry", 213 deps: [ 214 "blueprint", 215 "soong", 216 "soong-android", 217 ], 218 srcs: [ 219 "cherry.go", 220 ], 221 pluginFor: ["soong_build"], 222} 223EOF 224 225 cat > build/soong/cherry/cherry.go <<'EOF' 226package cherry 227 228import ( 229 "android/soong/android" 230 "github.com/google/blueprint" 231) 232 233var ( 234 pctx = android.NewPackageContext("cherry") 235) 236 237func init() { 238 android.RegisterSingletonType("cherry", CherrySingleton) 239} 240 241func CherrySingleton() android.Singleton { 242 return &cherrySingleton{} 243} 244 245type cherrySingleton struct{} 246 247func (p *cherrySingleton) GenerateBuildActions(ctx android.SingletonContext) { 248 cherryRule := ctx.Rule(pctx, "cherry", 249 blueprint.RuleParams{ 250 Command: "echo CHERRY IS " + ctx.Config().Getenv("CHERRY") + " > ${out}", 251 CommandDeps: []string{}, 252 Description: "Cherry", 253 }) 254 255 outputFile := android.PathForOutput(ctx, "cherry", "cherry.txt") 256 var deps android.Paths 257 258 ctx.Build(pctx, android.BuildParams{ 259 Rule: cherryRule, 260 Output: outputFile, 261 Inputs: deps, 262 }) 263} 264EOF 265 266 export CHERRY=TASTY 267 run_soong 268 grep -q "CHERRY IS TASTY" out/soong/build."${target_product}".ninja \ 269 || fail "first value of environment variable is not used" 270 271 export CHERRY=RED 272 run_soong 273 grep -q "CHERRY IS RED" out/soong/build."${target_product}".ninja \ 274 || fail "second value of environment variable not used" 275 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 276 277 run_soong 278 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 279 if [[ "$mtime1" != "$mtime2" ]]; then 280 fail "Output Ninja file changed when environment variable did not" 281 fi 282 283} 284 285function test_create_global_include_directory() { 286 setup 287 run_soong 288 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 289 290 # Soong needs to know if top level directories like hardware/ exist for use 291 # as global include directories. Make sure that doesn't cause regens for 292 # unrelated changes to the top level directory. 293 mkdir -p system/core 294 295 run_soong 296 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 297 if [[ "$mtime1" != "$mtime2" ]]; then 298 fail "Output Ninja file changed when top level directory changed" 299 fi 300 301 # Make sure it does regen if a missing directory in the path of a global 302 # include directory is added. 303 mkdir -p system/core/include 304 305 run_soong 306 local -r mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja) 307 if [[ "$mtime2" = "$mtime3" ]]; then 308 fail "Output Ninja file did not change when global include directory created" 309 fi 310 311} 312 313function test_add_file_to_soong_build() { 314 setup 315 run_soong 316 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 317 318 mkdir -p vendor/foo/picard 319 cat > vendor/foo/picard/Android.bp <<'EOF' 320bootstrap_go_package { 321 name: "picard-soong-rules", 322 pkgPath: "android/soong/picard", 323 deps: [ 324 "blueprint", 325 "soong", 326 "soong-android", 327 ], 328 srcs: [ 329 "picard.go", 330 ], 331 pluginFor: ["soong_build"], 332} 333EOF 334 335 cat > vendor/foo/picard/picard.go <<'EOF' 336package picard 337 338import ( 339 "android/soong/android" 340 "github.com/google/blueprint" 341) 342 343var ( 344 pctx = android.NewPackageContext("picard") 345) 346 347func init() { 348 android.RegisterSingletonType("picard", PicardSingleton) 349} 350 351func PicardSingleton() android.Singleton { 352 return &picardSingleton{} 353} 354 355type picardSingleton struct{} 356 357func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) { 358 picardRule := ctx.Rule(pctx, "picard", 359 blueprint.RuleParams{ 360 Command: "echo Make it so. > ${out}", 361 CommandDeps: []string{}, 362 Description: "Something quotable", 363 }) 364 365 outputFile := android.PathForOutput(ctx, "picard", "picard.txt") 366 var deps android.Paths 367 368 ctx.Build(pctx, android.BuildParams{ 369 Rule: picardRule, 370 Output: outputFile, 371 Inputs: deps, 372 }) 373} 374 375EOF 376 377 run_soong 378 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 379 if [[ "$mtime1" == "$mtime2" ]]; then 380 fail "Output Ninja file did not change" 381 fi 382 383 grep -q "Make it so" out/soong/build."${target_product}".ninja || fail "New action not present" 384} 385 386# Tests a glob in a build= statement in an Android.bp file, which is interpreted 387# during bootstrapping. 388function test_glob_during_bootstrapping() { 389 setup 390 391 mkdir -p build/soong/picard 392 cat > build/soong/picard/Android.bp <<'EOF' 393build=["foo*.bp"] 394EOF 395 cat > build/soong/picard/fooa.bp <<'EOF' 396bootstrap_go_package { 397 name: "picard-soong-rules", 398 pkgPath: "android/soong/picard", 399 deps: [ 400 "blueprint", 401 "soong", 402 "soong-android", 403 ], 404 srcs: [ 405 "picard.go", 406 ], 407 pluginFor: ["soong_build"], 408} 409EOF 410 411 cat > build/soong/picard/picard.go <<'EOF' 412package picard 413 414import ( 415 "android/soong/android" 416 "github.com/google/blueprint" 417) 418 419var ( 420 pctx = android.NewPackageContext("picard") 421) 422 423func init() { 424 android.RegisterSingletonType("picard", PicardSingleton) 425} 426 427func PicardSingleton() android.Singleton { 428 return &picardSingleton{} 429} 430 431type picardSingleton struct{} 432 433var Message = "Make it so." 434 435func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) { 436 picardRule := ctx.Rule(pctx, "picard", 437 blueprint.RuleParams{ 438 Command: "echo " + Message + " > ${out}", 439 CommandDeps: []string{}, 440 Description: "Something quotable", 441 }) 442 443 outputFile := android.PathForOutput(ctx, "picard", "picard.txt") 444 var deps android.Paths 445 446 ctx.Build(pctx, android.BuildParams{ 447 Rule: picardRule, 448 Output: outputFile, 449 Inputs: deps, 450 }) 451} 452 453EOF 454 455 run_soong 456 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 457 458 grep -q "Make it so" out/soong/build."${target_product}".ninja || fail "Original action not present" 459 460 cat > build/soong/picard/foob.bp <<'EOF' 461bootstrap_go_package { 462 name: "worf-soong-rules", 463 pkgPath: "android/soong/worf", 464 deps: [ 465 "blueprint", 466 "soong", 467 "soong-android", 468 "picard-soong-rules", 469 ], 470 srcs: [ 471 "worf.go", 472 ], 473 pluginFor: ["soong_build"], 474} 475EOF 476 477 cat > build/soong/picard/worf.go <<'EOF' 478package worf 479 480import "android/soong/picard" 481 482func init() { 483 picard.Message = "Engage." 484} 485EOF 486 487 run_soong 488 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 489 if [[ "$mtime1" == "$mtime2" ]]; then 490 fail "Output Ninja file did not change" 491 fi 492 493 grep -q "Engage" out/soong/build."${target_product}".ninja || fail "New action not present" 494 495 if grep -q "Make it so" out/soong/build."${target_product}".ninja; then 496 fail "Original action still present" 497 fi 498} 499 500function test_soong_docs_smoke() { 501 setup 502 503 run_soong soong_docs 504 505 [[ -e "out/soong/docs/soong_build.html" ]] || fail "Documentation for main page not created" 506 [[ -e "out/soong/docs/cc.html" ]] || fail "Documentation for C++ modules not created" 507} 508 509function test_null_build_after_soong_docs() { 510 setup 511 512 run_soong 513 local -r ninja_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 514 515 run_soong soong_docs 516 local -r docs_mtime1=$(stat -c "%y" out/soong/docs/soong_build.html) 517 518 run_soong soong_docs 519 local -r docs_mtime2=$(stat -c "%y" out/soong/docs/soong_build.html) 520 521 if [[ "$docs_mtime1" != "$docs_mtime2" ]]; then 522 fail "Output Ninja file changed on null build" 523 fi 524 525 run_soong 526 local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 527 528 if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then 529 fail "Output Ninja file changed on null build" 530 fi 531} 532 533function test_write_to_source_tree { 534 setup 535 mkdir -p a 536 cat > a/Android.bp <<EOF 537genrule { 538 name: "write_to_source_tree", 539 out: ["write_to_source_tree"], 540 cmd: "touch file_in_source_tree && touch \$(out)", 541} 542EOF 543 readonly EXPECTED_OUT=out/soong/.intermediates/a/write_to_source_tree/gen/write_to_source_tree 544 readonly ERROR_LOG=${MOCK_TOP}/out/error.log 545 readonly ERROR_MSG="Read-only file system" 546 readonly ERROR_HINT_PATTERN="BUILD_BROKEN_SRC_DIR" 547 # Test in ReadOnly source tree 548 run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=false ${EXPECTED_OUT} &> /dev/null && \ 549 fail "Write to source tree should not work in a ReadOnly source tree" 550 551 if grep -q "${ERROR_MSG}" "${ERROR_LOG}" && grep -q "${ERROR_HINT_PATTERN}" "${ERROR_LOG}" ; then 552 echo Error message and error hint found in logs >/dev/null 553 else 554 fail "Did not find Read-only error AND error hint in error.log" 555 fi 556 557 # Test in ReadWrite source tree 558 run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=true ${EXPECTED_OUT} &> /dev/null || \ 559 fail "Write to source tree did not succeed in a ReadWrite source tree" 560 561 if grep -q "${ERROR_MSG}\|${ERROR_HINT_PATTERN}" "${ERROR_LOG}" ; then 562 fail "Found read-only error OR error hint in error.log" 563 fi 564} 565 566function test_dump_json_module_graph() { 567 setup 568 run_soong json-module-graph 569 if [[ ! -r "out/soong/module-graph.json" ]]; then 570 fail "JSON file was not created" 571 fi 572} 573 574function test_json_module_graph_back_and_forth_null_build() { 575 setup 576 577 run_soong 578 local -r ninja_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 579 580 run_soong json-module-graph 581 local -r json_mtime1=$(stat -c "%y" out/soong/module-graph.json) 582 583 run_soong 584 local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 585 if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then 586 fail "Output Ninja file changed after writing JSON module graph" 587 fi 588 589 run_soong json-module-graph 590 local -r json_mtime2=$(stat -c "%y" out/soong/module-graph.json) 591 if [[ "$json_mtime1" != "$json_mtime2" ]]; then 592 fail "JSON module graph file changed after writing Ninja file" 593 fi 594 595} 596 597function test_queryview_null_build() { 598 setup 599 600 run_soong queryview 601 local -r output_mtime1=$(stat -c "%y" out/soong/queryview.marker) 602 603 run_soong queryview 604 local -r output_mtime2=$(stat -c "%y" out/soong/queryview.marker) 605 606 if [[ "$output_mtime1" != "$output_mtime2" ]]; then 607 fail "Queryview marker file changed on null build" 608 fi 609} 610 611# This test verifies that adding a new glob to a blueprint file only 612# causes build."${target_product}".ninja to be regenerated on the *next* build, and *not* 613# the build after. (This is a regression test for a bug where globs 614# resulted in two successive regenerations.) 615function test_new_glob_incrementality { 616 setup 617 618 run_soong nothing 619 local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja) 620 621 mkdir -p globdefpkg/ 622 cat > globdefpkg/Android.bp <<'EOF' 623filegroup { 624 name: "fg_with_glob", 625 srcs: ["*.txt"], 626} 627EOF 628 629 run_soong nothing 630 local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja) 631 632 if [[ "$mtime1" == "$mtime2" ]]; then 633 fail "Ninja file was not regenerated, despite a new bp file" 634 fi 635 636 run_soong nothing 637 local -r mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja) 638 639 if [[ "$mtime2" != "$mtime3" ]]; then 640 fail "Ninja file was regenerated despite no previous bp changes" 641 fi 642} 643 644scan_and_run_tests 645