Merge "Make cmake_snapshot output reproducible" into main
diff --git a/android/module.go b/android/module.go
index e2c24bf..c08d2f4 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1234,17 +1234,28 @@
 		// the special tag name which represents that.
 		tag := proptools.StringDefault(dist.Tag, DefaultDistTag)
 
+		distFileForTagFromProvider, err := outputFilesForModuleFromProvider(ctx, m.module, tag)
+		if err != OutputFilesProviderNotSet {
+			if err != nil && tag != DefaultDistTag {
+				ctx.PropertyErrorf("dist.tag", "%s", err.Error())
+			} else {
+				distFiles = distFiles.addPathsForTag(tag, distFileForTagFromProvider...)
+				continue
+			}
+		}
+
+		// if the tagged dist file cannot be obtained from OutputFilesProvider,
+		// fall back to use OutputFileProducer
+		// TODO: remove this part after OutputFilesProvider fully replaces OutputFileProducer
 		if outputFileProducer, ok := m.module.(OutputFileProducer); ok {
 			// Call the OutputFiles(tag) method to get the paths associated with the tag.
 			distFilesForTag, err := outputFileProducer.OutputFiles(tag)
-
 			// If the tag was not supported and is not DefaultDistTag then it is an error.
 			// Failing to find paths for DefaultDistTag is not an error. It just means
 			// that the module type requires the legacy behavior.
 			if err != nil && tag != DefaultDistTag {
 				ctx.PropertyErrorf("dist.tag", "%s", err.Error())
 			}
-
 			distFiles = distFiles.addPathsForTag(tag, distFilesForTag...)
 		} else if tag != DefaultDistTag {
 			// If the tag was specified then it is an error if the module does not
@@ -2513,7 +2524,7 @@
 
 func outputFilesForModule(ctx PathContext, module blueprint.Module, tag string) (Paths, error) {
 	outputFilesFromProvider, err := outputFilesForModuleFromProvider(ctx, module, tag)
-	if outputFilesFromProvider != nil || err != nil {
+	if outputFilesFromProvider != nil || err != OutputFilesProviderNotSet {
 		return outputFilesFromProvider, err
 	}
 	if outputFileProducer, ok := module.(OutputFileProducer); ok {
@@ -2541,38 +2552,38 @@
 // reading OutputFilesProvider before GenerateBuildActions is finished.
 // If a module doesn't have the OutputFilesProvider, nil is returned.
 func outputFilesForModuleFromProvider(ctx PathContext, module blueprint.Module, tag string) (Paths, error) {
-	var outputFilesProvider OutputFilesInfo
+	var outputFiles OutputFilesInfo
+	fromProperty := false
 
 	if mctx, isMctx := ctx.(ModuleContext); isMctx {
 		if mctx.Module() != module {
-			outputFilesProvider, _ = OtherModuleProvider(mctx, module, OutputFilesProvider)
+			outputFiles, _ = OtherModuleProvider(mctx, module, OutputFilesProvider)
 		} else {
-			if tag == "" {
-				return mctx.Module().base().outputFiles.DefaultOutputFiles, nil
-			} else if taggedOutputFiles, hasTag := mctx.Module().base().outputFiles.TaggedOutputFiles[tag]; hasTag {
-				return taggedOutputFiles, nil
-			} else {
-				return nil, fmt.Errorf("unsupported tag %q for module getting its own output files", tag)
-			}
+			outputFiles = mctx.Module().base().outputFiles
+			fromProperty = true
 		}
 	} else if cta, isCta := ctx.(*singletonContextAdaptor); isCta {
 		providerData, _ := cta.moduleProvider(module, OutputFilesProvider)
-		outputFilesProvider, _ = providerData.(OutputFilesInfo)
+		outputFiles, _ = providerData.(OutputFilesInfo)
 	}
 	// TODO: Add a check for skipped context
 
-	if !outputFilesProvider.isEmpty() {
-		if tag == "" {
-			return outputFilesProvider.DefaultOutputFiles, nil
-		} else if taggedOutputFiles, hasTag := outputFilesProvider.TaggedOutputFiles[tag]; hasTag {
-			return taggedOutputFiles, nil
+	if outputFiles.isEmpty() {
+		// TODO: Add a check for param module not having OutputFilesProvider set
+		return nil, OutputFilesProviderNotSet
+	}
+
+	if tag == "" {
+		return outputFiles.DefaultOutputFiles, nil
+	} else if taggedOutputFiles, hasTag := outputFiles.TaggedOutputFiles[tag]; hasTag {
+		return taggedOutputFiles, nil
+	} else {
+		if fromProperty {
+			return nil, fmt.Errorf("unsupported tag %q for module getting its own output files", tag)
 		} else {
 			return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 		}
 	}
-
-	// TODO: Add a check for param module not having OutputFilesProvider set
-	return nil, nil
 }
 
 func (o OutputFilesInfo) isEmpty() bool {
@@ -2589,6 +2600,9 @@
 
 var OutputFilesProvider = blueprint.NewProvider[OutputFilesInfo]()
 
+// This is used to mark the case where OutputFilesProvider is not set on some modules.
+var OutputFilesProviderNotSet = fmt.Errorf("No output files from provider")
+
 // Modules can implement HostToolProvider and return a valid OptionalPath from HostToolPath() to
 // specify that they can be used as a tool by a genrule module.
 type HostToolProvider interface {
diff --git a/android/testing.go b/android/testing.go
index 8dd467d..18fd3b3 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -1025,9 +1025,12 @@
 // otherwise returns the result of calling Paths.RelativeToTop
 // on the returned Paths.
 func (m TestingModule) OutputFiles(t *testing.T, tag string) Paths {
-	// TODO: add non-empty-string tag case and remove OutputFileProducer part
-	if tag == "" && m.module.base().outputFiles.DefaultOutputFiles != nil {
-		return m.module.base().outputFiles.DefaultOutputFiles.RelativeToTop()
+	// TODO: remove OutputFileProducer part
+	outputFiles := m.Module().base().outputFiles
+	if tag == "" && outputFiles.DefaultOutputFiles != nil {
+		return outputFiles.DefaultOutputFiles.RelativeToTop()
+	} else if taggedOutputFiles, hasTag := outputFiles.TaggedOutputFiles[tag]; hasTag {
+		return taggedOutputFiles
 	}
 
 	producer, ok := m.module.(OutputFileProducer)
diff --git a/apex/apex.go b/apex/apex.go
index 9fa48a8..e6815bc 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1172,6 +1172,7 @@
 		"test_com.android.os.statsd",
 		"test_com.android.permission",
 		"test_com.android.wifi",
+		"test_imgdiag_com.android.art",
 		"test_jitzygote_com.android.art",
 		// go/keep-sorted end
 	}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 3c8700d..3bb3966 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -10037,6 +10037,56 @@
 	}
 }
 
+func TestApexLintBcpFragmentSdkLibDeps(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: ["mybootclasspathfragment"],
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: ["foo"],
+			apex_available: ["myapex"],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "foo",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "29",
+			compile_dex: true,
+		}
+		`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.PrepareForTestWithJacocoInstrumentation,
+		java.FixtureWithLastReleaseApis("foo"),
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.SetApiLibraries([]string{"foo"})
+		}),
+		android.FixtureMergeMockFs(fs),
+	).RunTestWithBp(t, bp)
+
+	myapex := result.ModuleForTests("myapex", "android_common_myapex")
+	lintReportInputs := strings.Join(myapex.Output("lint-report-xml.zip").Inputs.Strings(), " ")
+	android.AssertStringDoesContain(t,
+		"myapex lint report expected to contain that of the sdk library impl lib as an input",
+		lintReportInputs, "foo.impl")
+}
+
 // updatable apexes should propagate updatable=true to its apps
 func TestUpdatableApexEnforcesAppUpdatability(t *testing.T) {
 	bp := `
diff --git a/cc/binary.go b/cc/binary.go
index 3ff35de..2ac9a45 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -451,7 +451,7 @@
 }
 
 func (binary *binaryDecorator) strippedAllOutputFilePath() android.Path {
-	panic("Not implemented.")
+	return nil
 }
 
 func (binary *binaryDecorator) setSymlinkList(ctx ModuleContext) {
diff --git a/cc/cc.go b/cc/cc.go
index d8fe319..3a8932c 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -2119,10 +2119,23 @@
 		if c.Properties.IsSdkVariant && c.Properties.SdkAndPlatformVariantVisibleToMake {
 			moduleInfoJSON.Uninstallable = true
 		}
-
 	}
 
 	buildComplianceMetadataInfo(ctx, c, deps)
+
+	c.setOutputFiles(ctx)
+}
+
+func (c *Module) setOutputFiles(ctx ModuleContext) {
+	if c.outputFile.Valid() {
+		ctx.SetOutputFiles(android.Paths{c.outputFile.Path()}, "")
+	} else {
+		ctx.SetOutputFiles(android.Paths{}, "")
+	}
+	if c.linker != nil {
+		ctx.SetOutputFiles(android.PathsIfNonNil(c.linker.unstrippedOutputFilePath()), "unstripped")
+		ctx.SetOutputFiles(android.PathsIfNonNil(c.linker.strippedAllOutputFilePath()), "stripped_all")
+	}
 }
 
 func buildComplianceMetadataInfo(ctx ModuleContext, c *Module, deps PathDeps) {
@@ -3615,28 +3628,6 @@
 	return c.outputFile
 }
 
-func (c *Module) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		if c.outputFile.Valid() {
-			return android.Paths{c.outputFile.Path()}, nil
-		}
-		return android.Paths{}, nil
-	case "unstripped":
-		if c.linker != nil {
-			return android.PathsIfNonNil(c.linker.unstrippedOutputFilePath()), nil
-		}
-		return nil, nil
-	case "stripped_all":
-		if c.linker != nil {
-			return android.PathsIfNonNil(c.linker.strippedAllOutputFilePath()), nil
-		}
-		return nil, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (c *Module) static() bool {
 	if static, ok := c.linker.(interface {
 		static() bool
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 776d133..ccdaae5 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -300,13 +300,9 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
-	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Errorf("Expected cc_test to produce output files, error: %s", err)
-		return
-	}
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	testBinary := testingModule.Module().(*Module).linker.(*testBinary)
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Errorf("expected exactly one output file. output files: [%s]", outputFiles)
 		return
@@ -356,12 +352,10 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	module := testingModule.Module()
 	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected cc_test to produce output files, error: %s", err)
-	}
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
@@ -1407,12 +1401,10 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	module := testingModule.Module()
 	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected cc_test to produce output files, error: %s", err)
-	}
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Errorf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
@@ -3118,12 +3110,8 @@
  `
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("test_lib", "android_arm_armv7-a-neon_shared").Module()
-	outputFile, err := module.(android.OutputFileProducer).OutputFiles("stripped_all")
-	if err != nil {
-		t.Errorf("Expected cc_library to produce output files, error: %s", err)
-		return
-	}
+	testingModule := ctx.ModuleForTests("test_lib", "android_arm_armv7-a-neon_shared")
+	outputFile := testingModule.OutputFiles(t, "stripped_all")
 	if !strings.HasSuffix(outputFile.Strings()[0], "/stripped_all/test_lib.so") {
 		t.Errorf("Unexpected output file: %s", outputFile.Strings()[0])
 		return
diff --git a/cc/object.go b/cc/object.go
index 6c0391f..8b23295 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -220,7 +220,7 @@
 }
 
 func (object *objectLinker) strippedAllOutputFilePath() android.Path {
-	panic("Not implemented.")
+	return nil
 }
 
 func (object *objectLinker) nativeCoverage() bool {
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 8599003..0c77968 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -71,12 +71,15 @@
 	return targetSdkVersionLevel.IsPreview() && (ctx.Config().UnbundledBuildApps() || includedInMts(ctx.Module()))
 }
 
-// Helper function that casts android.Module to java.androidTestApp
-// If this type conversion is possible, it queries whether the test app is included in an MTS suite
+// Helper function that returns true if android_test, android_test_helper_app, java_test are in an MTS suite.
 func includedInMts(module android.Module) bool {
 	if test, ok := module.(androidTestApp); ok {
 		return test.includedInTestSuite("mts")
 	}
+	// java_test
+	if test, ok := module.(*Test); ok {
+		return android.PrefixInList(test.testProperties.Test_suites, "mts")
+	}
 	return false
 }
 
diff --git a/java/app.go b/java/app.go
index 739ef1a..f35e4c3 100644
--- a/java/app.go
+++ b/java/app.go
@@ -345,7 +345,35 @@
 	}
 }
 
+// TODO(b/156476221): Remove this allowlist
+var (
+	missingMinSdkVersionMtsAllowlist = []string{
+		"CellBroadcastReceiverGoogleUnitTests",
+		"CellBroadcastReceiverUnitTests",
+		"CtsBatterySavingTestCases",
+		"CtsDeviceAndProfileOwnerApp23",
+		"CtsDeviceAndProfileOwnerApp30",
+		"CtsIntentSenderApp",
+		"CtsJobSchedulerTestCases",
+		"CtsMimeMapTestCases",
+		"CtsTareTestCases",
+		"LibStatsPullTests",
+		"MediaProviderClientTests",
+		"TeleServiceTests",
+		"TestExternalImsServiceApp",
+		"TestSmsRetrieverApp",
+		"TetheringPrivilegedTests",
+	}
+)
+
+func checkMinSdkVersionMts(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
+	if includedInMts(ctx.Module()) && !minSdkVersion.Specified() && !android.InList(ctx.ModuleName(), missingMinSdkVersionMtsAllowlist) {
+		ctx.PropertyErrorf("min_sdk_version", "min_sdk_version is a required property for tests included in MTS")
+	}
+}
+
 func (a *AndroidTestHelperApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, a.MinSdkVersion(ctx))
 	applicationId := a.appTestHelperAppProperties.Manifest_values.ApplicationId
 	if applicationId != nil {
 		if a.overridableAppProperties.Package_name != nil {
@@ -1366,6 +1394,7 @@
 }
 
 func (a *AndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, a.MinSdkVersion(ctx))
 	var configs []tradefed.Config
 	if a.appTestProperties.Instrumentation_target_package != nil {
 		a.additionalAaptFlags = append(a.additionalAaptFlags,
diff --git a/java/app_test.go b/java/app_test.go
index 1a862fa..9e2d19e 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -4146,6 +4146,7 @@
 	bpTemplate := `
 	%v {
 		name: "mytest",
+		min_sdk_version: "34",
 		target_sdk_version: "%v",
 		test_suites: ["othersuite", "%v"],
 	}
diff --git a/java/java.go b/java/java.go
index 6fee7ce..a2fc5fb 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1510,6 +1510,7 @@
 }
 
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, j.MinSdkVersion(ctx))
 	j.generateAndroidBuildActionsWithConfig(ctx, nil)
 	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 82f6cb5..e6cb6c4 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -118,9 +118,6 @@
 	// The tag to use to depend on the stubs source module (if separate from the API module).
 	stubsSourceTag scopeDependencyTag
 
-	// The tag to use to depend on the API file generating module (if separate from the stubs source module).
-	apiFileTag scopeDependencyTag
-
 	// The tag to use to depend on the stubs source and API module.
 	stubsSourceAndApiTag scopeDependencyTag
 
@@ -195,11 +192,6 @@
 		apiScope:         scope,
 		depInfoExtractor: (*scopePaths).extractStubsSourceInfoFromDep,
 	}
-	scope.apiFileTag = scopeDependencyTag{
-		name:             name + "-api",
-		apiScope:         scope,
-		depInfoExtractor: (*scopePaths).extractApiInfoFromDep,
-	}
 	scope.stubsSourceAndApiTag = scopeDependencyTag{
 		name:             name + "-stubs-source-and-api",
 		apiScope:         scope,
@@ -804,12 +796,6 @@
 	return combinedError
 }
 
-func (paths *scopePaths) extractApiInfoFromDep(ctx android.ModuleContext, dep android.Module) error {
-	return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-		return paths.extractApiInfoFromApiStubsProvider(provider, Everything)
-	})
-}
-
 func (paths *scopePaths) extractStubsSourceInfoFromApiStubsProviders(provider ApiStubsSrcProvider, stubsType StubsType) error {
 	stubsSrcJar, err := provider.StubsSrcJar(stubsType)
 	if err == nil {
@@ -819,22 +805,23 @@
 }
 
 func (paths *scopePaths) extractStubsSourceInfoFromDep(ctx android.ModuleContext, dep android.Module) error {
+	stubsType := Everything
+	if ctx.Config().ReleaseHiddenApiExportableStubs() {
+		stubsType = Exportable
+	}
 	return paths.treatDepAsApiStubsSrcProvider(dep, func(provider ApiStubsSrcProvider) error {
-		return paths.extractStubsSourceInfoFromApiStubsProviders(provider, Everything)
+		return paths.extractStubsSourceInfoFromApiStubsProviders(provider, stubsType)
 	})
 }
 
 func (paths *scopePaths) extractStubsSourceAndApiInfoFromApiStubsProvider(ctx android.ModuleContext, dep android.Module) error {
+	stubsType := Everything
 	if ctx.Config().ReleaseHiddenApiExportableStubs() {
-		return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-			extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, Exportable)
-			extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, Exportable)
-			return errors.Join(extractApiInfoErr, extractStubsSourceInfoErr)
-		})
+		stubsType = Exportable
 	}
 	return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-		extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, Everything)
