Add --bazel-mode and --bazel-mode-dev

This allows "bazel mixed builds prod mode", in additional to reworking
the mechanism in which mixed builds dev mode is enabled.

As a followup, CI scripts will be migrated to use the new flags, as
USE_BAZEL_ANALYSIS=1 is deprecated.

Test: Manually ran --bazel-mode with an allowlist verifying that the
module alone was enabled
Test: Manually verified --bazel-mode and --bazel-mode-dev cause a build
failure

Change-Id: If0d34360e60452f428b05828f4ec7596b7cb619a
diff --git a/ui/build/build.go b/ui/build/build.go
index f7a2d7b..c61baa1 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -110,6 +110,24 @@
 	RunAllWithBazel = RunProductConfig | RunSoong | RunKati | RunKatiNinja | RunBazel
 )
 
+// checkBazelMode fails the build if there are conflicting arguments for which bazel
+// build mode to use.
+func checkBazelMode(ctx Context, config Config) {
+	// TODO(cparsons): Remove USE_BAZEL_ANALYSIS handling.
+	if config.Environment().IsEnvTrue("USE_BAZEL_ANALYSIS") {
+		if config.bazelProdMode || config.bazelDevMode {
+			ctx.Fatalf("USE_BAZEL_ANALYSIS is deprecated.\n" +
+				"Unset USE_BAZEL_ANALYSIS when using --bazel-mode or --bazel-mode-dev.")
+		} else {
+			config.bazelDevMode = true
+		}
+	}
+	if config.bazelProdMode && config.bazelDevMode {
+		ctx.Fatalf("Conflicting bazel mode.\n" +
+			"Do not specify both --bazel-mode and --bazel-mode-dev")
+	}
+}
+
 // checkProblematicFiles fails the build if existing Android.mk or CleanSpec.mk files are found at the root of the tree.
 func checkProblematicFiles(ctx Context) {
 	files := []string{"Android.mk", "CleanSpec.mk"}
@@ -221,6 +239,8 @@
 
 	defer waitForDist(ctx)
 
+	checkBazelMode(ctx, config)
+
 	// checkProblematicFiles aborts the build if Android.mk or CleanSpec.mk are found at the root of the tree.
 	checkProblematicFiles(ctx)
 
diff --git a/ui/build/config.go b/ui/build/config.go
index e140867..590aeb9 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -99,7 +99,10 @@
 
 	pathReplaced bool
 
-	useBazel bool
+	// TODO(b/243077098): Remove useBazel.
+	useBazel      bool
+	bazelProdMode bool
+	bazelDevMode  bool
 
 	// During Bazel execution, Bazel cannot write outside OUT_DIR.
 	// So if DIST_DIR is set to an external dir (outside of OUT_DIR), we need to rig it temporarily and then migrate files at the end of the build.
@@ -130,18 +133,6 @@
 	BUILD_MODULES
 )
 
