xref: /aosp_15_r20/build/soong/tests/bootstrap_test.sh (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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