-		extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, Everything)
+		extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, stubsType)
+		extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, stubsType)
 		return errors.Join(extractApiInfoErr, extractStubsSourceInfoErr)
 	})
 }
diff --git a/scripts/buildinfo.py b/scripts/buildinfo.py
index 3383bf7..db99209 100755
--- a/scripts/buildinfo.py
+++ b/scripts/buildinfo.py
@@ -68,6 +68,7 @@
   option.build_id = product_config["BuildId"]
   option.build_type = product_config["BuildType"]
   option.build_variant = get_build_variant(product_config)
+  option.build_version_tags = product_config["BuildVersionTags"]
   option.cpu_abis = product_config["DeviceAbi"]
   option.default_locale = None
   if len(product_config.get("ProductLocales", [])) > 0:
@@ -96,9 +97,11 @@
 
   build_hostname = option.build_hostname_file.read().strip()
   build_number = option.build_number_file.read().strip()
-  build_version_tags = option.build_keys
+  build_version_tags_list = option.build_version_tags
   if option.build_type == "debug":
-    build_version_tags = "debug," + build_version_tags
+    build_version_tags_list.append("debug")
+  build_version_tags_list.append(option.build_keys)
+  build_version_tags = ",".join(sorted(set(build_version_tags_list)))
 
   raw_date = option.date_file.read().strip()
   date = subprocess.check_output(["date", "-d", f"@{raw_date}"], text=True).strip()
