Add container property to aconfig_declarations.

Bug: 311155208
Test: Unit test
Change-Id: I7b187138856d0144203961e82b6dad5e2f8eed9d
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index faa4ddb..04de919 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -12,28 +12,19 @@
         "soong",
         "soong-android",
         "soong-bazel",
-        "soong-android",
-        "soong-java",
-        "soong-rust",
     ],
     srcs: [
         "aconfig_declarations.go",
         "aconfig_values.go",
         "aconfig_value_set.go",
         "all_aconfig_declarations.go",
-        "cc_aconfig_library.go",
         "init.go",
-        "java_aconfig_library.go",
         "testing.go",
-        "rust_aconfig_library.go",
     ],
     testSrcs: [
         "aconfig_declarations_test.go",
         "aconfig_values_test.go",
         "aconfig_value_set_test.go",
-        "java_aconfig_library_test.go",
-        "cc_aconfig_library_test.go",
-        "rust_aconfig_library_test.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/aconfig/aconfig_declarations.go b/aconfig/aconfig_declarations.go
index 897f892..e662f1d 100644
--- a/aconfig/aconfig_declarations.go
+++ b/aconfig/aconfig_declarations.go
@@ -38,6 +38,9 @@
 
 		// Values from TARGET_RELEASE / RELEASE_ACONFIG_VALUE_SETS
 		Values []string `blueprint:"mutated"`
+
+		// Container(system/vendor/apex) that this module belongs to
+		Container string
 	}
 
 	intermediatePath android.WritablePath
@@ -69,6 +72,8 @@
 	if len(module.properties.Package) == 0 {
 		ctx.PropertyErrorf("package", "missing package property")
 	}
+	// TODO(b/311155208): Add mandatory check for container after all pre-existing
+	// ones are changed.
 
 	// Add a dependency on the aconfig_value_sets defined in
 	// RELEASE_ACONFIG_VALUE_SETS, and add any aconfig_values that
@@ -110,12 +115,21 @@
 }
 
 // Provider published by aconfig_value_set
-type declarationsProviderData struct {
+type DeclarationsProviderData struct {
 	Package          string
+	Container        string
 	IntermediatePath android.WritablePath
 }
 
-var declarationsProviderKey = blueprint.NewProvider(declarationsProviderData{})
+var DeclarationsProviderKey = blueprint.NewProvider(DeclarationsProviderData{})
+
+// This is used to collect the aconfig declarations info on the transitive closure,
+// the data is keyed on the container.
+type TransitiveDeclarationsInfo struct {
+	AconfigFiles map[string]*android.DepSet[android.Path]
+}
+
+var TransitiveDeclarationsInfoProvider = blueprint.NewProvider(TransitiveDeclarationsInfo{})
 
 func (module *DeclarationsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
@@ -156,13 +170,49 @@
 		},
 	})
 
