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
10function test_smoke {
11  setup
12  run_soong
13}
14
15function test_null_build() {
16  setup
17  run_soong
18  local bootstrap_mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
19  local output_mtime1=$(stat -c "%y" out/soong/build.ninja)
20  run_soong
21  local bootstrap_mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
22  local output_mtime2=$(stat -c "%y" out/soong/build.ninja)
23
24  if [[ "$bootstrap_mtime1" == "$bootstrap_mtime2" ]]; then
25    # Bootstrapping is always done. It doesn't take a measurable amount of time.
26    fail "Bootstrap Ninja file did not change on null build"
27  fi
28
29  if [[ "$output_mtime1" != "$output_mtime2" ]]; then
30    fail "Output Ninja file changed on null build"
31  fi
32}
33
34function test_soong_build_rebuilt_if_blueprint_changes() {
35  setup
36  run_soong
37  local mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
38
39  sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go
40
41  run_soong
42  local mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
43
44  if [[ "$mtime1" == "$mtime2" ]]; then
45    fail "Bootstrap Ninja file did not change"
46  fi
47}
48
49function test_change_android_bp() {
50  setup
51  mkdir -p a
52  cat > a/Android.bp <<'EOF'
53python_binary_host {
54  name: "my_little_binary_host",
55  srcs: ["my_little_binary_host.py"]
56}
57EOF
58  touch a/my_little_binary_host.py
59  run_soong
60
61  grep -q "^# Module:.*my_little_binary_host" out/soong/build.ninja || fail "module not found"
62
63  cat > a/Android.bp <<'EOF'
64python_binary_host {
65  name: "my_great_binary_host",
66  srcs: ["my_great_binary_host.py"]
67}
68EOF
69  touch a/my_great_binary_host.py
70  run_soong
71
72  grep -q "^# Module:.*my_little_binary_host" out/soong/build.ninja && fail "old module found"
73  grep -q "^# Module:.*my_great_binary_host" out/soong/build.ninja || fail "new module not found"
74}
75
76
77function test_add_android_bp() {
78  setup
79  run_soong
80  local mtime1=$(stat -c "%y" out/soong/build.ninja)
81
82  mkdir -p a
83  cat > a/Android.bp <<'EOF'
84python_binary_host {
85  name: "my_little_binary_host",
86  srcs: ["my_little_binary_host.py"]
87}
88EOF
89  touch a/my_little_binary_host.py
90  run_soong
91
92  local mtime2=$(stat -c "%y" out/soong/build.ninja)
93  if [[ "$mtime1" == "$mtime2" ]]; then
94    fail "Output Ninja file did not change"
95  fi
96
97  grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja || fail "New module not in output"
98
99  run_soong
100}
101
102function test_delete_android_bp() {
103  setup
104  mkdir -p a
105  cat > a/Android.bp <<'EOF'
106python_binary_host {
107  name: "my_little_binary_host",
108  srcs: ["my_little_binary_host.py"]
109}
110EOF
111  touch a/my_little_binary_host.py
112  run_soong
113
114  grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja || fail "Module not in output"
115
116  rm a/Android.bp
117  run_soong
118
119  if grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja; then
120    fail "Old module in output"
121  fi
122}
123
124# Test that an incremental build with a glob doesn't rerun soong_build, and
125# only regenerates the globs on the first but not the second incremental build.
126function test_glob_noop_incremental() {
127  setup
128
129  # This test needs to start from a clean build, but setup creates an
130  # initialized tree that has already been built once.  Clear the out
131  # directory to start from scratch (see b/185591972)
132  rm -rf out
133
134  mkdir -p a
135  cat > a/Android.bp <<'EOF'
136python_binary_host {
137  name: "my_little_binary_host",
138  srcs: ["*.py"],
139}
140EOF
141  touch a/my_little_binary_host.py
142  run_soong
143  local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
144
145  local glob_deps_file=out/soong/.primary/globs/0.d
146
147  if [ -e "$glob_deps_file" ]; then
148    fail "Glob deps file unexpectedly written on first build"
149  fi
150
151  run_soong
152  local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
153
154  # There is an ineffiencency in glob that requires bpglob to rerun once for each glob to update
155  # the entry in the .ninja_log.  It doesn't update the output file, but we can detect the rerun
156  # by checking if the deps file was created.
157  if [ ! -e "$glob_deps_file" ]; then
158    fail "Glob deps file missing after second build"
159  fi
160
161  local glob_deps_mtime2=$(stat -c "%y" "$glob_deps_file")
162
163  if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
164    fail "Ninja file rewritten on null incremental build"
165  fi
166
167  run_soong
168  local ninja_mtime3=$(stat -c "%y" out/soong/build.ninja)
169  local glob_deps_mtime3=$(stat -c "%y" "$glob_deps_file")
170
171  if [[ "$ninja_mtime2" != "$ninja_mtime3" ]]; then
172    fail "Ninja file rewritten on null incremental build"
173  fi
174
175  # The bpglob commands should not rerun after the first incremental build.
176  if [[ "$glob_deps_mtime2" != "$glob_deps_mtime3" ]]; then
177    fail "Glob deps file rewritten on second null incremental build"
178  fi
179}
180
181function test_add_file_to_glob() {
182  setup
183
184  mkdir -p a
185  cat > a/Android.bp <<'EOF'
186python_binary_host {
187  name: "my_little_binary_host",
188  srcs: ["*.py"],
189}
190EOF
191  touch a/my_little_binary_host.py
192  run_soong
193  local mtime1=$(stat -c "%y" out/soong/build.ninja)
194
195  touch a/my_little_library.py
196  run_soong
197
198  local mtime2=$(stat -c "%y" out/soong/build.ninja)
199  if [[ "$mtime1" == "$mtime2" ]]; then
200    fail "Output Ninja file did not change"
201  fi
202
203  grep -q my_little_library.py out/soong/build.ninja || fail "new file is not in output"
204}
205
206function test_soong_build_rerun_iff_environment_changes() {
207  setup
208
209  mkdir -p cherry
210  cat > cherry/Android.bp <<'EOF'
211bootstrap_go_package {
212  name: "cherry",
213  pkgPath: "android/soong/cherry",
214  deps: [
215    "blueprint",
216    "soong",
217    "soong-android",
218  ],
219  srcs: [
220    "cherry.go",
221  ],
222  pluginFor: ["soong_build"],
223}
224EOF
225
226  cat > cherry/cherry.go <<'EOF'
227package cherry
228
229import (
230  "android/soong/android"
231  "github.com/google/blueprint"
232)
233
234var (
235  pctx = android.NewPackageContext("cherry")
236)
237
238func init() {
239  android.RegisterSingletonType("cherry", CherrySingleton)
240}
241
242func CherrySingleton() android.Singleton {
243  return &cherrySingleton{}
244}
245
246type cherrySingleton struct{}
247
248func (p *cherrySingleton) GenerateBuildActions(ctx android.SingletonContext) {
249  cherryRule := ctx.Rule(pctx, "cherry",
250    blueprint.RuleParams{
251      Command: "echo CHERRY IS " + ctx.Config().Getenv("CHERRY") + " > ${out}",
252      CommandDeps: []string{},
253      Description: "Cherry",
254    })
255
256  outputFile := android.PathForOutput(ctx, "cherry", "cherry.txt")
257  var deps android.Paths
258
259  ctx.Build(pctx, android.BuildParams{
260    Rule: cherryRule,
261    Output: outputFile,
262    Inputs: deps,
263  })
264}
265EOF
266
267  export CHERRY=TASTY
268  run_soong
269  grep -q "CHERRY IS TASTY" out/soong/build.ninja \
270    || fail "first value of environment variable is not used"
271
272  export CHERRY=RED
273  run_soong
274  grep -q "CHERRY IS RED" out/soong/build.ninja \
275    || fail "second value of environment variable not used"
276  local mtime1=$(stat -c "%y" out/soong/build.ninja)
277
278  run_soong
279  local mtime2=$(stat -c "%y" out/soong/build.ninja)
280  if [[ "$mtime1" != "$mtime2" ]]; then
281    fail "Output Ninja file changed when environment variable did not"
282  fi
283
284}
285
286function test_add_file_to_soong_build() {
287  setup
288  run_soong
289  local mtime1=$(stat -c "%y" out/soong/build.ninja)
290
291  mkdir -p a
292  cat > a/Android.bp <<'EOF'
293bootstrap_go_package {
294  name: "picard-soong-rules",
295  pkgPath: "android/soong/picard",
296  deps: [
297    "blueprint",
298    "soong",
299    "soong-android",
300  ],
301  srcs: [
302    "picard.go",
303  ],
304  pluginFor: ["soong_build"],
305}
306EOF
307
308  cat > a/picard.go <<'EOF'
309package picard
310
311import (
312  "android/soong/android"
313  "github.com/google/blueprint"
314)
315
316var (
317  pctx = android.NewPackageContext("picard")
318)
319
320func init() {
321  android.RegisterSingletonType("picard", PicardSingleton)
322}
323
324func PicardSingleton() android.Singleton {
325  return &picardSingleton{}
326}
327
328type picardSingleton struct{}
329
330func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) {
331  picardRule := ctx.Rule(pctx, "picard",
332    blueprint.RuleParams{
333      Command: "echo Make it so. > ${out}",
334      CommandDeps: []string{},
335      Description: "Something quotable",
336    })
337
338  outputFile := android.PathForOutput(ctx, "picard", "picard.txt")
339  var deps android.Paths
340
341  ctx.Build(pctx, android.BuildParams{
342    Rule: picardRule,
343    Output: outputFile,
344    Inputs: deps,
345  })
346}
347
348EOF
349
350  run_soong
351  local mtime2=$(stat -c "%y" out/soong/build.ninja)
352  if [[ "$mtime1" == "$mtime2" ]]; then
353    fail "Output Ninja file did not change"
354  fi
355
356  grep -q "Make it so" out/soong/build.ninja || fail "New action not present"
357}
358
359# Tests a glob in a build= statement in an Android.bp file, which is interpreted
360# during bootstrapping.
361function test_glob_during_bootstrapping() {
362  setup
363
364  mkdir -p a
365  cat > a/Android.bp <<'EOF'
366build=["foo*.bp"]
367EOF
368  cat > a/fooa.bp <<'EOF'
369bootstrap_go_package {
370  name: "picard-soong-rules",
371  pkgPath: "android/soong/picard",
372  deps: [
373    "blueprint",
374    "soong",
375    "soong-android",
376  ],
377  srcs: [
378    "picard.go",
379  ],
380  pluginFor: ["soong_build"],
381}
382EOF
383
384  cat > a/picard.go <<'EOF'
385package picard
386
387import (
388  "android/soong/android"
389  "github.com/google/blueprint"
390)
391
392var (
393  pctx = android.NewPackageContext("picard")
394)
395
396func init() {
397  android.RegisterSingletonType("picard", PicardSingleton)
398}
399
400func PicardSingleton() android.Singleton {
401  return &picardSingleton{}
402}
403
404type picardSingleton struct{}
405
406var Message = "Make it so."
407
408func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) {
409  picardRule := ctx.Rule(pctx, "picard",
410    blueprint.RuleParams{
411      Command: "echo " + Message + " > ${out}",
412      CommandDeps: []string{},
413      Description: "Something quotable",
414    })
415
416  outputFile := android.PathForOutput(ctx, "picard", "picard.txt")
417  var deps android.Paths
418
419  ctx.Build(pctx, android.BuildParams{
420    Rule: picardRule,
421    Output: outputFile,
422    Inputs: deps,
423  })
424}
425
426EOF
427
428  run_soong
429  local mtime1=$(stat -c "%y" out/soong/build.ninja)
430
431  grep -q "Make it so" out/soong/build.ninja || fail "Original action not present"
432
433  cat > a/foob.bp <<'EOF'
434bootstrap_go_package {
435  name: "worf-soong-rules",
436  pkgPath: "android/soong/worf",
437  deps: [
438    "blueprint",
439    "soong",
440    "soong-android",
441    "picard-soong-rules",
442  ],
443  srcs: [
444    "worf.go",
445  ],
446  pluginFor: ["soong_build"],
447}
448EOF
449
450  cat > a/worf.go <<'EOF'
451package worf
452
453import "android/soong/picard"
454
455func init() {
456   picard.Message = "Engage."
457}
458EOF
459
460  run_soong
461  local mtime2=$(stat -c "%y" out/soong/build.ninja)
462  if [[ "$mtime1" == "$mtime2" ]]; then
463    fail "Output Ninja file did not change"
464  fi
465
466  grep -q "Engage" out/soong/build.ninja || fail "New action not present"
467
468  if grep -q "Make it so" out/soong/build.ninja; then
469    fail "Original action still present"
470  fi
471}
472
473function test_null_build_after_docs {
474  setup
475  run_soong
476  local mtime1=$(stat -c "%y" out/soong/build.ninja)
477
478  prebuilts/build-tools/linux-x86/bin/ninja -f out/soong/build.ninja soong_docs
479  run_soong
480  local mtime2=$(stat -c "%y" out/soong/build.ninja)
481
482  if [[ "$mtime1" != "$mtime2" ]]; then
483    fail "Output Ninja file changed on null build"
484  fi
485}
486
487function test_bp2build_smoke {
488  setup
489  GENERATE_BAZEL_FILES=1 run_soong
490  [[ -e out/soong/.bootstrap/bp2build_workspace_marker ]] || fail "bp2build marker file not created"
491  [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
492}
493
494function test_bp2build_add_android_bp {
495  setup
496
497  mkdir -p a
498  touch a/a.txt
499  cat > a/Android.bp <<'EOF'
500filegroup {
501  name: "a",
502  srcs: ["a.txt"],
503  bazel_module: { bp2build_available: true },
504}
505EOF
506
507  GENERATE_BAZEL_FILES=1 run_soong
508  [[ -e out/soong/bp2build/a/BUILD ]] || fail "a/BUILD not created"
509  [[ -L out/soong/workspace/a/BUILD ]] || fail "a/BUILD not symlinked"
510
511  mkdir -p b
512  touch b/b.txt
513  cat > b/Android.bp <<'EOF'
514filegroup {
515  name: "b",
516  srcs: ["b.txt"],
517  bazel_module: { bp2build_available: true },
518}
519EOF
520
521  GENERATE_BAZEL_FILES=1 run_soong
522  [[ -e out/soong/bp2build/b/BUILD ]] || fail "a/BUILD not created"
523  [[ -L out/soong/workspace/b/BUILD ]] || fail "a/BUILD not symlinked"
524}
525
526function test_bp2build_null_build {
527  setup
528
529  GENERATE_BAZEL_FILES=1 run_soong
530  local mtime1=$(stat -c "%y" out/soong/build.ninja)
531
532  GENERATE_BAZEL_FILES=1 run_soong
533  local mtime2=$(stat -c "%y" out/soong/build.ninja)
534
535  if [[ "$mtime1" != "$mtime2" ]]; then
536    fail "Output Ninja file changed on null build"
537  fi
538}
539
540function test_bp2build_add_to_glob {
541  setup
542
543  mkdir -p a
544  touch a/a1.txt
545  cat > a/Android.bp <<'EOF'
546filegroup {
547  name: "a",
548  srcs: ["*.txt"],
549  bazel_module: { bp2build_available: true },
550}
551EOF
552
553  GENERATE_BAZEL_FILES=1 run_soong
554  grep -q a1.txt out/soong/bp2build/a/BUILD || fail "a1.txt not in BUILD file"
555
556  touch a/a2.txt
557  GENERATE_BAZEL_FILES=1 run_soong
558  grep -q a2.txt out/soong/bp2build/a/BUILD || fail "a2.txt not in BUILD file"
559}
560
561function test_dump_json_module_graph() {
562  setup
563  SOONG_DUMP_JSON_MODULE_GRAPH="$MOCK_TOP/modules.json" run_soong
564  if [[ ! -r "$MOCK_TOP/modules.json" ]]; then
565    fail "JSON file was not created"
566  fi
567}
568
569function test_bp2build_bazel_workspace_structure {
570  setup
571
572  mkdir -p a/b
573  touch a/a.txt
574  touch a/b/b.txt
575  cat > a/b/Android.bp <<'EOF'
576filegroup {
577  name: "b",
578  srcs: ["b.txt"],
579  bazel_module: { bp2build_available: true },
580}
581EOF
582
583  GENERATE_BAZEL_FILES=1 run_soong
584  [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
585  [[ -d out/soong/workspace/a/b ]] || fail "module directory not a directory"
586  [[ -L out/soong/workspace/a/b/BUILD ]] || fail "BUILD file not symlinked"
587  [[ "$(readlink -f out/soong/workspace/a/b/BUILD)" =~ bp2build/a/b/BUILD$ ]] \
588    || fail "BUILD files symlinked at the wrong place"
589  [[ -L out/soong/workspace/a/b/b.txt ]] || fail "a/b/b.txt not symlinked"
590  [[ -L out/soong/workspace/a/a.txt ]] || fail "a/b/a.txt not symlinked"
591  [[ ! -e out/soong/workspace/out ]] || fail "out directory symlinked"
592}
593
594function test_bp2build_bazel_workspace_add_file {
595  setup
596
597  mkdir -p a
598  touch a/a.txt
599  cat > a/Android.bp <<EOF
600filegroup {
601  name: "a",
602  srcs: ["a.txt"],
603  bazel_module: { bp2build_available: true },
604}
605EOF
606
607  GENERATE_BAZEL_FILES=1 run_soong
608
609  touch a/a2.txt  # No reference in the .bp file needed
610  GENERATE_BAZEL_FILES=1 run_soong
611  [[ -L out/soong/workspace/a/a2.txt ]] || fail "a/a2.txt not symlinked"
612}
613
614function test_bp2build_build_file_precedence {
615  setup
616
617  mkdir -p a
618  touch a/a.txt
619  touch a/BUILD
620  cat > a/Android.bp <<EOF
621filegroup {
622  name: "a",
623  srcs: ["a.txt"],
624  bazel_module: { bp2build_available: true },
625}
626EOF
627
628  GENERATE_BAZEL_FILES=1 run_soong
629  [[ -L out/soong/workspace/a/BUILD ]] || fail "BUILD file not symlinked"
630  [[ "$(readlink -f out/soong/workspace/a/BUILD)" =~ bp2build/a/BUILD$ ]] \
631    || fail "BUILD files symlinked to the wrong place"
632}
633
634function test_bp2build_reports_multiple_errors {
635  setup
636
637  mkdir -p a/BUILD
638  touch a/a.txt
639  cat > a/Android.bp <<EOF
640filegroup {
641  name: "a",
642  srcs: ["a.txt"],
643  bazel_module: { bp2build_available: true },
644}
645EOF
646
647  mkdir -p b/BUILD
648  touch b/b.txt
649  cat > b/Android.bp <<EOF
650filegroup {
651  name: "b",
652  srcs: ["b.txt"],
653  bazel_module: { bp2build_available: true },
654}
655EOF
656
657  if GENERATE_BAZEL_FILES=1 run_soong >& "$MOCK_TOP/errors"; then
658    fail "Build should have failed"
659  fi
660
661  grep -q "a/BUILD' exist" "$MOCK_TOP/errors" || fail "Error for a/BUILD not found"
662  grep -q "b/BUILD' exist" "$MOCK_TOP/errors" || fail "Error for b/BUILD not found"
663}
664
665test_smoke
666test_null_build
667test_null_build_after_docs
668test_soong_build_rebuilt_if_blueprint_changes
669test_glob_noop_incremental
670test_add_file_to_glob
671test_add_android_bp
672test_change_android_bp
673test_delete_android_bp
674test_add_file_to_soong_build
675test_glob_during_bootstrapping
676test_soong_build_rerun_iff_environment_changes
677test_dump_json_module_graph
678test_bp2build_smoke
679test_bp2build_null_build
680test_bp2build_add_android_bp
681test_bp2build_add_to_glob
682test_bp2build_bazel_workspace_structure
683test_bp2build_bazel_workspace_add_file
684test_bp2build_build_file_precedence
685test_bp2build_reports_multiple_errors
686