diff --git a/scripts/gen_build_prop.py b/scripts/gen_build_prop.py
index 6c02906..42e3207 100644
--- a/scripts/gen_build_prop.py
+++ b/scripts/gen_build_prop.py
@@ -73,9 +73,13 @@
   config["BuildHostname"] = args.build_hostname_file.read().strip()
   config["BuildNumber"] = args.build_number_file.read().strip()
   config["BuildUsername"] = args.build_username
-  config["BuildVersionTags"] = config["BuildKeys"]
+
+  build_version_tags_list = config["BuildVersionTags"]
   if config["BuildType"] == "debug":
-    config["BuildVersionTags"] = "debug," + config["BuildVersionTags"]
+    build_version_tags_list.append("debug")
+  build_version_tags_list.append(config["BuildKeys"])
+  build_version_tags = ",".join(sorted(set(build_version_tags_list)))
+  config["BuildVersionTags"] = build_version_tags
 
   raw_date = args.date_file.read().strip()
   config["Date"] = subprocess.check_output(["date", "-d", f"@{raw_date}"], text=True).strip()
diff --git a/ui/build/config.go b/ui/build/config.go
index c4a6797..1d5269c 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -99,9 +99,10 @@
 	// Autodetected
 	totalRAM uint64
 
-	brokenDupRules     bool
-	brokenUsesNetwork  bool
-	brokenNinjaEnvVars []string
+	brokenDupRules       bool
+	brokenUsesNetwork    bool
+	brokenNinjaEnvVars   []string
+	brokenMissingOutputs bool
 
 	pathReplaced bool
 