-	ctx.SetProvider(declarationsProviderKey, declarationsProviderData{
+	ctx.SetProvider(DeclarationsProviderKey, DeclarationsProviderData{
 		Package:          module.properties.Package,
+		Container:        module.properties.Container,
 		IntermediatePath: intermediatePath,
 	})
 
 }
 
+func CollectTransitiveAconfigFiles(ctx android.ModuleContext, transitiveAconfigFiles *map[string]*android.DepSet[android.Path]) {
+	if *transitiveAconfigFiles == nil {
+		*transitiveAconfigFiles = make(map[string]*android.DepSet[android.Path])
+	}
+	ctx.VisitDirectDeps(func(module android.Module) {
+		if dep := ctx.OtherModuleProvider(module, DeclarationsProviderKey).(DeclarationsProviderData); dep.IntermediatePath != nil {
+			aconfigMap := make(map[string]*android.DepSet[android.Path])
+			aconfigMap[dep.Container] = android.NewDepSet(android.POSTORDER, []android.Path{dep.IntermediatePath}, nil)
+			mergeTransitiveAconfigFiles(aconfigMap, *transitiveAconfigFiles)
+			return
+		}
+		if dep := ctx.OtherModuleProvider(module, TransitiveDeclarationsInfoProvider).(TransitiveDeclarationsInfo); len(dep.AconfigFiles) > 0 {
+			mergeTransitiveAconfigFiles(dep.AconfigFiles, *transitiveAconfigFiles)
+		}
+	})
+
+	ctx.SetProvider(TransitiveDeclarationsInfoProvider, TransitiveDeclarationsInfo{
+		AconfigFiles: *transitiveAconfigFiles,
+	})
+}
+
+func mergeTransitiveAconfigFiles(from, to map[string]*android.DepSet[android.Path]) {
+	for fromKey, fromValue := range from {
+		if fromValue == nil {
+			continue
+		}
+		toValue, ok := to[fromKey]
+		if !ok {
+			to[fromKey] = fromValue
+		} else {
+			to[fromKey] = android.NewDepSet(android.POSTORDER, toValue.ToList(), []*android.DepSet[android.Path]{fromValue})
+		}
+	}
+}
+
 type bazelAconfigDeclarationsAttributes struct {
 	Srcs    bazel.LabelListAttribute
 	Package string
diff --git a/aconfig/aconfig_declarations_test.go b/aconfig/aconfig_declarations_test.go
index e0d8f7d..e6bd87c 100644
--- a/aconfig/aconfig_declarations_test.go
+++ b/aconfig/aconfig_declarations_test.go
@@ -26,6 +26,7 @@
 		aconfig_declarations {
 			name: "module_name",
 			package: "com.example.package",
+			container: "com.android.foo",
 			srcs: [
 				"foo.aconfig",
 				"bar.aconfig",
@@ -37,8 +38,9 @@
 	module := result.ModuleForTests("module_name", "").Module().(*DeclarationsModule)
 
 	// Check that the provider has the right contents
-	depData := result.ModuleProvider(module, declarationsProviderKey).(declarationsProviderData)
+	depData := result.ModuleProvider(module, DeclarationsProviderKey).(DeclarationsProviderData)
 	android.AssertStringEquals(t, "package", depData.Package, "com.example.package")
+	android.AssertStringEquals(t, "container", depData.Container, "com.android.foo")
 	if !strings.HasSuffix(depData.IntermediatePath.String(), "/intermediate.pb") {
 		t.Errorf("Missing intermediates path in provider: %s", depData.IntermediatePath.String())
 	}
diff --git a/aconfig/all_aconfig_declarations.go b/aconfig/all_aconfig_declarations.go
index 6096c6c..7ccb81a 100644
--- a/aconfig/all_aconfig_declarations.go
+++ b/aconfig/all_aconfig_declarations.go
@@ -37,17 +37,17 @@
 	// Find all of the aconfig_declarations modules
 	var cacheFiles android.Paths
 	ctx.VisitAllModules(func(module android.Module) {
-		if !ctx.ModuleHasProvider(module, declarationsProviderKey) {
+		if !ctx.ModuleHasProvider(module, DeclarationsProviderKey) {
 			return
 		}
-		decl := ctx.ModuleProvider(module, declarationsProviderKey).(declarationsProviderData)
+		decl := ctx.ModuleProvider(module, DeclarationsProviderKey).(DeclarationsProviderData)
 		cacheFiles = append(cacheFiles, decl.IntermediatePath)
 	})
 
 	// Generate build action for aconfig
 	this.intermediatePath = android.PathForIntermediates(ctx, "all_aconfig_declarations.pb")
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        allDeclarationsRule,
+		Rule:        AllDeclarationsRule,
 		Inputs:      cacheFiles,
 		Output:      this.intermediatePath,
 		Description: "all_aconfig_declarations",
diff --git a/aconfig/codegen/Android.bp b/aconfig/codegen/Android.bp
new file mode 100644
index 0000000..494f7e6
--- /dev/null
+++ b/aconfig/codegen/Android.bp
@@ -0,0 +1,32 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-aconfig-codegen",
+    pkgPath: "android/soong/aconfig/codegen",
+    deps: [
+        "blueprint",
+        "blueprint-pathtools",
+        "sbox_proto",
+        "soong",
+        "soong-aconfig",
+        "soong-android",
+        "soong-bazel",
+        "soong-java",
+        "soong-rust",
+    ],
+    srcs: [
+        "cc_aconfig_library.go",
+        "init.go",
+        "java_aconfig_library.go",
+        "rust_aconfig_library.go",
+        "testing.go",
+    ],
+    testSrcs: [
+        "java_aconfig_library_test.go",
+        "cc_aconfig_library_test.go",
+        "rust_aconfig_library_test.go",
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/aconfig/cc_aconfig_library.go b/aconfig/codegen/cc_aconfig_library.go
similarity index 96%
rename from aconfig/cc_aconfig_library.go
rename to aconfig/codegen/cc_aconfig_library.go
index 210a581..7b68844 100644
--- a/aconfig/cc_aconfig_library.go
+++ b/aconfig/codegen/cc_aconfig_library.go
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package aconfig
+package codegen
 
 import (
+	"android/soong/aconfig"
 	"android/soong/android"
 	"android/soong/bazel"
 	"android/soong/cc"
@@ -92,7 +93,7 @@
 	if len(declarationsModules) != 1 {
 		panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
 	}
-	declarations := ctx.OtherModuleProvider(declarationsModules[0], declarationsProviderKey).(declarationsProviderData)
+	declarations := ctx.OtherModuleProvider(declarationsModules[0], aconfig.DeclarationsProviderKey).(aconfig.DeclarationsProviderData)
 
 	// Figure out the generated file paths.  This has to match aconfig's codegen_cpp.rs.
 	this.generatedDir = android.PathForModuleGen(ctx)
@@ -122,7 +123,7 @@
 	if len(declarationsModules) != 1 {
 		panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
 	}
-	declarations := ctx.OtherModuleProvider(declarationsModules[0], declarationsProviderKey).(declarationsProviderData)
+	declarations := ctx.OtherModuleProvider(declarationsModules[0], aconfig.DeclarationsProviderKey).(aconfig.DeclarationsProviderData)
 
 	mode := proptools.StringDefault(this.properties.Mode, "production")
 	if !isModeSupported(mode) {
diff --git a/aconfig/cc_aconfig_library_test.go b/aconfig/codegen/cc_aconfig_library_test.go
similarity index 99%
rename from aconfig/cc_aconfig_library_test.go
rename to aconfig/codegen/cc_aconfig_library_test.go
index ba27250..0c8a969 100644
--- a/aconfig/cc_aconfig_library_test.go
+++ b/aconfig/codegen/cc_aconfig_library_test.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package aconfig
+package codegen
 
 import (
 	"fmt"
diff --git a/aconfig/codegen/init.go b/aconfig/codegen/init.go
new file mode 100644
index 0000000..0bff9d2
--- /dev/null
+++ b/aconfig/codegen/init.go
@@ -0,0 +1,83 @@
+// Copyright 2023 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package codegen
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	pctx = android.NewPackageContext("android/soong/aconfig/codegen")
+
+	// For java_aconfig_library: Generate java library
+	javaRule = pctx.AndroidStaticRule("java_aconfig_library",
+		blueprint.RuleParams{
+			Command: `rm -rf ${out}.tmp` +
+				` && mkdir -p ${out}.tmp` +
+				` && ${aconfig} create-java-lib` +
+				`    --mode ${mode}` +
+				`    --cache ${in}` +
+				`    --out ${out}.tmp` +
+				` && $soong_zip -write_if_changed -jar -o ${out} -C ${out}.tmp -D ${out}.tmp` +
+				` && rm -rf ${out}.tmp`,
+			CommandDeps: []string{
+				"$aconfig",
+				"$soong_zip",
+			},
+			Restat: true,
+		}, "mode")
+
+	// For cc_aconfig_library: Generate C++ library
+	cppRule = pctx.AndroidStaticRule("cc_aconfig_library",
+		blueprint.RuleParams{
+			Command: `rm -rf ${gendir}` +
+				` && mkdir -p ${gendir}` +
+				` && ${aconfig} create-cpp-lib` +
+				`    --mode ${mode}` +
+				`    --cache ${in}` +
+				`    --out ${gendir}`,
+			CommandDeps: []string{
+				"$aconfig",
+			},
+		}, "gendir", "mode")
+
+	// For rust_aconfig_library: Generate Rust library
+	rustRule = pctx.AndroidStaticRule("rust_aconfig_library",
+		blueprint.RuleParams{
+			Command: `rm -rf ${gendir}` +
+				` && mkdir -p ${gendir}` +
+				` && ${aconfig} create-rust-lib` +
+				`    --mode ${mode}` +
+				`    --cache ${in}` +
+				`    --out ${gendir}`,
+			CommandDeps: []string{
+				"$aconfig",
+			},
+		}, "gendir", "mode")
+)
+
+func init() {
+	RegisterBuildComponents(android.InitRegistrationContext)
+	pctx.HostBinToolVariable("aconfig", "aconfig")
+	pctx.HostBinToolVariable("soong_zip", "soong_zip")
+}
+
+func RegisterBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("cc_aconfig_library", CcAconfigLibraryFactory)
+	ctx.RegisterModuleType("java_aconfig_library", JavaDeclarationsLibraryFactory)
+	ctx.RegisterModuleType("rust_aconfig_library", RustAconfigLibraryFactory)
+}
diff --git a/aconfig/java_aconfig_library.go b/aconfig/codegen/java_aconfig_library.go
similarity index 94%
rename from aconfig/java_aconfig_library.go
rename to aconfig/codegen/java_aconfig_library.go
index eedb3c3..e2fb15b 100644
--- a/aconfig/java_aconfig_library.go
+++ b/aconfig/codegen/java_aconfig_library.go
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package aconfig
+package codegen
 
 import (
 	"fmt"
 
+	"android/soong/aconfig"
 	"android/soong/android"
 	"android/soong/bazel"
 	"android/soong/java"
@@ -73,7 +74,7 @@
 	if len(declarationsModules) != 1 {
 		panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
 	}
-	declarations := ctx.OtherModuleProvider(declarationsModules[0], declarationsProviderKey).(declarationsProviderData)
+	declarations := ctx.OtherModuleProvider(declarationsModules[0], aconfig.DeclarationsProviderKey).(aconfig.DeclarationsProviderData)
 
 	// Generate the action to build the srcjar
 	srcJarPath := android.PathForModuleGen(ctx, ctx.ModuleName()+".srcjar")
@@ -93,10 +94,6 @@
 		},
 	})
 
-	// Tell the java module about the .aconfig files, so they can be propagated up the dependency chain.
-	// TODO: It would be nice to have that propagation code here instead of on java.Module and java.JavaInfo.
-	module.AddAconfigIntermediate(declarations.IntermediatePath)
-
 	return srcJarPath
 }
 
diff --git a/aconfig/java_aconfig_library_test.go b/aconfig/codegen/java_aconfig_library_test.go
similarity index 82%
rename from aconfig/java_aconfig_library_test.go
rename to aconfig/codegen/java_aconfig_library_test.go
index a803672..cbfdc21 100644
--- a/aconfig/java_aconfig_library_test.go
+++ b/aconfig/codegen/java_aconfig_library_test.go
@@ -12,11 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package aconfig
+package codegen
 
 import (
 	"fmt"
-	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -34,14 +33,25 @@
 		ExtendWithErrorHandler(android.FixtureExpectsNoErrors).
 		RunTestWithBp(t, bp+`
 			aconfig_declarations {
-				name: "my_aconfig_declarations",
+				name: "my_aconfig_declarations_foo",
 				package: "com.example.package",
 				srcs: ["foo.aconfig"],
 			}
 
 			java_aconfig_library {
-				name: "my_java_aconfig_library",
-				aconfig_declarations: "my_aconfig_declarations",
+				name: "my_java_aconfig_library_foo",
+				aconfig_declarations: "my_aconfig_declarations_foo",
+			}
+
+			aconfig_declarations {
+				name: "my_aconfig_declarations_bar",
+				package: "com.example.package",
+				srcs: ["bar.aconfig"],
+			}
+
+			java_aconfig_library {
+				name: "my_java_aconfig_library_bar",
+				aconfig_declarations: "my_aconfig_declarations_bar",
 			}
 		`)
 
@@ -50,10 +60,9 @@
 	entry := android.AndroidMkEntriesForTest(t, result.TestContext, module)[0]
 
 	makeVar := entry.EntryMap["LOCAL_ACONFIG_FILES"]
-	android.AssertIntEquals(t, "len(LOCAL_ACONFIG_FILES)", 1, len(makeVar))
-	if !strings.HasSuffix(makeVar[0], "intermediate.pb") {
-		t.Errorf("LOCAL_ACONFIG_FILES should end with /intermediates.pb, instead it is: %s", makeVar[0])
-	}
+	android.AssertIntEquals(t, "len(LOCAL_ACONFIG_FILES)", 2, len(makeVar))
+	android.EnsureListContainsSuffix(t, makeVar, "my_aconfig_declarations_foo/intermediate.pb")
+	android.EnsureListContainsSuffix(t, makeVar, "my_aconfig_declarations_bar/intermediate.pb")
 }
 
 func TestAndroidMkJavaLibrary(t *testing.T) {
@@ -64,7 +73,8 @@
 				"src/foo.java",
 			],
 			static_libs: [
-				"my_java_aconfig_library",
+				"my_java_aconfig_library_foo",
+				"my_java_aconfig_library_bar",
 			],
 			platform_apis: true,
 		}
@@ -81,7 +91,8 @@
 				"src/foo.java",
 			],
 			static_libs: [
-				"my_java_aconfig_library",
+				"my_java_aconfig_library_foo",
+				"my_java_aconfig_library_bar",
 			],
 			platform_apis: true,
 		}
@@ -98,7 +109,8 @@
 				"src/foo.java",
 			],
 			static_libs: [
-				"my_java_aconfig_library",
+				"my_java_aconfig_library_foo",
+				"my_java_aconfig_library_bar",
 			],
 			platform_apis: true,
 			main_class: "foo",
