Merge "Use OutputFilesProvider on prebuilt_etc" into main
diff --git a/Android.bp b/Android.bp
index b1db8e9..2c0ef47 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,6 +104,7 @@
 // Instantiate the dex_bootjars singleton module.
 dex_bootjars {
     name: "dex_bootjars",
+    no_full_install: true,
 }
 
 // Pseudo-test that's run on checkbuilds to ensure that get_clang_version can
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index af9123e..919cb01 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -1366,4 +1366,89 @@
 	android.AssertStringDoesContain(t, "test", command, "--test-stub-classpath="+nonUpdatableTestStubs)
 }
 
+func TestBootclasspathFragmentProtoContainsMinSdkVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootclasspathFragment,
+		prepareForTestWithMyapex,
+		// Configure bootclasspath jars to ensure that hidden API encoding is performed on them.
+		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
+		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
+		// is disabled.
+		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
+
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo", "bar"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_sdk_library {
+			name: "foo",
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {enabled: true},
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "33",
+		}
+
+		java_sdk_library {
+			name: "bar",
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {enabled: true},
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "34",
+		}
+
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: [
+				"foo",
+				"bar",
+			],
+			apex_available: [
+				"myapex",
+			],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+	`)
+
+	fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000")
+	classPathProtoContent := android.ContentFromFileRuleForTests(t, result.TestContext, fragment.Output("bootclasspath.pb.textproto"))
+	// foo
+	ensureContains(t, classPathProtoContent, `jars {
+path: "/apex/myapex/javalib/foo.jar"
+classpath: BOOTCLASSPATH
+min_sdk_version: "33"
+max_sdk_version: ""
+}
+`)
+	// bar
+	ensureContains(t, classPathProtoContent, `jars {
+path: "/apex/myapex/javalib/bar.jar"
+classpath: BOOTCLASSPATH
+min_sdk_version: "34"
+max_sdk_version: ""
+}
+`)
+}
+
 // TODO(b/177892522) - add test for host apex.
diff --git a/cc/builder.go b/cc/builder.go
index 7a3394a..8719d4f 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -485,7 +485,7 @@
 		coverageFiles = make(android.Paths, 0, len(srcFiles))
 	}
 	var kytheFiles android.Paths
-	if flags.emitXrefs {
+	if flags.emitXrefs && ctx.Module() == ctx.PrimaryModule() {
 		kytheFiles = make(android.Paths, 0, len(srcFiles))
 	}
 
@@ -664,7 +664,7 @@
 		})
 
 		// Register post-process build statements (such as for tidy or kythe).
-		if emitXref {
+		if emitXref && ctx.Module() == ctx.PrimaryModule() {
 			kytheFile := android.ObjPathWithExt(ctx, subdir, srcFile, "kzip")
 			ctx.Build(pctx, android.BuildParams{
 				Rule:        kytheExtract,
diff --git a/cc/cmake_main.txt b/cc/cmake_main.txt
index deb1de1..e9177d6 100644
--- a/cc/cmake_main.txt
+++ b/cc/cmake_main.txt
@@ -1,6 +1,7 @@
 cmake_minimum_required(VERSION 3.18)
 project(<<.M.Name>> CXX)
 set(CMAKE_CXX_STANDARD 20)
+enable_testing()
 
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
 include(AddAidlLibrary)
diff --git a/cc/cmake_module_cc.txt b/cc/cmake_module_cc.txt
index 488e5e1..693406d 100644
--- a/cc/cmake_module_cc.txt
+++ b/cc/cmake_module_cc.txt
@@ -8,15 +8,24 @@
 (getLinkerProperties .M).Header_libs
 (getExtraLibs .M)
 ) .Pprop.LibraryMapping>>
+<<$moduleType := getModuleType .M>>
+<<$moduleTypeCmake := "executable">>
+<<if eq $moduleType "library">>
+<<$moduleTypeCmake = "library">>
+<<end>>
 
 # <<.M.Name>>
 <<if $srcs>>
 <<setList .M.Name "_SRCS" "${ANDROID_BUILD_TOP}/" (toStrings $srcs)>>
-add_<<getModuleType .M>>(<<.M.Name>> ${<<.M.Name>>_SRCS})
+add_<<$moduleTypeCmake>>(<<.M.Name>> ${<<.M.Name>>_SRCS})
 <<- else>>
-add_<<getModuleType .M>>(<<.M.Name>> INTERFACE)
+add_<<$moduleTypeCmake>>(<<.M.Name>> INTERFACE)
 <<- end>>
-add_<<getModuleType .M>>(android::<<.M.Name>> ALIAS <<.M.Name>>)
+<<- if eq $moduleType "library">>
+add_library(android::<<.M.Name>> ALIAS <<.M.Name>>)
+<<- else if eq $moduleType "test">>
+add_test(NAME <<.M.Name>> COMMAND <<.M.Name>>)
+<<- end>>
 <<print "">>
 
 <<- if $includeDirs>>
diff --git a/cc/cmake_snapshot.go b/cc/cmake_snapshot.go
index 9ac6350..ad7beed 100644
--- a/cc/cmake_snapshot.go
+++ b/cc/cmake_snapshot.go
@@ -488,9 +488,9 @@
 	case *libraryDecorator:
 		return "library"
 	case *testBinary:
-		return "executable"
+		return "test"
 	case *benchmarkDecorator:
-		return "executable"
+		return "test"
 	}
 	panic(fmt.Sprintf("Unexpected module type: %T", m.linker))
 }
diff --git a/cmd/release_config/build_flag/main.go b/cmd/release_config/build_flag/main.go
index f74784b..5d183ee 100644
--- a/cmd/release_config/build_flag/main.go
+++ b/cmd/release_config/build_flag/main.go
@@ -163,6 +163,7 @@
 		for _, fa := range configs.FlagArtifacts {
 			args = append(args, *fa.FlagDeclaration.Name)
 		}
+		slices.Sort(args)
 	}
 
 	var maxVariableNameLen, maxReleaseNameLen int
@@ -232,7 +233,7 @@
 			} else {
 				outputOneLine(arg, config.Name, "REDACTED", "%s")
 			}
-			if isTrace {
+			if err == nil && isTrace {
 				for _, trace := range config.FlagArtifacts[arg].Traces {
 					fmt.Printf("  => \"%s\" in %s\n", rc_lib.MarshalValue(trace.Value), *trace.Source)
 				}
@@ -244,6 +245,8 @@
 
 func SetCommand(configs *rc_lib.ReleaseConfigs, commonFlags Flags, cmd string, args []string) error {
 	var valueDir string
+	var redacted bool
+	var value string
 	if len(commonFlags.targetReleases) > 1 {
 		return fmt.Errorf("set command only allows one --release argument.  Got: %s", strings.Join(commonFlags.targetReleases, " "))
 	}
@@ -251,13 +254,20 @@
 
 	setFlags := flag.NewFlagSet("set", flag.ExitOnError)
 	setFlags.StringVar(&valueDir, "dir", "", "Directory in which to place the value")
+	setFlags.BoolVar(&redacted, "redacted", false, "Whether the flag should be redacted")
 	setFlags.Parse(args)
 	setArgs := setFlags.Args()
-	if len(setArgs) != 2 {
+	if redacted {
+		if len(setArgs) != 1 {
+			return fmt.Errorf("set command expected '--redacted=true flag', got: --redacted=true %s", strings.Join(setArgs, " "))
+		}
+	} else if len(setArgs) != 2 {
 		return fmt.Errorf("set command expected flag and value, got: %s", strings.Join(setArgs, " "))
 	}
 	name := setArgs[0]
-	value := setArgs[1]
+	if !redacted {
+		value = setArgs[1]
+	}
 	release, err := configs.GetReleaseConfig(targetRelease)
 	targetRelease = release.Name
 	if err != nil {
@@ -278,9 +288,30 @@
 		valueDir = mapDir
 	}
 
+	var updatedFiles []string
+	rcPath := filepath.Join(valueDir, "release_configs", fmt.Sprintf("%s.textproto", targetRelease))
+	// Create the release config declaration only if necessary.
+	if _, err = os.Stat(rcPath); err != nil {
+		if err = os.MkdirAll(filepath.Dir(rcPath), 0775); err != nil {
+			return err
+		}
+		rcValue := &rc_proto.ReleaseConfig{
+			Name: proto.String(targetRelease),
+		}
+		err = rc_lib.WriteMessage(rcPath, rcValue)
+		if err != nil {
+			return err
+		}
+		updatedFiles = append(updatedFiles, rcPath)
+	}
+
 	flagValue := &rc_proto.FlagValue{
-		Name:  proto.String(name),
-		Value: rc_lib.UnmarshalValue(value),
+		Name: proto.String(name),
+	}
+	if redacted {
+		flagValue.Redacted = proto.Bool(true)
+	} else {
+		flagValue.Value = rc_lib.UnmarshalValue(value)
 	}
 	flagPath := filepath.Join(valueDir, "flag_values", targetRelease, fmt.Sprintf("%s.textproto", name))
 	err = rc_lib.WriteMessage(flagPath, flagValue)
@@ -293,11 +324,12 @@
 	if err != nil {
 		return err
 	}
-	err = GetCommand(configs, commonFlags, cmd, args[0:1])
+	err = GetCommand(configs, commonFlags, cmd, []string{name})
 	if err != nil {
 		return err
 	}
-	fmt.Printf("Updated: %s\n", flagPath)
+	updatedFiles = append(updatedFiles, flagPath)
+	fmt.Printf("Added/Updated: %s\n", strings.Join(updatedFiles, " "))
 	return nil
 }
 
diff --git a/cmd/release_config/release_config_lib/util.go b/cmd/release_config/release_config_lib/util.go
index b8824d1..0a19efe 100644
--- a/cmd/release_config/release_config_lib/util.go
+++ b/cmd/release_config/release_config_lib/util.go
@@ -83,6 +83,11 @@
 //	error: any error encountered.
 func WriteFormattedMessage(path, format string, message proto.Message) (err error) {
 	var data []byte
+	if _, err := os.Stat(filepath.Dir(path)); err != nil {
+		if err = os.MkdirAll(filepath.Dir(path), 0775); err != nil {
+			return err
+		}
+	}
 	switch format {
 	case "json":
 		data, err = json.MarshalIndent(message, "", "  ")
diff --git a/java/base.go b/java/base.go
index b4f800b..1a6584b 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1852,7 +1852,7 @@
 	classes := android.PathForModuleOut(ctx, "javac", jarName).OutputPath
 	TransformJavaToClasses(ctx, classes, idx, srcFiles, srcJars, annoSrcJar, flags, extraJarDeps)
 
-	if ctx.Config().EmitXrefRules() {
+	if ctx.Config().EmitXrefRules() && ctx.Module() == ctx.PrimaryModule() {
 		extractionFile := android.PathForModuleOut(ctx, kzipName)
 		emitXrefRule(ctx, extractionFile, idx, srcFiles, srcJars, flags, extraJarDeps)
 		j.kytheFiles = append(j.kytheFiles, extractionFile)
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index 07bc5c1..18a5dae 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -128,19 +128,21 @@
 				return m.Name() == configuredJars.Jar(i)
 			}, func(m android.Module) {
 				if s, ok := m.(*SdkLibrary); ok {
+					minSdkVersion := s.MinSdkVersion(ctx)
+					maxSdkVersion := s.MaxSdkVersion(ctx)
 					// TODO(208456999): instead of mapping "current" to latest, min_sdk_version should never be set to "current"
-					if s.minSdkVersion.Specified() {
-						if s.minSdkVersion.IsCurrent() {
+					if minSdkVersion.Specified() {
+						if minSdkVersion.IsCurrent() {
 							jar.minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 						} else {
-							jar.minSdkVersion = s.minSdkVersion.String()
+							jar.minSdkVersion = minSdkVersion.String()
 						}
 					}
-					if s.maxSdkVersion.Specified() {
-						if s.maxSdkVersion.IsCurrent() {
+					if maxSdkVersion.Specified() {
+						if maxSdkVersion.IsCurrent() {
 							jar.maxSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 						} else {
-							jar.maxSdkVersion = s.maxSdkVersion.String()
+							jar.maxSdkVersion = maxSdkVersion.String()
 						}
 					}
 				}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 832b850..57aaa1a 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -312,10 +312,6 @@
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
-func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, libName string, installPath android.InstallPath) bool {
-	return dexpreopt.OdexOnSystemOtherByName(libName, android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
-}
-
 // Returns the install path of the dex jar of a module.
 //
 // Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather
@@ -545,12 +541,21 @@
 	// Use the path of the dex file to determine the library name
 	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(dexJarStem)
 
+	partition := d.installPath.Partition()
 	for _, install := range dexpreoptRule.Installs() {
 		// Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT.
 		installDir := strings.TrimPrefix(filepath.Dir(install.To), "/")
+		if strings.HasPrefix(installDir, partition+"/") {
+			installDir = strings.TrimPrefix(installDir, partition+"/")
+		} else {
+			// If the partition for the installDir is different from the install partition, set the
+			// partition empty to install the dexpreopt files to the desired partition.
+			// TODO(b/346439786): Define and use the dexpreopt module type to avoid this mismatch.
+			partition = ""
+		}
 		installBase := filepath.Base(install.To)
 		arch := filepath.Base(installDir)
-		installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+		installPath := android.PathForModuleInPartitionInstall(ctx, partition, installDir)
 		isProfile := strings.HasSuffix(installBase, ".prof")
 
 		if isProfile {
@@ -584,6 +589,37 @@
 	}
 }
 
+func getModuleInstallPathInfo(ctx android.ModuleContext, fullInstallPath string) (android.InstallPath, string, string) {
+	installPath := android.PathForModuleInstall(ctx)
+	installDir, installBase := filepath.Split(strings.TrimPrefix(fullInstallPath, "/"))
+
+	if !strings.HasPrefix(installDir, installPath.Partition()+"/") {
+		// Return empty filename if the install partition is not for the target image.
+		return installPath, "", ""
+	}
+	relDir, err := filepath.Rel(installPath.Partition(), installDir)
+	if err != nil {
+		panic(err)
+	}
+	return installPath, relDir, installBase
+}
+
+// RuleBuilder.Install() adds output-to-install copy pairs to a list for Make. To share this
+// information with PackagingSpec in soong, call PackageFile for them.
+// The install path and the target install partition of the module must be the same.
+func packageFile(ctx android.ModuleContext, install android.RuleBuilderInstall) {
+	installPath, relDir, name := getModuleInstallPathInfo(ctx, install.To)
+	// Empty name means the install partition is not for the target image.
+	// For the system image, files for "apex" and "system_other" are skipped here.
+	// The skipped "apex" files are for testing only, for example,
+	// "/apex/art_boot_images/javalib/x86/boot.vdex".
+	// TODO(b/320196894): Files for "system_other" are skipped because soong creates the system
+	// image only for now.
+	if name != "" {
+		ctx.PackageFile(installPath.Join(ctx, relDir), name, install.From)
+	}
+}
+
 func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall {
 	return d.builtInstalledForApex
 }
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 7229ca0..defa82c 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -612,6 +612,9 @@
 			profileInstalls:            profileInstalls,
 			profileLicenseMetadataFile: android.OptionalPathForPath(ctx.LicenseMetadataFile()),
 		})
+		for _, install := range profileInstalls {
+			packageFile(ctx, install)
+		}
 	}
 }
 
@@ -929,6 +932,35 @@
 	return apexNameToApexExportsInfoMap
 }
 
+func packageFileForTargetImage(ctx android.ModuleContext, image *bootImageVariant) {
+	if image.target.Os != ctx.Os() {
+		// This is not for the target device.
+		return
+	}
+
+	for _, install := range image.installs {
+		packageFile(ctx, install)
+	}
+
+	for _, install := range image.vdexInstalls {
+		if image.target.Arch.ArchType.Name != ctx.DeviceConfig().DeviceArch() {
+			// Note that the vdex files are identical between architectures. If the target image is
+			// not for the primary architecture create symlinks to share the vdex of the primary
+			// architecture with the other architectures.
+			//
+			// Assuming that the install path has the architecture name with it, replace the
+			// architecture name with the primary architecture name to find the source vdex file.
+			installPath, relDir, name := getModuleInstallPathInfo(ctx, install.To)
+			if name != "" {
+				srcRelDir := strings.Replace(relDir, image.target.Arch.ArchType.Name, ctx.DeviceConfig().DeviceArch(), 1)
+				ctx.InstallSymlink(installPath.Join(ctx, relDir), name, installPath.Join(ctx, srcRelDir, name))
+			}
+		} else {
+			packageFile(ctx, install)
+		}
+	}
+}
+
 // Generate boot image build rules for a specific target.
 func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) bootImageVariantOutputs {
 
@@ -1123,6 +1155,7 @@
 	image.installs = rule.Installs()
 	image.vdexInstalls = vdexInstalls
 	image.unstrippedInstalls = unstrippedInstalls
+	packageFileForTargetImage(ctx, image)
 
 	// Only set the licenseMetadataFile from the active module.
 	if isActiveModule(ctx, ctx.Module()) {
diff --git a/ui/build/build.go b/ui/build/build.go
index 9a9eccd..03d8392 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -21,7 +21,6 @@
 	"path/filepath"
 	"sync"
 	"text/template"
-	"time"
 
 	"android/soong/ui/metrics"
 )
@@ -66,9 +65,12 @@
 	// (to allow for source control that uses something other than numbers),
 	// but must be a single word and a valid file name.
 	//
-	// If no BUILD_NUMBER is set, create a useful "I am an engineering build
-	// from this date/time" value.  Make it start with a non-digit so that
-	// anyone trying to parse it as an integer will probably get "0".
+	// If no BUILD_NUMBER is set, create a useful "I am an engineering build"
+	// value.  Make it start with a non-digit so that anyone trying to parse
+	// it as an integer will probably get "0". This value used to contain
+	// a timestamp, but now that more dependencies are tracked in order to
+	// reduce the importance of `m installclean`, changing it every build
+	// causes unnecessary rebuilds for local development.
 	buildNumber, ok := config.environ.Get("BUILD_NUMBER")
 	if ok {
 		writeValueIfChanged(ctx, config, config.OutDir(), "file_name_tag.txt", buildNumber)
@@ -77,7 +79,7 @@
 		if username, ok = config.environ.Get("BUILD_USERNAME"); !ok {
 			ctx.Fatalln("Missing BUILD_USERNAME")
 		}
-		buildNumber = fmt.Sprintf("eng.%.6s.%s", username, time.Now().Format("20060102.150405" /* YYYYMMDD.HHMMSS */))
+		buildNumber = fmt.Sprintf("eng.%.6s.00000000.000000", username)
 		writeValueIfChanged(ctx, config, config.OutDir(), "file_name_tag.txt", username)
 	}
 	// Write the build number to a file so it can be read back in