@@ -1608,6 +1609,14 @@
 	return c.brokenNinjaEnvVars
 }
 
+func (c *configImpl) SetBuildBrokenMissingOutputs(val bool) {
+	c.brokenMissingOutputs = val
+}
+
+func (c *configImpl) BuildBrokenMissingOutputs() bool {
+	return c.brokenMissingOutputs
+}
+
 func (c *configImpl) SetTargetDeviceDir(dir string) {
 	c.targetDeviceDir = dir
 }
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index eba86a0..e77df44 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -235,6 +235,11 @@
 		"BUILD_BROKEN_SRC_DIR_IS_WRITABLE",
 		"BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST",
 
+		// Whether missing outputs should be treated as warnings
+		// instead of errors.
+		// `true` will relegate missing outputs to warnings.
+		"BUILD_BROKEN_MISSING_OUTPUTS",
+
 		// Not used, but useful to be in the soong.log
 		"TARGET_BUILD_TYPE",
 		"HOST_ARCH",
@@ -301,4 +306,5 @@
 	config.SetBuildBrokenUsesNetwork(makeVars["BUILD_BROKEN_USES_NETWORK"] == "true")
 	config.SetBuildBrokenNinjaUsesEnvVars(strings.Fields(makeVars["BUILD_BROKEN_NINJA_USES_ENV_VARS"]))
 	config.SetSourceRootDirs(strings.Fields(makeVars["PRODUCT_SOURCE_ROOT_DIRS"]))
+	config.SetBuildBrokenMissingOutputs(makeVars["BUILD_BROKEN_MISSING_OUTPUTS"] == "true")
 }
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 551b8ab..ae27330 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -77,6 +77,14 @@
 		"-w", "dupbuild=err",
 		"-w", "missingdepfile=err")
 
+	if !config.BuildBrokenMissingOutputs() {
+		// Missing outputs will be treated as errors.
+		// BUILD_BROKEN_MISSING_OUTPUTS can be used to bypass this check.
+		args = append(args,
+			"-w", "missingoutfile=err",
+		)
+	}
+
 	cmd := Command(ctx, config, "ninja", executable, args...)
 
 	// Set up the nsjail sandbox Ninja runs in.