@@ -116,7 +128,8 @@
 				"src/foo.java",
 			],
 			static_libs: [
-				"my_java_aconfig_library",
+				"my_java_aconfig_library_foo",
+				"my_java_aconfig_library_bar",
 			],
 			platform_apis: true,
 		}
@@ -134,7 +147,8 @@
 				"src/foo.java",
 			],
 			static_libs: [
-				"my_java_aconfig_library",
+				"my_java_aconfig_library_foo",
+				"my_java_aconfig_library_bar",
 			],
 			platform_apis: true,
 		}
diff --git a/aconfig/rust_aconfig_library.go b/aconfig/codegen/rust_aconfig_library.go
similarity index 96%
rename from aconfig/rust_aconfig_library.go
rename to aconfig/codegen/rust_aconfig_library.go
index 265685e..3525de1 100644
--- a/aconfig/rust_aconfig_library.go
+++ b/aconfig/codegen/rust_aconfig_library.go
@@ -1,8 +1,9 @@
-package aconfig
+package codegen
 
 import (
 	"fmt"
 
+	"android/soong/aconfig"
 	"android/soong/android"
 	"android/soong/rust"
 
@@ -64,7 +65,7 @@
 	if len(declarationsModules) != 1 {
 		panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
 	}
-	declarations := ctx.OtherModuleProvider(declarationsModules[0], declarationsProviderKey).(declarationsProviderData)
+	declarations := ctx.OtherModuleProvider(declarationsModules[0], aconfig.DeclarationsProviderKey).(aconfig.DeclarationsProviderData)
 
 	mode := proptools.StringDefault(a.Properties.Mode, "production")
 	if !isModeSupported(mode) {
diff --git a/aconfig/rust_aconfig_library_test.go b/aconfig/codegen/rust_aconfig_library_test.go
similarity index 99%
rename from aconfig/rust_aconfig_library_test.go
rename to aconfig/codegen/rust_aconfig_library_test.go
index 3aeab76..c09f701 100644
--- a/aconfig/rust_aconfig_library_test.go
+++ b/aconfig/codegen/rust_aconfig_library_test.go
@@ -1,4 +1,4 @@
-package aconfig
+package codegen
 
 import (
 	"fmt"
diff --git a/aconfig/codegen/testing.go b/aconfig/codegen/testing.go
new file mode 100644
index 0000000..3e1c22e
--- /dev/null
+++ b/aconfig/codegen/testing.go
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package codegen
+
+import (
+	"android/soong/aconfig"
+	"android/soong/android"
+)
+
+var PrepareForTestWithAconfigBuildComponents = android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("aconfig_declarations", aconfig.DeclarationsFactory)
+	RegisterBuildComponents(ctx)
+})
diff --git a/aconfig/init.go b/aconfig/init.go
index 626e66d..79bf002 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -40,54 +40,8 @@
 			Restat: true,
 		}, "release_version", "package", "declarations", "values", "default-permission")
 