-type bazelBuildMode int
-
-// Bazel-related build modes.
-const (
-	// Don't use bazel at all.
-	noBazel bazelBuildMode = iota
-
-	// Generate synthetic build files and incorporate these files into a build which
-	// partially uses Bazel. Build metadata may come from Android.bp or BUILD files.
-	mixedBuild
-)
-
 // checkTopDir validates that the current directory is at the root directory of the source tree.
 func checkTopDir(ctx Context) {
 	if _, err := os.Stat(srcDirFileCheck); err != nil {
@@ -496,7 +487,7 @@
 		UseGoma:         proto.Bool(config.UseGoma()),
 		UseRbe:          proto.Bool(config.UseRBE()),
 		BazelAsNinja:    proto.Bool(config.UseBazel()),
-		BazelMixedBuild: proto.Bool(config.bazelBuildMode() == mixedBuild),
+		BazelMixedBuild: proto.Bool(config.BazelBuildEnabled()),
 	}
 	c.Targets = append(c.Targets, config.arguments...)
 
@@ -747,6 +738,10 @@
 			c.skipSoongTests = true
 		} else if arg == "--mk-metrics" {
 			c.reportMkMetrics = true
+		} else if arg == "--bazel-mode" {
+			c.bazelProdMode = true
+		} else if arg == "--bazel-mode-dev" {
+			c.bazelDevMode = true
 		} else if len(arg) > 0 && arg[0] == '-' {
 			parseArgNum := func(def int) int {
 				if len(arg) > 2 {
@@ -1134,16 +1129,13 @@
 	return false
 }
 
+// TODO(b/243077098): Remove UseBazel.
 func (c *configImpl) UseBazel() bool {
 	return c.useBazel
 }
 
-func (c *configImpl) bazelBuildMode() bazelBuildMode {
-	if c.Environment().IsEnvTrue("USE_BAZEL_ANALYSIS") {
-		return mixedBuild
-	} else {
-		return noBazel
-	}
+func (c *configImpl) BazelBuildEnabled() bool {
+	return c.bazelProdMode || c.bazelDevMode
 }
 
 func (c *configImpl) StartRBE() bool {
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index e293275..63716b0 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -28,6 +28,7 @@
 	"android/soong/ui/logger"
 	smpb "android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/status"
+	"google.golang.org/protobuf/encoding/prototext"
 
 	"google.golang.org/protobuf/proto"
 )
@@ -1005,6 +1006,8 @@
 		environ             Environment
 		arguments           []string
 		useBazel            bool
+		bazelDevMode        bool
+		bazelProdMode       bool
 		expectedBuildConfig *smpb.BuildConfig
 	}{
 		{
@@ -1064,7 +1067,7 @@
 			},
 		},
 		{
-			name:    "bazel mixed build",
+			name:    "bazel mixed build from env",
 			environ: Environment{"USE_BAZEL_ANALYSIS=1"},
 			expectedBuildConfig: &smpb.BuildConfig{
 				ForceUseGoma:    proto.Bool(false),
@@ -1075,6 +1078,30 @@
 			},
 		},
 		{
+			name:         "bazel mixed build from dev mode",
+			environ:      Environment{},
+			bazelDevMode: true,
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(false),
+				BazelMixedBuild: proto.Bool(true),
+			},
+		},
+		{
+			name:          "bazel mixed build from prod mode",
+			environ:       Environment{},
+			bazelProdMode: true,
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(false),
+				BazelMixedBuild: proto.Bool(true),
+			},
+		},
+		{
 			name:      "specified targets",
 			environ:   Environment{},
 			useBazel:  true,
@@ -1094,9 +1121,9 @@
 				"FORCE_USE_GOMA=1",
 				"USE_GOMA=1",
 				"USE_RBE=1",
-				"USE_BAZEL_ANALYSIS=1",
 			},
-			useBazel: true,
+			useBazel:     true,
+			bazelDevMode: true,
 			expectedBuildConfig: &smpb.BuildConfig{
 				ForceUseGoma:    proto.Bool(true),
 				UseGoma:         proto.Bool(true),
@@ -1107,17 +1134,23 @@
 		},
 	}
 
+	ctx := testContext()
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			c := &configImpl{
-				environ:   &tc.environ,
-				useBazel:  tc.useBazel,
-				arguments: tc.arguments,
+				environ:       &tc.environ,
+				useBazel:      tc.useBazel,
+				bazelDevMode:  tc.bazelDevMode,
+				bazelProdMode: tc.bazelProdMode,
+				arguments:     tc.arguments,
 			}
 			config := Config{c}
+			checkBazelMode(ctx, config)
 			actualBuildConfig := buildConfig(config)
 			if expected := tc.expectedBuildConfig; !proto.Equal(expected, actualBuildConfig) {
-				t.Errorf("Expected build config != actual build config: %#v != %#v", *expected, *actualBuildConfig)
+				t.Errorf("Build config mismatch.\n"+
+					"Expected build config: %#v\n"+
+					"Actual build config: %#v", prototext.Format(expected), prototext.Format(actualBuildConfig))
 			}
 		})
 	}
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 3920ddd..2e94a46 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -254,6 +254,12 @@
 	if config.EmptyNinjaFile() {
 		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--empty-ninja-file")
 	}
+	if config.bazelProdMode {
+		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--bazel-mode")
+	}
+	if config.bazelDevMode {
+		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--bazel-mode-dev")
+	}
 
 	mainSoongBuildInvocation := primaryBuilderInvocation(
 		config,
@@ -263,7 +269,7 @@
 		fmt.Sprintf("analyzing Android.bp files and generating ninja file at %s", config.SoongNinjaFile()),
 	)
 
-	if config.bazelBuildMode() == mixedBuild {
+	if config.BazelBuildEnabled() {
 		// Mixed builds call Bazel from soong_build and they therefore need the
 		// Bazel workspace to be available. Make that so by adding a dependency on
 		// the bp2build marker file to the action that invokes soong_build .
@@ -373,9 +379,6 @@
 	// unused variables were changed?
 	envFile := filepath.Join(config.SoongOutDir(), availableEnvFile)
 
-	buildMode := config.bazelBuildMode()
-	integratedBp2Build := buildMode == mixedBuild
-
 	// This is done unconditionally, but does not take a measurable amount of time
 	bootstrapBlueprint(ctx, config)
 
@@ -405,7 +408,7 @@
 
 		checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(soongBuildTag))
 
-		if integratedBp2Build || config.Bp2Build() {
+		if config.BazelBuildEnabled() || config.Bp2Build() {
 			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(bp2buildTag))
 		}