diff --git a/android/bazel_handler.go b/android/bazel_handler.go index bbec389e9..0595d68a1 100644 --- a/android/bazel_handler.go +++ b/android/bazel_handler.go @@ -717,7 +717,7 @@ func (c *bazelSingleton) GenerateBuildActions(ctx SingletonContext) { // Add ninja file dependencies for files which all bazel invocations require. bazelBuildList := absolutePath(filepath.Join( - filepath.Dir(bootstrap.ModuleListFile), "bazel.list")) + filepath.Dir(bootstrap.CmdlineModuleListFile()), "bazel.list")) ctx.AddNinjaFileDeps(bazelBuildList) data, err := ioutil.ReadFile(bazelBuildList) diff --git a/android/config.go b/android/config.go index cae419b8f..f4685a196 100644 --- a/android/config.go +++ b/android/config.go @@ -506,6 +506,10 @@ func (c *config) SetStopBefore(stopBefore bootstrap.StopBefore) { c.stopBefore = stopBefore } +func (c *config) SetAllowMissingDependencies() { + c.productVariables.Allow_missing_dependencies = proptools.BoolPtr(true) +} + var _ bootstrap.ConfigStopBefore = (*config)(nil) // BlueprintToolLocation returns the directory containing build system tools diff --git a/bootstrap_test.sh b/bootstrap_test.sh new file mode 100755 index 000000000..68067ee87 --- /dev/null +++ b/bootstrap_test.sh @@ -0,0 +1,376 @@ +#!/bin/bash -eu + +# This test exercises the bootstrapping process of the build system +# in a source tree that only contains enough files for Bazel and Soong to work. + +HARDWIRED_MOCK_TOP= +# Uncomment this for to be able to view the source tree after a test is run +# HARDWIRED_MOCK_TOP=/tmp/td + +REAL_TOP="$(readlink -f "$(dirname "$0")"/../..)" + +function fail { + echo ERROR: $1 + exit 1 +} + +function copy_directory() { + local dir="$1" + local parent="$(dirname "$dir")" + + mkdir -p "$MOCK_TOP/$parent" + cp -R "$REAL_TOP/$dir" "$MOCK_TOP/$parent" +} + +function symlink_file() { + local file="$1" + + mkdir -p "$MOCK_TOP/$(dirname "$file")" + ln -s "$REAL_TOP/$file" "$MOCK_TOP/$file" +} + +function symlink_directory() { + local dir="$1" + + mkdir -p "$MOCK_TOP/$dir" + # We need to symlink the contents of the directory individually instead of + # using one symlink for the whole directory because finder.go doesn't follow + # symlinks when looking for Android.bp files + for i in $(ls "$REAL_TOP/$dir"); do + local target="$MOCK_TOP/$dir/$i" + local source="$REAL_TOP/$dir/$i" + + if [[ -e "$target" ]]; then + if [[ ! -d "$source" || ! -d "$target" ]]; then + fail "Trying to symlink $dir twice" + fi + else + ln -s "$REAL_TOP/$dir/$i" "$MOCK_TOP/$dir/$i"; + fi + done +} + +function setup_bazel() { + copy_directory build/bazel + + symlink_directory prebuilts/bazel + symlink_directory prebuilts/jdk + + symlink_file WORKSPACE + symlink_file tools/bazel +} + +function setup() { + if [[ ! -z "$HARDWIRED_MOCK_TOP" ]]; then + MOCK_TOP="$HARDWIRED_MOCK_TOP" + rm -fr "$MOCK_TOP" + mkdir -p "$MOCK_TOP" + else + MOCK_TOP=$(mktemp -t -d st.XXXXX) + trap 'echo cd / && echo rm -fr "$MOCK_TOP"' EXIT + fi + + echo "Test case: ${FUNCNAME[1]}, mock top path: $MOCK_TOP" + cd "$MOCK_TOP" + + copy_directory build/blueprint + copy_directory build/soong + + symlink_directory prebuilts/go + symlink_directory prebuilts/build-tools + symlink_directory external/golang-protobuf + + touch "$MOCK_TOP/Android.bp" + + export ALLOW_MISSING_DEPENDENCIES=true + + mkdir -p out/soong + # This is necessary because the empty soong.variables file written to satisfy + # Ninja would contain "BootJars: {}" instead of "BootJars: []" which cannot + # be parsed back + # TODO(b/182965747): Fix this. + cat > out/soong/soong.variables <<'EOF' +{ + "BuildNumberFile": "build_number.txt", + "Platform_version_name": "S", + "Platform_sdk_version": 30, + "Platform_sdk_codename": "S", + "Platform_sdk_final": false, + "Platform_version_active_codenames": [ + "S" + ], + "Platform_vndk_version": "S", + "DeviceName": "generic_arm64", + "DeviceArch": "arm64", + "DeviceArchVariant": "armv8-a", + "DeviceCpuVariant": "generic", + "DeviceAbi": [ + "arm64-v8a" + ], + "DeviceSecondaryArch": "arm", + "DeviceSecondaryArchVariant": "armv8-a", + "DeviceSecondaryCpuVariant": "generic", + "DeviceSecondaryAbi": [ + "armeabi-v7a", + "armeabi" + ], + "HostArch": "x86_64", + "HostSecondaryArch": "x86", + "CrossHost": "windows", + "CrossHostArch": "x86", + "CrossHostSecondaryArch": "x86_64", + "AAPTCharacteristics": "nosdcard", + "AAPTConfig": [ + "normal", + "large", + "xlarge", + "hdpi", + "xhdpi", + "xxhdpi" + ], + "AAPTPreferredConfig": "xhdpi", + "AAPTPrebuiltDPI": [ + "xhdpi", + "xxhdpi" + ], + "Malloc_not_svelte": true, + "Malloc_zero_contents": true, + "Malloc_pattern_fill_contents": false, + "Safestack": false, + "BootJars": [], + "UpdatableBootJars": [], + "Native_coverage": null +} +EOF +} + +function run_soong() { + build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests +} + +function test_smoke { + setup + run_soong +} + +function test_bazel_smoke { + setup + setup_bazel + + tools/bazel info + +} +function test_null_build() { + setup + run_soong + local bootstrap_mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja) + local output_mtime1=$(stat -c "%y" out/soong/build.ninja) + run_soong + local bootstrap_mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja) + local output_mtime2=$(stat -c "%y" out/soong/build.ninja) + + if [[ "$bootstrap_mtime1" == "$bootstrap_mtime2" ]]; then + # Bootstrapping is always done. It doesn't take a measurable amount of time. + fail "Bootstrap Ninja file did not change on null build" + fi + + if [[ "$output_mtime1" != "$output_mtime2" ]]; then + fail "Output Ninja file changed on null build" + fi +} + +function test_soong_build_rebuilt_if_blueprint_changes() { + setup + run_soong + local mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja) + + sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go + + run_soong + local mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja) + + if [[ "$mtime1" == "$mtime2" ]]; then + fail "Bootstrap Ninja file did not change" + fi +} + +function test_change_android_bp() { + setup + mkdir -p a + cat > a/Android.bp <<'EOF' +python_binary_host { + name: "my_little_binary_host", + srcs: ["my_little_binary_host.py"] +} +EOF + touch a/my_little_binary_host.py + run_soong + + grep -q "^# Module:.*my_little_binary_host" out/soong/build.ninja || fail "module not found" + + cat > a/Android.bp <<'EOF' +python_binary_host { + name: "my_great_binary_host", + srcs: ["my_great_binary_host.py"] +} +EOF + touch a/my_great_binary_host.py + run_soong + + grep -q "^# Module:.*my_little_binary_host" out/soong/build.ninja && fail "old module found" + grep -q "^# Module:.*my_great_binary_host" out/soong/build.ninja || fail "new module not found" +} + + +function test_add_android_bp() { + setup + run_soong + local mtime1=$(stat -c "%y" out/soong/build.ninja) + + mkdir -p a + cat > a/Android.bp <<'EOF' +python_binary_host { + name: "my_little_binary_host", + srcs: ["my_little_binary_host.py"] +} +EOF + touch a/my_little_binary_host.py + run_soong + + local mtime2=$(stat -c "%y" out/soong/build.ninja) + if [[ "$mtime1" == "$mtime2" ]]; then + fail "Output Ninja file did not change" + fi + + grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja || fail "New module not in output" + + run_soong +} + +function test_delete_android_bp() { + setup + mkdir -p a + cat > a/Android.bp <<'EOF' +python_binary_host { + name: "my_little_binary_host", + srcs: ["my_little_binary_host.py"] +} +EOF + touch a/my_little_binary_host.py + run_soong + + grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja || fail "Module not in output" + + rm a/Android.bp + run_soong + + grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja && fail "Old module in output" +} + +function test_add_file_to_glob() { + setup + + mkdir -p a + cat > a/Android.bp <<'EOF' +python_binary_host { + name: "my_little_binary_host", + srcs: ["*.py"], +} +EOF + touch a/my_little_binary_host.py + run_soong + local mtime1=$(stat -c "%y" out/soong/build.ninja) + + touch a/my_little_library.py + run_soong + + local mtime2=$(stat -c "%y" out/soong/build.ninja) + if [[ "$mtime1" == "$mtime2" ]]; then + fail "Output Ninja file did not change" + fi + + grep -q my_little_library.py out/soong/build.ninja || fail "new file is not in output" +} + +function test_add_file_to_soong_build() { + setup + run_soong + local mtime1=$(stat -c "%y" out/soong/build.ninja) + + mkdir -p a + cat > a/Android.bp <<'EOF' +bootstrap_go_package { + name: "picard-soong-rules", + pkgPath: "android/soong/picard", + deps: [ + "blueprint", + "soong", + "soong-android", + ], + srcs: [ + "picard.go", + ], + pluginFor: ["soong_build"], +} +EOF + + cat > a/picard.go <<'EOF' +package picard + +import ( + "android/soong/android" + "github.com/google/blueprint" +) + +var ( + pctx = android.NewPackageContext("picard") +) + +func init() { + android.RegisterSingletonType("picard", PicardSingleton) +} + +func PicardSingleton() android.Singleton { + return &picardSingleton{} +} + +type picardSingleton struct{} + +func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) { + picardRule := ctx.Rule(pctx, "picard", + blueprint.RuleParams{ + Command: "echo Make it so. > ${out}", + CommandDeps: []string{}, + Description: "Something quotable", + }) + + outputFile := android.PathForOutput(ctx, "picard", "picard.txt") + var deps android.Paths + + ctx.Build(pctx, android.BuildParams{ + Rule: picardRule, + Output: outputFile, + Inputs: deps, + }) +} + +EOF + + run_soong + local mtime2=$(stat -c "%y" out/soong/build.ninja) + if [[ "$mtime1" == "$mtime2" ]]; then + fail "Output Ninja file did not change" + fi + + grep -q "Make it so" out/soong/build.ninja || fail "New action not present" +} + +test_bazel_smoke +test_smoke +test_null_build +test_soong_build_rebuilt_if_blueprint_changes +test_add_file_to_glob +test_add_android_bp +test_change_android_bp +test_delete_android_bp +test_add_file_to_soong_build diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go index d96abd9e1..67a4ffb47 100644 --- a/cmd/soong_build/main.go +++ b/cmd/soong_build/main.go @@ -80,7 +80,7 @@ func newContext(configuration android.Config) *android.Context { } func newConfig(srcDir string) android.Config { - configuration, err := android.NewConfig(srcDir, bootstrap.BuildDir, bootstrap.ModuleListFile) + configuration, err := android.NewConfig(srcDir, bootstrap.CmdlineBuildDir(), bootstrap.CmdlineModuleListFile()) if err != nil { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) @@ -101,6 +101,10 @@ func main() { configuration := newConfig(srcDir) extraNinjaDeps := []string{configuration.ProductVariablesFileName} + if configuration.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" { + configuration.SetAllowMissingDependencies() + } + // These two are here so that we restart a non-debugged soong_build when the // user sets SOONG_DELVE the first time. configuration.Getenv("SOONG_DELVE") @@ -167,7 +171,7 @@ func main() { // TODO(ccross): make this a command line argument. Requires plumbing through blueprint // to affect the command line of the primary builder. if shouldPrepareBuildActions(configuration) { - metricsFile := filepath.Join(bootstrap.BuildDir, "soong_build_metrics.pb") + metricsFile := filepath.Join(bootstrap.CmdlineBuildDir(), "soong_build_metrics.pb") err := android.WriteMetrics(configuration, metricsFile) if err != nil { fmt.Fprintf(os.Stderr, "error writing soong_build metrics %s: %s", metricsFile, err) @@ -193,7 +197,7 @@ func runBp2Build(srcDir string, configuration android.Config) { // Android.bp files. It must not depend on the values of per-build product // configurations or variables, since those will generate different BUILD // files based on how the user has configured their tree. - bp2buildCtx.SetModuleListFile(bootstrap.ModuleListFile) + bp2buildCtx.SetModuleListFile(bootstrap.CmdlineModuleListFile()) extraNinjaDeps, err := bp2buildCtx.ListModulePaths(srcDir) if err != nil { panic(err) diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go index 1c5e78ad6..390a9ecdf 100644 --- a/cmd/soong_ui/main.go +++ b/cmd/soong_ui/main.go @@ -70,7 +70,7 @@ var commands []command = []command{ return build.NewConfig(ctx, args...) }, stdio: stdio, - run: make, + run: runMake, }, { flag: "--dumpvar-mode", description: "print the value of the legacy make variable VAR to stdout", @@ -92,7 +92,7 @@ var commands []command = []command{ description: "build modules based on the specified build action", config: buildActionConfig, stdio: stdio, - run: make, + run: runMake, }, } @@ -478,7 +478,7 @@ func buildActionConfig(ctx build.Context, args ...string) build.Config { return build.NewBuildActionConfig(buildAction, *dir, ctx, args...) } -func make(ctx build.Context, config build.Config, _ []string, logsDir string) { +func runMake(ctx build.Context, config build.Config, _ []string, logsDir string) { if config.IsVerbose() { writer := ctx.Writer fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.") diff --git a/ui/build/Android.bp b/ui/build/Android.bp index 32b6edade..d17b4645c 100644 --- a/ui/build/Android.bp +++ b/ui/build/Android.bp @@ -32,6 +32,8 @@ bootstrap_go_package { name: "soong-ui-build", pkgPath: "android/soong/ui/build", deps: [ + "blueprint", + "blueprint-bootstrap", "soong-ui-build-paths", "soong-ui-logger", "soong-ui-metrics", diff --git a/ui/build/build.go b/ui/build/build.go index 215a6c8ce..3692f4fb4 100644 --- a/ui/build/build.go +++ b/ui/build/build.go @@ -218,6 +218,11 @@ func Build(ctx Context, config Config, what int) { what = what &^ BuildKati } + if config.SkipNinja() { + ctx.Verboseln("Skipping Ninja as requested") + what = what &^ BuildNinja + } + if config.StartGoma() { // Ensure start Goma compiler_proxy startGoma(ctx, config) @@ -290,7 +295,7 @@ func Build(ctx Context, config Config, what int) { } // Run ninja - runNinja(ctx, config) + runNinjaForBuild(ctx, config) } // Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last. diff --git a/ui/build/config.go b/ui/build/config.go index 1152cd790..4816d1f15 100644 --- a/ui/build/config.go +++ b/ui/build/config.go @@ -48,6 +48,7 @@ type configImpl struct { dist bool skipConfig bool skipKati bool + skipNinja bool skipSoongTests bool // From the product config @@ -552,6 +553,8 @@ func (c *configImpl) parseArgs(ctx Context, args []string) { if arg == "--make-mode" { } else if arg == "showcommands" { c.verbose = true + } else if arg == "--skip-ninja" { + c.skipNinja = true } else if arg == "--skip-make" { c.skipConfig = true c.skipKati = true @@ -772,6 +775,10 @@ func (c *configImpl) SkipKati() bool { return c.skipKati } +func (c *configImpl) SkipNinja() bool { + return c.skipNinja +} + func (c *configImpl) SkipConfig() bool { return c.skipConfig } diff --git a/ui/build/ninja.go b/ui/build/ninja.go index 779976653..893fd6db8 100644 --- a/ui/build/ninja.go +++ b/ui/build/ninja.go @@ -30,7 +30,7 @@ import ( // Constructs and runs the Ninja command line with a restricted set of // environment variables. It's important to restrict the environment Ninja runs // for hermeticity reasons, and to avoid spurious rebuilds. -func runNinja(ctx Context, config Config) { +func runNinjaForBuild(ctx Context, config Config) { ctx.BeginTrace(metrics.PrimaryNinja, "ninja") defer ctx.EndTrace() diff --git a/ui/build/soong.go b/ui/build/soong.go index 884e95741..a8a65b1ea 100644 --- a/ui/build/soong.go +++ b/ui/build/soong.go @@ -23,6 +23,8 @@ import ( "android/soong/shared" soong_metrics_proto "android/soong/ui/metrics/metrics_proto" + "github.com/google/blueprint" + "github.com/google/blueprint/bootstrap" "github.com/golang/protobuf/proto" "github.com/google/blueprint/microfactory" @@ -42,17 +44,75 @@ func writeEnvironmentFile(ctx Context, envFile string, envDeps map[string]string // This uses Android.bp files and various tools to generate /build.ninja. // -// However, the execution of /build.ninja happens later in build/soong/ui/build/build.go#Build() +// However, the execution of /build.ninja happens later in +// build/soong/ui/build/build.go#Build() // -// We want to rely on as few prebuilts as possible, so there is some bootstrapping here. +// We want to rely on as few prebuilts as possible, so we need to bootstrap +// Soong. The process is as follows: // -// "Microfactory" is a tool for compiling Go code. We use it to build two other tools: -// - minibp, used to generate build.ninja files. This is really build/blueprint/bootstrap/command.go#Main() -// - bpglob, used during incremental builds to identify files in a glob that have changed -// -// In reality, several build.ninja files are generated and/or used during the bootstrapping and build process. -// See build/blueprint/bootstrap/doc.go for more information. +// 1. We use "Microfactory", a simple tool to compile Go code, to build +// first itself, then soong_ui from soong_ui.bash. This binary contains +// parts of soong_build that are needed to build itself. +// 2. This simplified version of soong_build then reads the Blueprint files +// that describe itself and emits .bootstrap/build.ninja that describes +// how to build its full version and use that to produce the final Ninja +// file Soong emits. +// 3. soong_ui executes .bootstrap/build.ninja // +// (After this, Kati is executed to parse the Makefiles, but that's not part of +// bootstrapping Soong) + +// A tiny struct used to tell Blueprint that it's in bootstrap mode. It would +// probably be nicer to use a flag in bootstrap.Args instead. +type BlueprintConfig struct { + srcDir string + buildDir string + ninjaBuildDir string +} + +func (c BlueprintConfig) GeneratingPrimaryBuilder() bool { + return true +} + +func (c BlueprintConfig) SrcDir() string { + return "." +} + +func (c BlueprintConfig) BuildDir() string { + return c.buildDir +} + +func (c BlueprintConfig) NinjaBuildDir() string { + return c.ninjaBuildDir +} + +func bootstrapBlueprint(ctx Context, config Config) { + ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap") + defer ctx.EndTrace() + + var args bootstrap.Args + + args.RunGoTests = !config.skipSoongTests + args.UseValidations = true // Use validations to depend on tests + args.BuildDir = config.SoongOutDir() + args.NinjaBuildDir = config.OutDir() + args.TopFile = "Android.bp" + args.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list") + args.OutFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja") + args.DepFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja.d") + args.GlobFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/soong-build-globs.ninja") + + blueprintCtx := blueprint.NewContext() + blueprintCtx.SetIgnoreUnknownModuleTypes(true) + blueprintConfig := BlueprintConfig{ + srcDir: os.Getenv("TOP"), + buildDir: config.SoongOutDir(), + ninjaBuildDir: config.OutDir(), + } + + bootstrap.RunBlueprint(args, blueprintCtx, blueprintConfig) +} + func runSoong(ctx Context, config Config) { ctx.BeginTrace(metrics.RunSoong, "soong") defer ctx.EndTrace() @@ -63,33 +123,15 @@ func runSoong(ctx Context, config Config) { // unused variables were changed? envFile := filepath.Join(config.SoongOutDir(), "soong.environment.available") - // Use an anonymous inline function for tracing purposes (this pattern is used several times below). - func() { - ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap") - defer ctx.EndTrace() - - // Use validations to depend on tests. - args := []string{"-n"} - - if !config.skipSoongTests { - // Run tests. - args = append(args, "-t") + for _, n := range []string{".bootstrap", ".minibootstrap"} { + dir := filepath.Join(config.SoongOutDir(), n) + if err := os.MkdirAll(dir, 0755); err != nil { + ctx.Fatalf("Cannot mkdir " + dir) } + } - cmd := Command(ctx, config, "blueprint bootstrap", "build/blueprint/bootstrap.bash", args...) - - cmd.Environment.Set("BLUEPRINTDIR", "./build/blueprint") - cmd.Environment.Set("BOOTSTRAP", "./build/blueprint/bootstrap.bash") - cmd.Environment.Set("BUILDDIR", config.SoongOutDir()) - cmd.Environment.Set("GOROOT", "./"+filepath.Join("prebuilts/go", config.HostPrebuiltTag())) - cmd.Environment.Set("BLUEPRINT_LIST_FILE", filepath.Join(config.FileListDir(), "Android.bp.list")) - cmd.Environment.Set("NINJA_BUILDDIR", config.OutDir()) - cmd.Environment.Set("SRCDIR", ".") - cmd.Environment.Set("TOPNAME", "Android.bp") - cmd.Sandbox = soongSandbox - - cmd.RunAndPrintOrFatal() - }() + // This is done unconditionally, but does not take a measurable amount of time + bootstrapBlueprint(ctx, config) soongBuildEnv := config.Environment().Copy() soongBuildEnv.Set("TOP", os.Getenv("TOP")) @@ -105,6 +147,11 @@ func runSoong(ctx Context, config Config) { soongBuildEnv.Set("BAZEL_WORKSPACE", absPath(ctx, ".")) soongBuildEnv.Set("BAZEL_METRICS_DIR", config.BazelMetricsDir()) + // For Soong bootstrapping tests + if os.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" { + soongBuildEnv.Set("ALLOW_MISSING_DEPENDENCIES", "true") + } + err := writeEnvironmentFile(ctx, envFile, soongBuildEnv.AsMap()) if err != nil { ctx.Fatalf("failed to write environment file %s: %s", envFile, err) @@ -129,16 +176,6 @@ func runSoong(ctx Context, config Config) { cfg.TrimPath = absPath(ctx, ".") - func() { - ctx.BeginTrace(metrics.RunSoong, "minibp") - defer ctx.EndTrace() - - minibp := filepath.Join(config.SoongOutDir(), ".minibootstrap/minibp") - if _, err := microfactory.Build(&cfg, minibp, "github.com/google/blueprint/bootstrap/minibp"); err != nil { - ctx.Fatalln("Failed to build minibp:", err) - } - }() - func() { ctx.BeginTrace(metrics.RunSoong, "bpglob") defer ctx.EndTrace() @@ -187,10 +224,6 @@ func runSoong(ctx Context, config Config) { cmd.Sandbox = soongSandbox cmd.RunAndStreamOrFatal() } - - // This build generates .bootstrap/build.ninja, which is used in the next step. - ninja("minibootstrap", ".minibootstrap/build.ninja") - // This build generates /build.ninja, which is used later by build/soong/ui/build/build.go#Build(). ninja("bootstrap", ".bootstrap/build.ninja")