-	// For java_aconfig_library: Generate java library
-	javaRule = pctx.AndroidStaticRule("java_aconfig_library",
-		blueprint.RuleParams{
-			Command: `rm -rf ${out}.tmp` +
-				` && mkdir -p ${out}.tmp` +
-				` && ${aconfig} create-java-lib` +
-				`    --mode ${mode}` +
-				`    --cache ${in}` +
-				`    --out ${out}.tmp` +
-				` && $soong_zip -write_if_changed -jar -o ${out} -C ${out}.tmp -D ${out}.tmp` +
-				` && rm -rf ${out}.tmp`,
-			CommandDeps: []string{
-				"$aconfig",
-				"$soong_zip",
-			},
-			Restat: true,
-		}, "mode")
-
-	// For cc_aconfig_library: Generate C++ library
-	cppRule = pctx.AndroidStaticRule("cc_aconfig_library",
-		blueprint.RuleParams{
-			Command: `rm -rf ${gendir}` +
-				` && mkdir -p ${gendir}` +
-				` && ${aconfig} create-cpp-lib` +
-				`    --mode ${mode}` +
-				`    --cache ${in}` +
-				`    --out ${gendir}`,
-			CommandDeps: []string{
-				"$aconfig",
-			},
-		}, "gendir", "mode")
-
-	// For rust_aconfig_library: Generate Rust library
-	rustRule = pctx.AndroidStaticRule("rust_aconfig_library",
-		blueprint.RuleParams{
-			Command: `rm -rf ${gendir}` +
-				` && mkdir -p ${gendir}` +
-				` && ${aconfig} create-rust-lib` +
-				`    --mode ${mode}` +
-				`    --cache ${in}` +
-				`    --out ${gendir}`,
-			CommandDeps: []string{
-				"$aconfig",
-			},
-		}, "gendir", "mode")
-
 	// For all_aconfig_declarations: Combine all parsed_flags proto files
-	allDeclarationsRule = pctx.AndroidStaticRule("all_aconfig_declarations_dump",
+	AllDeclarationsRule = pctx.AndroidStaticRule("All_aconfig_declarations_dump",
 		blueprint.RuleParams{
 			Command: `${aconfig} dump --format protobuf --out ${out} ${cache_files}`,
 			CommandDeps: []string{
@@ -106,8 +60,5 @@
 	ctx.RegisterModuleType("aconfig_declarations", DeclarationsFactory)
 	ctx.RegisterModuleType("aconfig_values", ValuesFactory)
 	ctx.RegisterModuleType("aconfig_value_set", ValueSetFactory)
-	ctx.RegisterModuleType("cc_aconfig_library", CcAconfigLibraryFactory)
-	ctx.RegisterModuleType("java_aconfig_library", JavaDeclarationsLibraryFactory)
-	ctx.RegisterModuleType("rust_aconfig_library", RustAconfigLibraryFactory)
 	ctx.RegisterParallelSingletonType("all_aconfig_declarations", AllAconfigDeclarationsFactory)
 }
diff --git a/android/testing.go b/android/testing.go
index da3b75a..c596468 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -1293,3 +1293,10 @@
 func StringsRelativeToTop(config Config, command []string) []string {
 	return normalizeStringArrayRelativeToTop(config, command)
 }
+
+func EnsureListContainsSuffix(t *testing.T, result []string, expected string) {
+	t.Helper()
+	if !SuffixInList(result, expected) {
+		t.Errorf("%q is not found in %v", expected, result)
+	}
+}
diff --git a/apex/Android.bp b/apex/Android.bp
index 0791497..0a5063f 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -8,6 +8,8 @@
     deps: [
         "blueprint",
         "soong",
+        "soong-aconfig",
+        "soong-aconfig-codegen",
         "soong-android",
         "soong-bazel",
         "soong-bpf",
diff --git a/apex/apex.go b/apex/apex.go
index ecc794b..56f3367 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -24,6 +24,7 @@
 	"sort"
 	"strings"
 
+	"android/soong/aconfig"
 	"android/soong/bazel/cquery"
 
 	"github.com/google/blueprint"
@@ -482,6 +483,8 @@
 	nativeApisUsedByModuleFile   android.ModuleOutPath
 	nativeApisBackedByModuleFile android.ModuleOutPath
 	javaApisUsedByModuleFile     android.ModuleOutPath
+
+	aconfigFiles []android.Path
 }
 
 // apexFileClass represents a type of file that can be included in APEX.
@@ -2008,6 +2011,8 @@
 
 	// visitor skips these from this list of module names
 	unwantedTransitiveDeps []string
+
+	aconfigFiles []android.Path
 }
 
 func (vctx *visitorContext) normalizeFileInfo(mctx android.ModuleContext) {
@@ -2067,6 +2072,7 @@
 				fi := apexFileForNativeLibrary(ctx, ch, vctx.handleSpecialLibs)
 				fi.isJniLib = isJniLib
 				vctx.filesInfo = append(vctx.filesInfo, fi)
+				addAconfigFiles(vctx, ctx, child)
 				// Collect the list of stub-providing libs except:
 				// - VNDK libs are only for vendors
 				// - bootstrap bionic libs are treated as provided by system
@@ -2090,6 +2096,7 @@
 			switch ch := child.(type) {
 			case *cc.Module:
 				vctx.filesInfo = append(vctx.filesInfo, apexFileForExecutable(ctx, ch))
+				addAconfigFiles(vctx, ctx, child)
 				return true // track transitive dependencies
 			case *rust.Module:
 				vctx.filesInfo = append(vctx.filesInfo, apexFileForRustExecutable(ctx, ch))
@@ -2132,6 +2139,7 @@
 					return false
 				}
 				vctx.filesInfo = append(vctx.filesInfo, af)
+				addAconfigFiles(vctx, ctx, child)
 				return true // track transitive dependencies
 			default:
 				ctx.PropertyErrorf("java_libs", "%q of type %q is not supported", depName, ctx.OtherModuleType(child))
@@ -2140,6 +2148,7 @@
 			switch ap := child.(type) {
 			case *java.AndroidApp:
 				vctx.filesInfo = append(vctx.filesInfo, apexFilesForAndroidApp(ctx, ap)...)
+				addAconfigFiles(vctx, ctx, child)
 				return true // track transitive dependencies
 			case *java.AndroidAppImport:
 				vctx.filesInfo = append(vctx.filesInfo, apexFilesForAndroidApp(ctx, ap)...)
@@ -2298,6 +2307,7 @@
 			}
 
 			vctx.filesInfo = append(vctx.filesInfo, af)
+			addAconfigFiles(vctx, ctx, child)
 			return true // track transitive dependencies
 		} else if rm, ok := child.(*rust.Module); ok {
 			af := apexFileForRustLibrary(ctx, rm)
@@ -2378,6 +2388,13 @@
 	return false
 }
 
+func addAconfigFiles(vctx *visitorContext, ctx android.ModuleContext, module blueprint.Module) {
+	dep := ctx.OtherModuleProvider(module, aconfig.TransitiveDeclarationsInfoProvider).(aconfig.TransitiveDeclarationsInfo)
+	if len(dep.AconfigFiles) > 0 && dep.AconfigFiles[ctx.ModuleName()] != nil {
+		vctx.aconfigFiles = append(vctx.aconfigFiles, dep.AconfigFiles[ctx.ModuleName()].ToList()...)
+	}
+}
+
 func (a *apexBundle) shouldCheckDuplicate(ctx android.ModuleContext) bool {
 	// TODO(b/263308293) remove this
 	if a.properties.IsCoverageVariant {
@@ -2459,6 +2476,7 @@
 	// 3) some fields in apexBundle struct are configured
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	a.filesInfo = vctx.filesInfo
+	a.aconfigFiles = android.FirstUniquePaths(vctx.aconfigFiles)
 
 	a.setPayloadFsType(ctx)
 	a.setSystemLibLink(ctx)
diff --git a/apex/apex_test.go b/apex/apex_test.go
index ddb9a40..af643a0 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -25,6 +25,7 @@
 	"strings"
 	"testing"
 
+	"android/soong/aconfig/codegen"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -151,6 +152,7 @@
 	prebuilt_etc.PrepareForTestWithPrebuiltEtc,
 	rust.PrepareForTestWithRustDefaultModules,
 	sh.PrepareForTestWithShBuildComponents,
+	codegen.PrepareForTestWithAconfigBuildComponents,
 
 	PrepareForTestWithApexBuildComponents,
 
@@ -10756,3 +10758,437 @@
 		inputs.Strings(),
 		"out/soong/.intermediates/libbar/android_arm64_armv8-a_shared/libbar.so")
 }
+
+var apex_default_bp = `
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		filegroup {
+			name: "myapex.manifest",
+			srcs: ["apex_manifest.json"],
+		}
+
+		filegroup {
+			name: "myapex.androidmanifest",
+			srcs: ["AndroidManifest.xml"],
+		}
+`
+
+func TestAconfigFilesJavaDeps(t *testing.T) {
+	ctx := testApex(t, apex_default_bp+`
+		apex {
+			name: "myapex",
+			manifest: ":myapex.manifest",
+			androidManifest: ":myapex.androidmanifest",
+			key: "myapex.key",
+			java_libs: [
+				"my_java_library_foo",
+				"my_java_library_bar",
+			],
+			updatable: false,
+		}
+
+		java_library {
+			name: "my_java_library_foo",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			static_libs: ["my_java_aconfig_library_foo"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		java_library {
+			name: "my_java_library_bar",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			static_libs: ["my_java_aconfig_library_bar"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		aconfig_declarations {
+			name: "my_aconfig_declarations_foo",
+			package: "com.example.package",
+			container: "myapex",
+			srcs: ["foo.aconfig"],
+		}
+
+		java_aconfig_library {
+			name: "my_java_aconfig_library_foo",
+			aconfig_declarations: "my_aconfig_declarations_foo",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		aconfig_declarations {
+			name: "my_aconfig_declarations_bar",
+			package: "com.example.package",
+			container: "myapex",
+			srcs: ["bar.aconfig"],
+		}
+
+		java_aconfig_library {
+			name: "my_java_aconfig_library_bar",
+			aconfig_declarations: "my_aconfig_declarations_bar",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+	`)
+
+	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	s := mod.Rule("apexRule").Args["copy_commands"]
+	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
+	if len(copyCmds) != 5 {
+		t.Fatalf("Expected 5 commands, got %d in:\n%s", len(copyCmds), s)
+	}
+
+	ensureMatches(t, copyCmds[4], "^cp -f .*/aconfig_flags.pb .*/image.apex$")
+
+	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
+	s = " " + combineAconfigRule.Args["cache_files"]
+	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
+	if len(aconfigArgs) != 2 {
+		t.Fatalf("Expected 2 commands, got %d in:\n%s", len(aconfigArgs), s)
+	}
+	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_foo/intermediate.pb")
+	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_bar/intermediate.pb")
+
+	buildParams := combineAconfigRule.BuildParams
+	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_foo/intermediate.pb")
+	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_bar/intermediate.pb")
+	ensureContains(t, buildParams.Output.String(), "android_common_myapex/aconfig_flags.pb")
+}
+
+func TestAconfigFilesJavaAndCcDeps(t *testing.T) {
+	ctx := testApex(t, apex_default_bp+`
+		apex {
+			name: "myapex",
+			manifest: ":myapex.manifest",
+			androidManifest: ":myapex.androidmanifest",
+			key: "myapex.key",
+			java_libs: [
+				"my_java_library_foo",
+			],
+			native_shared_libs: [
+				"my_cc_library_bar",
+			],
+			binaries: [
+				"my_cc_binary_baz",
+			],
+			updatable: false,
+		}
+
+		java_library {
+			name: "my_java_library_foo",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			static_libs: ["my_java_aconfig_library_foo"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		cc_library {
+			name: "my_cc_library_bar",
+			srcs: ["foo/bar/MyClass.cc"],
+			static_libs: ["my_cc_aconfig_library_bar"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		cc_binary {
+			name: "my_cc_binary_baz",
+			srcs: ["foo/bar/MyClass.cc"],
+			static_libs: ["my_cc_aconfig_library_baz"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		aconfig_declarations {
+			name: "my_aconfig_declarations_foo",
+			package: "com.example.package",
+			container: "myapex",
+			srcs: ["foo.aconfig"],
+		}
+
+		java_aconfig_library {
+			name: "my_java_aconfig_library_foo",
+			aconfig_declarations: "my_aconfig_declarations_foo",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		aconfig_declarations {
+			name: "my_aconfig_declarations_bar",
+			package: "com.example.package",
+			container: "myapex",
+			srcs: ["bar.aconfig"],
+		}
+
+		cc_aconfig_library {
+			name: "my_cc_aconfig_library_bar",
+			aconfig_declarations: "my_aconfig_declarations_bar",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		aconfig_declarations {
+			name: "my_aconfig_declarations_baz",
+			package: "com.example.package",
+			container: "myapex",
+			srcs: ["baz.aconfig"],
+		}
+
+		cc_aconfig_library {
+			name: "my_cc_aconfig_library_baz",
+			aconfig_declarations: "my_aconfig_declarations_baz",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		cc_library {
+			name: "server_configurable_flags",
+			srcs: ["server_configurable_flags.cc"],
+		}
+	`)
+
+	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	s := mod.Rule("apexRule").Args["copy_commands"]
+	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
+	if len(copyCmds) != 9 {
+		t.Fatalf("Expected 9 commands, got %d in:\n%s", len(copyCmds), s)
+	}
+
+	ensureMatches(t, copyCmds[8], "^cp -f .*/aconfig_flags.pb .*/image.apex$")
+
+	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
+	s = " " + combineAconfigRule.Args["cache_files"]
+	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
+	if len(aconfigArgs) != 3 {
+		t.Fatalf("Expected 3 commands, got %d in:\n%s", len(aconfigArgs), s)
+	}
+	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_foo/intermediate.pb")
+	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_bar/intermediate.pb")
+	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_baz/intermediate.pb")
+
+	buildParams := combineAconfigRule.BuildParams
+	if len(buildParams.Inputs) != 3 {
+		t.Fatalf("Expected 3 input, got %d", len(buildParams.Inputs))
+	}
+	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_foo/intermediate.pb")
+	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_bar/intermediate.pb")
+	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_baz/intermediate.pb")
+	ensureContains(t, buildParams.Output.String(), "android_common_myapex/aconfig_flags.pb")
+}
+
+func TestAconfigFilesOnlyMatchCurrentApex(t *testing.T) {
+	ctx := testApex(t, apex_default_bp+`
+		apex {
+			name: "myapex",
+			manifest: ":myapex.manifest",
+			androidManifest: ":myapex.androidmanifest",
+			key: "myapex.key",
+			java_libs: [
+				"my_java_library_foo",
+				"other_java_library_bar",
+			],
+			updatable: false,
+		}
+
+		java_library {
+			name: "my_java_library_foo",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			static_libs: ["my_java_aconfig_library_foo"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		java_library {
+			name: "other_java_library_bar",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			static_libs: ["other_java_aconfig_library_bar"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		aconfig_declarations {
+			name: "my_aconfig_declarations_foo",
+			package: "com.example.package",
+			container: "myapex",
+			srcs: ["foo.aconfig"],
+		}
+
+		java_aconfig_library {
+			name: "my_java_aconfig_library_foo",
+			aconfig_declarations: "my_aconfig_declarations_foo",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		aconfig_declarations {
+			name: "other_aconfig_declarations_bar",
+			package: "com.example.package",
+			container: "otherapex",
+			srcs: ["bar.aconfig"],
+		}
+
+		java_aconfig_library {
+			name: "other_java_aconfig_library_bar",
+			aconfig_declarations: "other_aconfig_declarations_bar",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+	`)
+
+	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
+	s := " " + combineAconfigRule.Args["cache_files"]
+	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
+	if len(aconfigArgs) != 1 {
+		t.Fatalf("Expected 1 commands, got %d in:\n%s", len(aconfigArgs), s)
+	}
+	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_foo/intermediate.pb")
+
+	buildParams := combineAconfigRule.BuildParams
+	if len(buildParams.Inputs) != 1 {
+		t.Fatalf("Expected 1 input, got %d", len(buildParams.Inputs))
+	}
+	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_foo/intermediate.pb")
+	ensureContains(t, buildParams.Output.String(), "android_common_myapex/aconfig_flags.pb")
+}
+
+func TestAconfigFilesRemoveDuplicates(t *testing.T) {
+	ctx := testApex(t, apex_default_bp+`
+		apex {
+			name: "myapex",
+			manifest: ":myapex.manifest",
+			androidManifest: ":myapex.androidmanifest",
+			key: "myapex.key",
+			java_libs: [
+				"my_java_library_foo",
+				"my_java_library_bar",
+			],
+			updatable: false,
+		}
+
+		java_library {
+			name: "my_java_library_foo",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			static_libs: ["my_java_aconfig_library_foo"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		java_library {
+			name: "my_java_library_bar",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			static_libs: ["my_java_aconfig_library_bar"],
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		aconfig_declarations {
+			name: "my_aconfig_declarations_foo",
+			package: "com.example.package",
+			container: "myapex",
+			srcs: ["foo.aconfig"],
+		}
+
+		java_aconfig_library {
+			name: "my_java_aconfig_library_foo",
+			aconfig_declarations: "my_aconfig_declarations_foo",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+
+		java_aconfig_library {
+			name: "my_java_aconfig_library_bar",
+			aconfig_declarations: "my_aconfig_declarations_foo",
+			// TODO: remove //apex_available:platform
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+	`)
+
+	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
+	s := " " + combineAconfigRule.Args["cache_files"]
+	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
+	if len(aconfigArgs) != 1 {
+		t.Fatalf("Expected 1 commands, got %d in:\n%s", len(aconfigArgs), s)
+	}
+	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_foo/intermediate.pb")
+
+	buildParams := combineAconfigRule.BuildParams
+	if len(buildParams.Inputs) != 1 {
+		t.Fatalf("Expected 1 input, got %d", len(buildParams.Inputs))
+	}
+	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_foo/intermediate.pb")
+	ensureContains(t, buildParams.Output.String(), "android_common_myapex/aconfig_flags.pb")
+}
diff --git a/apex/builder.go b/apex/builder.go
index 3f358ac..3078863 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -24,6 +24,7 @@
 	"strconv"
 	"strings"
 
+	"android/soong/aconfig"
 	"android/soong/android"
 	"android/soong/java"
 
@@ -36,6 +37,7 @@
 )
 
 func init() {
+	pctx.Import("android/soong/aconfig")
 	pctx.Import("android/soong/android")
 	pctx.Import("android/soong/cc/config")
 	pctx.Import("android/soong/java")
@@ -80,6 +82,7 @@
 	pctx.HostBinToolVariable("conv_linker_config", "conv_linker_config")
 	pctx.HostBinToolVariable("assemble_vintf", "assemble_vintf")
 	pctx.HostBinToolVariable("apex_elf_checker", "apex_elf_checker")
+	pctx.HostBinToolVariable("aconfig", "aconfig")
 }
 
 var (
@@ -574,6 +577,7 @@
 
 		installMapSet[installMapPath.String()+":"+fi.installDir+"/"+fi.builtFile.Base()] = true
 	}
+
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
 
 	if len(installMapSet) > 0 {
@@ -628,10 +632,28 @@
 	outHostBinDir := ctx.Config().HostToolPath(ctx, "").String()
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
+	defaultReadOnlyFiles := []string{"apex_manifest.json", "apex_manifest.pb"}
+	if len(a.aconfigFiles) > 0 {
+		apexAconfigFile := android.PathForModuleOut(ctx, "aconfig_flags.pb")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        aconfig.AllDeclarationsRule,
+			Inputs:      a.aconfigFiles,
+			Output:      apexAconfigFile,
+			Description: "combine_aconfig_declarations",
+			Args: map[string]string{
+				"cache_files": android.JoinPathsWithPrefix(a.aconfigFiles, "--cache "),
+			},
+		})
+
+		copyCommands = append(copyCommands, "cp -f "+apexAconfigFile.String()+" "+imageDir.String())
+		implicitInputs = append(implicitInputs, apexAconfigFile)
+		defaultReadOnlyFiles = append(defaultReadOnlyFiles, apexAconfigFile.Base())
+	}
+
 	////////////////////////////////////////////////////////////////////////////////////
 	// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
 	// in this APEX. The file will be used by apexer in later steps.
-	cannedFsConfig := a.buildCannedFsConfig(ctx)
+	cannedFsConfig := a.buildCannedFsConfig(ctx, defaultReadOnlyFiles)
 	implicitInputs = append(implicitInputs, cannedFsConfig)
 
 	////////////////////////////////////////////////////////////////////////////////////
@@ -1082,8 +1104,8 @@
 	a.lintReports = java.BuildModuleLintReportZips(ctx, depSetsBuilder.Build())
 }
 
-func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.OutputPath {
-	var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
+func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext, defaultReadOnlyFiles []string) android.OutputPath {
+	var readOnlyPaths = defaultReadOnlyFiles
 	var executablePaths []string // this also includes dirs
 	var appSetDirs []string
 	appSetFiles := make(map[string]android.Path)
diff --git a/cc/Android.bp b/cc/Android.bp
index 77e96db..06e0e5e 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -9,6 +9,7 @@
         "blueprint",
         "blueprint-pathtools",
         "soong",
+        "soong-aconfig",
         "soong-aidl-library",
         "soong-android",
         "soong-bazel",
diff --git a/cc/cc.go b/cc/cc.go
index e215438..d2518a1 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -30,6 +30,7 @@
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
+	"android/soong/aconfig"
 	"android/soong/aidl_library"
 	"android/soong/android"
 	"android/soong/bazel/cquery"
@@ -923,6 +924,9 @@
 	apexSdkVersion android.ApiLevel
 
 	hideApexVariantFromMake bool
+
+	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
+	transitiveAconfigFiles map[string]*android.DepSet[android.Path]
 }
 
 func (c *Module) AddJSONData(d *map[string]interface{}) {
@@ -2335,6 +2339,8 @@
 		ctx.SetProvider(testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 	}
 
+	aconfig.CollectTransitiveAconfigFiles(ctx, &c.transitiveAconfigFiles)
+
 	c.maybeInstall(ctx, apexInfo)
 }
 
diff --git a/java/Android.bp b/java/Android.bp
index cf96871..6020d00 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -9,6 +9,7 @@
         "blueprint",
         "blueprint-pathtools",
         "soong",
+        "soong-aconfig",
         "soong-android",
         "soong-bazel",
         "soong-cc",
diff --git a/java/androidmk.go b/java/androidmk.go
index 84f78c8..fb3fefa 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -128,8 +128,8 @@
 					if library.dexpreopter.configPath != nil {
 						entries.SetPath("LOCAL_SOONG_DEXPREOPT_CONFIG", library.dexpreopter.configPath)
 					}
-
-					entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", library.getTransitiveAconfigFiles().ToList())
+					// TODO(b/311155208): The container here should be system.
+					entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", library.getTransitiveAconfigFiles(""))
 				},
 			},
 		})
@@ -306,7 +306,8 @@
 					if len(binary.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", binary.dexpreopter.builtInstalled)
 					}
-					entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", binary.getTransitiveAconfigFiles().ToList())
+					// TODO(b/311155208): The container here should be system.
+					entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", binary.getTransitiveAconfigFiles(""))
 				},
 			},
 			ExtraFooters: []android.AndroidMkExtraFootersFunc{
@@ -459,7 +460,8 @@
 				entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", app.linter.reports)
 
 				if app.Name() != "framework-res" {
-					entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", app.getTransitiveAconfigFiles().ToList())
+					// TODO(b/311155208): The container here should be system.
+					entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", app.getTransitiveAconfigFiles(""))
 				}
 			},
 		},
@@ -537,7 +539,8 @@
 		entries.SetPath("LOCAL_FULL_MANIFEST_FILE", a.mergedManifestFile)
 		entries.AddStrings("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", a.exportedProguardFlagFiles.Strings()...)
 		entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true)
-		entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", a.getTransitiveAconfigFiles().ToList())
+		// TODO(b/311155208): The container here should be system.
+		entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", a.getTransitiveAconfigFiles(""))
 	})
 
 	return entriesList
diff --git a/java/base.go b/java/base.go
index fdc164e..3bbae2e 100644
--- a/java/base.go
+++ b/java/base.go
@@ -25,6 +25,7 @@
 	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
 
+	"android/soong/aconfig"
 	"android/soong/android"
 	"android/soong/dexpreopt"
 	"android/soong/java/config"
@@ -512,13 +513,8 @@
 	// or the module should override Stem().
 	stem string
 
-	// Aconfig "cache files" that went directly into this module.  Transitive ones are
-	// tracked via JavaInfo.TransitiveAconfigFiles
-	// TODO: Extract to something standalone to propagate tags via GeneratedJavaLibraryModule
-	aconfigIntermediates android.Paths
-
-	// Aconfig files for all transitive deps.  Also exposed via JavaInfo
-	transitiveAconfigFiles *android.DepSet[android.Path]
+	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
+	transitiveAconfigFiles map[string]*android.DepSet[android.Path]
 }
 
 func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
@@ -1723,7 +1719,7 @@
 
 	ctx.CheckbuildFile(outputFile)
 
-	j.collectTransitiveAconfigFiles(ctx)
+	aconfig.CollectTransitiveAconfigFiles(ctx, &j.transitiveAconfigFiles)
 
 	ctx.SetProvider(JavaInfoProvider, JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(j.headerJarFile),
@@ -1740,7 +1736,6 @@
 		ExportedPluginClasses:          j.exportedPluginClasses,
 		ExportedPluginDisableTurbine:   j.exportedDisableTurbine,
 		JacocoReportClassesFile:        j.jacocoReportClassesFile,
-		TransitiveAconfigFiles:         j.transitiveAconfigFiles,
 	})
 
 	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
@@ -2081,32 +2076,8 @@
 	return Bool(j.properties.Installable)
 }
 
-func (j *Module) collectTransitiveAconfigFiles(ctx android.ModuleContext) {
-	// Aconfig files from this module
-	mine := j.aconfigIntermediates
-
-	// Aconfig files from transitive dependencies
-	fromDeps := []*android.DepSet[android.Path]{}
-	ctx.VisitDirectDeps(func(module android.Module) {
-		dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
-		if dep.TransitiveAconfigFiles != nil {
-			fromDeps = append(fromDeps, dep.TransitiveAconfigFiles)
-		}
-	})
-
-	// DepSet containing aconfig files myself and from dependencies
-	j.transitiveAconfigFiles = android.NewDepSet(android.POSTORDER, mine, fromDeps)
-}
-
-func (j *Module) AddAconfigIntermediate(path android.Path) {
-	j.aconfigIntermediates = append(j.aconfigIntermediates, path)
-}
-
-func (j *Module) getTransitiveAconfigFiles() *android.DepSet[android.Path] {
-	if j.transitiveAconfigFiles == nil {
-		panic(fmt.Errorf("java.Moduile: getTransitiveAconfigFiles called before collectTransitiveAconfigFiles module=%s", j.Name()))
-	}
-	return j.transitiveAconfigFiles
+func (j *Module) getTransitiveAconfigFiles(container string) []android.Path {
+	return j.transitiveAconfigFiles[container].ToList()
 }
 
 type sdkLinkType int
diff --git a/java/generated_java_library_test.go b/java/generated_java_library_test.go
index 7fbbfee..ac9524e 100644
--- a/java/generated_java_library_test.go
+++ b/java/generated_java_library_test.go
@@ -37,7 +37,6 @@
 }
 
 func (callbacks *JavaGenLibTestCallbacks) GenerateSourceJarBuildActions(module *GeneratedJavaLibraryModule, ctx android.ModuleContext) android.Path {
-	module.AddAconfigIntermediate(android.PathForOutput(ctx, "aconfig_cache_file"))
 	return android.PathForOutput(ctx, "blah.srcjar")
 }
 
diff --git a/java/java.go b/java/java.go
index bb9357c..4962698 100644
--- a/java/java.go
+++ b/java/java.go
@@ -297,15 +297,6 @@
 	// JacocoReportClassesFile is the path to a jar containing uninstrumented classes that will be
 	// instrumented by jacoco.
 	JacocoReportClassesFile android.Path
-
-	// set of aconfig flags for all transitive libs deps
-	// TODO(joeo): It would be nice if this were over in the aconfig package instead of here.
-	// In order to do that, generated_java_library would need a way doing
-	// collectTransitiveAconfigFiles with one of the callbacks, and having that automatically
-	// propagated. If we were to clean up more of the stuff on JavaInfo that's not part of
-	// core java rules (e.g. AidlIncludeDirs), then maybe adding more framework to do that would be
-	// worth it.
-	TransitiveAconfigFiles *android.DepSet[android.Path]
 }
 
 var JavaInfoProvider = blueprint.NewProvider(JavaInfo{})