Merge "Reapply "Upgrade to clang-r530567"" into main
diff --git a/android/Android.bp b/android/Android.bp
index 3c38148..774d24a 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -42,6 +42,7 @@
         "buildinfo_prop.go",
         "compliance_metadata.go",
         "config.go",
+        "container.go",
         "test_config.go",
         "configurable_properties.go",
         "configured_jars.go",
diff --git a/android/container.go b/android/container.go
new file mode 100644
index 0000000..c4fdd9c
--- /dev/null
+++ b/android/container.go
@@ -0,0 +1,233 @@
+// Copyright 2024 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 android
+
+import (
+	"reflect"
+	"slices"
+
+	"github.com/google/blueprint"
+)
+
+type StubsAvailableModule interface {
+	IsStubsModule() bool
+}
+
+// Returns true if the dependency module is a stubs module
+var depIsStubsModule = func(_ ModuleContext, _, dep Module) bool {
+	if stubsModule, ok := dep.(StubsAvailableModule); ok {
+		return stubsModule.IsStubsModule()
+	}
+	return false
+}
+
+// Labels of exception functions, which are used to determine special dependencies that allow
+// otherwise restricted inter-container dependencies
+type exceptionHandleFuncLabel int
+
+const (
+	checkStubs exceptionHandleFuncLabel = iota
+)
+
+// Functions cannot be used as a value passed in providers, because functions are not
+// hashable. As a workaround, the exceptionHandleFunc enum values are passed using providers,
+// and the corresponding functions are called from this map.
+var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]func(ModuleContext, Module, Module) bool{
+	checkStubs: depIsStubsModule,
+}
+
+type InstallableModule interface {
+	EnforceApiContainerChecks() bool
+}
+
+type restriction struct {
+	// container of the dependency
+	dependency *container
+
+	// Error message to be emitted to the user when the dependency meets this restriction
+	errorMessage string
+
+	// List of labels of allowed exception functions that allows bypassing this restriction.
+	// If any of the functions mapped to each labels returns true, this dependency would be
+	// considered allowed and an error will not be thrown.
+	allowedExceptions []exceptionHandleFuncLabel
+}
+type container struct {
+	// The name of the container i.e. partition, api domain
+	name string
+
+	// Map of dependency restricted containers.
+	restricted []restriction
+}
+
+var (
+	VendorContainer = &container{
+		name:       VendorVariation,
+		restricted: nil,
+	}
+	SystemContainer = &container{
+		name: "system",
+		restricted: []restriction{
+			{
+				dependency: VendorContainer,
+				errorMessage: "Module belonging to the system partition other than HALs is " +
+					"not allowed to depend on the vendor partition module, in order to support " +
+					"independent development/update cycles and to support the Generic System " +
+					"Image. Try depending on HALs, VNDK or AIDL instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{},
+			},
+		},
+	}
+	ProductContainer = &container{
+		name: ProductVariation,
+		restricted: []restriction{
+			{
+				dependency: VendorContainer,
+				errorMessage: "Module belonging to the product partition is not allowed to " +
+					"depend on the vendor partition module, as this may lead to security " +
+					"vulnerabilities. Try depending on the HALs or utilize AIDL instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{},
+			},
+		},
+	}
+	ApexContainer = initializeApexContainer()
+	CtsContainer  = &container{
+		name: "cts",
+		restricted: []restriction{
+			{
+				dependency: SystemContainer,
+				errorMessage: "CTS module should not depend on the modules belonging to the " +
+					"system partition, including \"framework\". Depending on the system " +
+					"partition may lead to disclosure of implementation details and regression " +
+					"due to API changes across platform versions. Try depending on the stubs instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+			},
+		},
+	}
+)
+
+func initializeApexContainer() *container {
+	apexContainer := &container{
+		name: "apex",
+		restricted: []restriction{
+			{
+				dependency: SystemContainer,
+				errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
+					"modules belonging to the system partition. Either statically depend on the " +
+					"module or convert the depending module to java_sdk_library and depend on " +
+					"the stubs.",
+				allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+			},
+		},
+	}
+
+	apexContainer.restricted = append(apexContainer.restricted, restriction{
+		dependency: apexContainer,
+		errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
+			"modules belonging to other Apex(es). Either include the depending " +
+			"module in the Apex or convert the depending module to java_sdk_library " +
+			"and depend on its stubs.",
+		allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+	})
+
+	return apexContainer
+}
+
+type ContainersInfo struct {
+	belongingContainers []*container
+
+	belongingApexes []ApexInfo
+}
+
+func (c *ContainersInfo) BelongingContainers() []*container {
+	return c.belongingContainers
+}
+
+var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]()
+
+// Determines if the module can be installed in the system partition or not.
+// Logic is identical to that of modulePartition(...) defined in paths.go
+func installInSystemPartition(ctx ModuleContext) bool {
+	module := ctx.Module()
+	return !module.InstallInTestcases() &&
+		!module.InstallInData() &&
+		!module.InstallInRamdisk() &&
+		!module.InstallInVendorRamdisk() &&
+		!module.InstallInDebugRamdisk() &&
+		!module.InstallInRecovery() &&
+		!module.InstallInVendor() &&
+		!module.InstallInOdm() &&
+		!module.InstallInProduct() &&
+		determineModuleKind(module.base(), ctx.blueprintBaseModuleContext()) == platformModule
+}
+
+func generateContainerInfo(ctx ModuleContext) ContainersInfo {
+	inSystem := installInSystemPartition(ctx)
+	inProduct := ctx.Module().InstallInProduct()
+	inVendor := ctx.Module().InstallInVendor()
+	inCts := false
+	inApex := false
+
+	if m, ok := ctx.Module().(ImageInterface); ok {
+		inProduct = inProduct || m.ProductVariantNeeded(ctx)
+		inVendor = inVendor || m.VendorVariantNeeded(ctx)
+	}
+
+	props := ctx.Module().GetProperties()
+	for _, prop := range props {
+		val := reflect.ValueOf(prop).Elem()
+		if val.Kind() == reflect.Struct {
+			testSuites := val.FieldByName("Test_suites")
+			if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") {
+				inCts = true
+			}
+		}
+	}
+
+	var belongingApexes []ApexInfo
+	if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
+		belongingApexes = apexInfo.ApexInfos
+		inApex = true
+	}
+
+	containers := []*container{}
+	if inSystem {
+		containers = append(containers, SystemContainer)
+	}
+	if inProduct {
+		containers = append(containers, ProductContainer)
+	}
+	if inVendor {
+		containers = append(containers, VendorContainer)
+	}
+	if inCts {
+		containers = append(containers, CtsContainer)
+	}
+	if inApex {
+		containers = append(containers, ApexContainer)
+	}
+
+	return ContainersInfo{
+		belongingContainers: containers,
+		belongingApexes:     belongingApexes,
+	}
+}
+
+func setContainerInfo(ctx ModuleContext) {
+	if _, ok := ctx.Module().(InstallableModule); ok {
+		containersInfo := generateContainerInfo(ctx)
+		SetProvider(ctx, ContainersInfoProvider, containersInfo)
+	}
+}
diff --git a/android/image.go b/android/image.go
index c278dcd..0f03107 100644
--- a/android/image.go
+++ b/android/image.go
@@ -22,7 +22,7 @@
 	// VendorVariantNeeded should return true if the module needs a vendor variant (installed on the vendor image).
 	VendorVariantNeeded(ctx BaseModuleContext) bool
 
-	// ProductVariantNeeded should return true if the module needs a product variant (unstalled on the product image).
+	// ProductVariantNeeded should return true if the module needs a product variant (installed on the product image).
 	ProductVariantNeeded(ctx BaseModuleContext) bool
 
 	// CoreVariantNeeded should return true if the module needs a core variant (installed on the system image).
diff --git a/android/module.go b/android/module.go
index 9f7cb37..dd56031 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1207,29 +1207,7 @@
 				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
-			// implement OutputFileProducer because there is no other way of accessing
-			// the paths for the specified tag.
-			ctx.PropertyErrorf("dist.tag",
-				"tag %s not supported because the module does not implement OutputFileProducer", tag)
-		}
 	}
-
 	return distFiles
 }
 
@@ -1779,6 +1757,8 @@
 		variables:         make(map[string]string),
 	}
 
+	setContainerInfo(ctx)
+
 	m.licenseMetadataFile = PathForModuleOut(ctx, "meta_lic")
 
 	dependencyInstallFiles, dependencyPackagingSpecs := m.computeInstallDeps(ctx)
@@ -2383,7 +2363,7 @@
 	// The name of the module.
 	moduleName string
 
-	// The tag that will be passed to the module's OutputFileProducer.OutputFiles(tag) method.
+	// The tag that will be used to get the specific output file(s).
 	tag string
 }
 
@@ -2437,14 +2417,7 @@
 	Srcs() Paths
 }
 
-// A module that implements OutputFileProducer can be referenced from any property that is tagged with `android:"path"`
-// using the ":module" syntax or ":module{.tag}" syntax and provides a list of output files to be used as if they were
-// listed in the property.
-type OutputFileProducer interface {
-	OutputFiles(tag string) (Paths, error)
-}
-
-// OutputFilesForModule returns the paths from an OutputFileProducer with the given tag.  On error, including if the
+// OutputFilesForModule returns the output file paths with the given tag. On error, including if the
 // module produced zero paths, it reports errors to the ctx and returns nil.
 func OutputFilesForModule(ctx PathContext, module blueprint.Module, tag string) Paths {
 	paths, err := outputFilesForModule(ctx, module, tag)
@@ -2455,7 +2428,7 @@
 	return paths
 }
 
-// OutputFileForModule returns the path from an OutputFileProducer with the given tag.  On error, including if the
+// OutputFileForModule returns the output file paths with the given tag.  On error, including if the
 // module produced zero or multiple paths, it reports errors to the ctx and returns nil.
 func OutputFileForModule(ctx PathContext, module blueprint.Module, tag string) Path {
 	paths, err := outputFilesForModule(ctx, module, tag)
diff --git a/android/neverallow.go b/android/neverallow.go
index 62c5e59..ef4b8b8 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -237,6 +237,7 @@
 			Without("name", "init_first_stage").
 			Without("name", "init_first_stage.microdroid").
 			With("install_in_root", "true").
+			NotModuleType("prebuilt_root").
 			Because("install_in_root is only for init_first_stage."),
 	}
 }
diff --git a/android/packaging.go b/android/packaging.go
index ae412e1..c247ed2 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -377,31 +378,59 @@
 // CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
 // entries into the specified directory.
 func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir WritablePath) (entries []string) {
-	if len(specs) == 0 {
+	dirsToSpecs := make(map[WritablePath]map[string]PackagingSpec)
+	dirsToSpecs[dir] = specs
+	return p.CopySpecsToDirs(ctx, builder, dirsToSpecs)
+}
+
+// CopySpecsToDirs is a helper that will add commands to the rule builder to copy the PackagingSpec
+// entries into corresponding directories.
+func (p *PackagingBase) CopySpecsToDirs(ctx ModuleContext, builder *RuleBuilder, dirsToSpecs map[WritablePath]map[string]PackagingSpec) (entries []string) {
+	empty := true
+	for _, specs := range dirsToSpecs {
+		if len(specs) > 0 {
+			empty = false
+			break
+		}
+	}
+	if empty {
 		return entries
 	}
+
 	seenDir := make(map[string]bool)
 	preparerPath := PathForModuleOut(ctx, "preparer.sh")
 	cmd := builder.Command().Tool(preparerPath)
 	var sb strings.Builder
 	sb.WriteString("set -e\n")
-	for _, k := range SortedKeys(specs) {
-		ps := specs[k]
-		destPath := filepath.Join(dir.String(), ps.relPathInPackage)
-		destDir := filepath.Dir(destPath)
-		entries = append(entries, ps.relPathInPackage)
-		if _, ok := seenDir[destDir]; !ok {
-			seenDir[destDir] = true
-			sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir))
-		}
-		if ps.symlinkTarget == "" {
-			cmd.Implicit(ps.srcPath)
-			sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath))
-		} else {
-			sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath))
-		}
-		if ps.executable {
-			sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath))
+
+	dirs := make([]WritablePath, 0, len(dirsToSpecs))
+	for dir, _ := range dirsToSpecs {
+		dirs = append(dirs, dir)
+	}
+	sort.Slice(dirs, func(i, j int) bool {
+		return dirs[i].String() < dirs[j].String()
+	})
+
+	for _, dir := range dirs {
+		specs := dirsToSpecs[dir]
+		for _, k := range SortedKeys(specs) {
+			ps := specs[k]
+			destPath := filepath.Join(dir.String(), ps.relPathInPackage)
+			destDir := filepath.Dir(destPath)
+			entries = append(entries, ps.relPathInPackage)
+			if _, ok := seenDir[destDir]; !ok {
+				seenDir[destDir] = true
+				sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir))
+			}
+			if ps.symlinkTarget == "" {
+				cmd.Implicit(ps.srcPath)
+				sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath))
+			} else {
+				sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath))
+			}
+			if ps.executable {
+				sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath))
+			}
 		}
 	}
 
diff --git a/android/paths.go b/android/paths.go
index 03772eb..dda48dd 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -463,8 +463,8 @@
 //   - glob, relative to the local module directory, resolves as filepath(s), relative to the local
 //     source directory.
 //   - other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
-//     or OutputFileProducer. These resolve as a filepath to an output filepath or generated source
-//     filepath.
+//     or set the OutputFilesProvider. These resolve as a filepath to an output filepath or generated
+//     source filepath.
 //
 // Properties passed as the paths argument must have been annotated with struct tag
 // `android:"path"` so that dependencies on SourceFileProducer modules will have already been handled by the
@@ -491,8 +491,8 @@
 //   - glob, relative to the local module directory, resolves as filepath(s), relative to the local
 //     source directory. Not valid in excludes.
 //   - other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
-//     or OutputFileProducer. These resolve as a filepath to an output filepath or generated source
-//     filepath.
+//     or set the OutputFilesProvider. These resolve as a filepath to an output filepath or generated
+//     source filepath.
 //
 // excluding the items (similarly resolved
 // Properties passed as the paths argument must have been annotated with struct tag
@@ -620,8 +620,8 @@
 //   - glob, relative to the local module directory, resolves as filepath(s), relative to the local
 //     source directory. Not valid in excludes.
 //   - other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
-//     or OutputFileProducer. These resolve as a filepath to an output filepath or generated source
-//     filepath.
+//     or set the OutputFilesProvider. These resolve as a filepath to an output filepath or generated
+//     source filepath.
 //
 // and a list of the module names of missing module dependencies are returned as the second return.
 // Properties passed as the paths argument must have been annotated with struct tag
diff --git a/android/testing.go b/android/testing.go
index 18fd3b3..e39a1a7 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -1018,31 +1018,21 @@
 	return normalizeStringMapRelativeToTop(m.config, m.module.VariablesForTests())
 }
 
-// OutputFiles first checks if module base outputFiles property has any output
+// OutputFiles checks if module base outputFiles property has any output
 // files can be used to return.
-// If not, it calls OutputFileProducer.OutputFiles on the
-// encapsulated module, exits the test immediately if there is an error and
+// Exits the test immediately if there is an error and
 // otherwise returns the result of calling Paths.RelativeToTop
 // on the returned Paths.
 func (m TestingModule) OutputFiles(t *testing.T, tag string) Paths {
-	// 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
+		return taggedOutputFiles.RelativeToTop()
 	}
 
-	producer, ok := m.module.(OutputFileProducer)
-	if !ok {
-		t.Fatalf("%q must implement OutputFileProducer\n", m.module.Name())
-	}
-	paths, err := producer.OutputFiles(tag)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	return paths.RelativeToTop()
+	t.Fatal(fmt.Errorf("No test output file has been set for tag %q", tag))
+	return nil
 }
 
 // TestingSingleton is wrapper around an android.Singleton that provides methods to find information about individual
diff --git a/android/util.go b/android/util.go
index e21e66b..3c0af2f 100644
--- a/android/util.go
+++ b/android/util.go
@@ -201,6 +201,12 @@
 	return listsDiffer, diff1, diff2
 }
 
+// Returns true if the two lists have common elements.
+func HasIntersection[T comparable](l1, l2 []T) bool {
+	_, a, b := ListSetDifference(l1, l2)
+	return len(a)+len(b) < len(setFromList(l1))+len(setFromList(l2))
+}
+
 // Returns true if the given string s is prefixed with any string in the given prefix list.
 func HasAnyPrefix(s string, prefixList []string) bool {
 	for _, prefix := range prefixList {
diff --git a/android/util_test.go b/android/util_test.go
index 8e73d83..6537d69 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -818,3 +818,52 @@
 		})
 	}
 }
+
+var hasIntersectionTestCases = []struct {
+	name     string
+	l1       []string
+	l2       []string
+	expected bool
+}{
+	{
+		name:     "empty",
+		l1:       []string{"a", "b", "c"},
+		l2:       []string{},
+		expected: false,
+	},
+	{
+		name:     "both empty",
+		l1:       []string{},
+		l2:       []string{},
+		expected: false,
+	},
+	{
+		name:     "identical",
+		l1:       []string{"a", "b", "c"},
+		l2:       []string{"a", "b", "c"},
+		expected: true,
+	},
+	{
+		name:     "duplicates",
+		l1:       []string{"a", "a", "a"},
+		l2:       []string{"a", "b", "c"},
+		expected: true,
+	},
+	{
+		name:     "duplicates with no intersection",
+		l1:       []string{"d", "d", "d", "d"},
+		l2:       []string{"a", "b", "c"},
+		expected: false,
+	},
+}
+
+func TestHasIntersection(t *testing.T) {
+	for _, testCase := range hasIntersectionTestCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			hasIntersection := HasIntersection(testCase.l1, testCase.l2)
+			if !reflect.DeepEqual(hasIntersection, testCase.expected) {
+				t.Errorf("expected %#v, got %#v", testCase.expected, hasIntersection)
+			}
+		})
+	}
+}
diff --git a/android/variable.go b/android/variable.go
index d144f7d..3b02bc7 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -132,9 +132,11 @@
 				Keep_symbols                 *bool
 				Keep_symbols_and_debug_frame *bool
 			}
-			Static_libs       []string
-			Whole_static_libs []string
-			Shared_libs       []string
+			Static_libs         []string
+			Exclude_static_libs []string
+			Whole_static_libs   []string
+			Shared_libs         []string
+			Jni_libs            []string
 
 			Cmdline []string
 
diff --git a/apex/Android.bp b/apex/Android.bp
index abae9e2..17fdfc3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,7 @@
         "apex_test.go",
         "bootclasspath_fragment_test.go",
         "classpath_element_test.go",
+        "container_test.go",
         "dexpreopt_bootjars_test.go",
         "platform_bootclasspath_test.go",
         "systemserver_classpath_fragment_test.go",
diff --git a/apex/apex.go b/apex/apex.go
index 10fe372..f9b30d4 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -2663,12 +2663,20 @@
 	})
 }
 
+// TODO (b/221087384): Remove this allowlist
+var (
+	updatableApexesWithCurrentMinSdkVersionAllowlist = []string{"com.android.profiling"}
+)
+
 // checkUpdatable enforces APEX and its transitive dep properties to have desired values for updatable APEXes.
 func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) {
 	if a.Updatable() {
 		if a.minSdkVersionValue(ctx) == "" {
 			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
 		}
+		if a.minSdkVersion(ctx).IsCurrent() && !android.InList(ctx.ModuleName(), updatableApexesWithCurrentMinSdkVersionAllowlist) {
+			ctx.PropertyErrorf("updatable", "updatable APEXes should not set min_sdk_version to current. Please use a finalized API level or a recognized in-development codename")
+		}
 		if a.UsePlatformApis() {
 			ctx.PropertyErrorf("updatable", "updatable APEXes can't use platform APIs")
 		}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 15c713b..a2dbbfc 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -11721,3 +11721,20 @@
 
 	java.CheckModuleHasDependency(t, res.TestContext, "myoverrideapex", "android_common_myoverrideapex_myoverrideapex", "foo")
 }
+
+func TestUpdatableApexMinSdkVersionCurrent(t *testing.T) {
+	testApexError(t, `"myapex" .*: updatable: updatable APEXes should not set min_sdk_version to current. Please use a finalized API level or a recognized in-development codename`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: true,
+			min_sdk_version: "current",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+}
diff --git a/apex/container_test.go b/apex/container_test.go
new file mode 100644
index 0000000..3931174
--- /dev/null
+++ b/apex/container_test.go
@@ -0,0 +1,329 @@
+// Copyright 2024 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 apex
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+	"fmt"
+	"testing"
+)
+
+var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
+	errorMessage := fmt.Sprintf("module %s container %s value differ", name, container)
+	android.AssertBoolEquals(t, errorMessage, expected, actual)
+}
+
+func TestApexDepsContainers(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mybootclasspathlib"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
+			],
+			updatable: true,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: [
+				"mybootclasspathlib",
+			],
+			apex_available: [
+				"myapex",
+			],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "mybootclasspathlib",
+			srcs: [
+				"mybootclasspathlib.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			compile_dex: true,
+			static_libs: [
+				"foo",
+				"baz",
+			],
+			libs: [
+				"bar",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "bar",
+			srcs:[
+				"A.java",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "baz",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+	`)
+	testcases := []struct {
+		moduleName        string
+		variant           string
+		isSystemContainer bool
+		isApexContainer   bool
+	}{
+		{
+			moduleName:        "mybootclasspathlib",
+			variant:           "android_common_myapex",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.impl",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.stubs",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "foo",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "bar",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "baz",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(c.moduleName, c.variant)
+		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
+		belongingContainers := containers.BelongingContainers()
+		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "apex", c.isApexContainer, android.InList(android.ApexContainer, belongingContainers))
+	}
+}
+
+func TestNonUpdatableApexDepsContainers(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mybootclasspathlib"),
+	).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",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: [
+				"mybootclasspathlib",
+			],
+			apex_available: [
+				"myapex",
+			],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "mybootclasspathlib",
+			srcs: [
+				"mybootclasspathlib.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			compile_dex: true,
+			static_libs: [
+				"foo",
+			],
+			libs: [
+				"bar",
+			],
+		}
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+		java_library {
+			name: "bar",
+			srcs:[
+				"A.java",
+			],
+		}
+	`)
+	testcases := []struct {
+		moduleName        string
+		variant           string
+		isSystemContainer bool
+		isApexContainer   bool
+	}{
+		{
+			moduleName:        "mybootclasspathlib",
+			variant:           "android_common_myapex",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.impl",
+			variant:           "android_common_apex10000",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.stubs",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "foo",
+			variant:           "android_common_apex10000",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "bar",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(c.moduleName, c.variant)
+		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
+		belongingContainers := containers.BelongingContainers()
+		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "apex", c.isApexContainer, android.InList(android.ApexContainer, belongingContainers))
+	}
+}
+
+func TestUpdatableAndNonUpdatableApexesIdenticalMinSdkVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		android.FixtureMergeMockFs(android.MockFS{
+			"system/sepolicy/apex/myapex_non_updatable-file_contexts": nil,
+			"system/sepolicy/apex/myapex_updatable-file_contexts":     nil,
+		}),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex_non_updatable",
+			key: "myapex_non_updatable.key",
+			java_libs: [
+				"foo",
+			],
+			updatable: false,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex_non_updatable.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		apex {
+			name: "myapex_updatable",
+			key: "myapex_updatable.key",
+			java_libs: [
+				"foo",
+			],
+			updatable: true,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex_updatable.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex_non_updatable",
+				"myapex_updatable",
+			],
+			min_sdk_version: "30",
+			sdk_version: "current",
+		}
+	`)
+
+	fooApexVariant := result.ModuleForTests("foo", "android_common_apex30")
+	containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), fooApexVariant.Module(), android.ContainersInfoProvider)
+	belongingContainers := containers.BelongingContainers()
+	checkContainerMatch(t, "foo", "system", true, android.InList(android.SystemContainer, belongingContainers))
+	checkContainerMatch(t, "foo", "apex", true, android.InList(android.ApexContainer, belongingContainers))
+}
diff --git a/cc/cc.go b/cc/cc.go
index 740be3a..d307be6 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1867,8 +1867,10 @@
 		"libdl":         true,
 		"libz":          true,
 		// art apex
+		// TODO(b/234351700): Remove this when com.android.art.debug is gone.
 		"libandroidio":    true,
 		"libdexfile":      true,
+		"libdexfiled":     true, // com.android.art.debug only
 		"libnativebridge": true,
 		"libnativehelper": true,
 		"libnativeloader": true,
diff --git a/cc/makevars.go b/cc/makevars.go
index 9d29aff..cd13965 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -138,7 +138,6 @@
 	ctx.Strict("CLANG_COVERAGE_HWASAN_FLAGS", strings.Join(clangCoverageHWASanFlags, " "))
 
 	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(asanCflags, " "))
-	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", strings.Join(asanLdflags, " "))
 
 	ctx.Strict("HWADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(hwasanCflags, " "))
 	ctx.Strict("HWADDRESS_SANITIZER_GLOBAL_OPTIONS", strings.Join(hwasanGlobalOptions, ","))
diff --git a/cc/sanitize.go b/cc/sanitize.go
index e6f00fa..64a313b 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -36,7 +36,6 @@
 	asanCflags = []string{
 		"-fno-omit-frame-pointer",
 	}
-	asanLdflags = []string{"-Wl,-u,__asan_preinit"}
 
 	// DO NOT ADD MLLVM FLAGS HERE! ADD THEM BELOW TO hwasanCommonFlags.
 	hwasanCflags = []string{
@@ -797,16 +796,17 @@
 			flags.RequiredInstructionSet = "arm"
 		}
 		flags.Local.CFlags = append(flags.Local.CFlags, asanCflags...)
-		flags.Local.LdFlags = append(flags.Local.LdFlags, asanLdflags...)
 
 		if Bool(sanProps.Writeonly) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-asan-instrument-reads=0")
 		}
 
 		if ctx.Host() {
-			// -nodefaultlibs (provided with libc++) prevents the driver from linking
-			// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
-			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-as-needed")
+			if !ctx.Darwin() { // ld64.lld doesn't know about '--no-as-needed'
+				// -nodefaultlibs (provided with libc++) prevents the driver from linking
+				// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
+				flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-as-needed")
+			}
 		} else {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-asan-globals=0")
 			if ctx.bootstrap() {
diff --git a/cmd/release_config/release_config_lib/release_config.go b/cmd/release_config/release_config_lib/release_config.go
index 6d71d93..adf0e62 100644
--- a/cmd/release_config/release_config_lib/release_config.go
+++ b/cmd/release_config/release_config_lib/release_config.go
@@ -192,6 +192,7 @@
 
 	workflowManual := rc_proto.Workflow(rc_proto.Workflow_MANUAL)
 	myDirsMap := make(map[int]bool)
+	myValueDirsMap := make(map[int]bool)
 	if isBuildPrefix && releasePlatformVersion != nil {
 		if MarshalValue(releasePlatformVersion.Value) != strings.ToUpper(config.Name) {
 			value := FlagValue{
@@ -226,6 +227,8 @@
 			config.PriorStagesMap[priorStage] = true
 		}
 		myDirsMap[contrib.DeclarationIndex] = true
+		// This path *could* provide a value for this release config.
+		myValueDirsMap[contrib.DeclarationIndex] = true
 		if config.AconfigFlagsOnly {
 			// AconfigFlagsOnly allows very very few build flag values, all of them are part of aconfig flags.
 			allowedFlags := map[string]bool{
@@ -243,10 +246,13 @@
 			if !ok {
 				return fmt.Errorf("Setting value for undefined flag %s in %s\n", name, value.path)
 			}
+			// Record that flag declarations from fa.DeclarationIndex were included in this release config.
 			myDirsMap[fa.DeclarationIndex] = true
+			// Do not set myValueDirsMap, since it just records that we *could* provide values here.
 			if fa.DeclarationIndex > contrib.DeclarationIndex {
 				// Setting location is to the left of declaration.
-				return fmt.Errorf("Setting value for flag %s not allowed in %s\n", name, value.path)
+				return fmt.Errorf("Setting value for flag %s (declared in %s) not allowed in %s\n",
+					name, filepath.Dir(configs.ReleaseConfigMaps[fa.DeclarationIndex].path), value.path)
 			}
 			if isRoot && *fa.FlagDeclaration.Workflow != workflowManual {
 				// The "root" release config can only contain workflow: MANUAL flags.
@@ -273,10 +279,14 @@
 	releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{strings.TrimSpace(strings.Join(myAconfigValueSets, " "))}}
 
 	directories := []string{}
+	valueDirectories := []string{}
 	for idx, confDir := range configs.configDirs {
 		if _, ok := myDirsMap[idx]; ok {
 			directories = append(directories, confDir)
 		}
+		if _, ok := myValueDirsMap[idx]; ok {
+			valueDirectories = append(valueDirectories, confDir)
+		}
 	}
 
 	// Now build the per-partition artifacts
@@ -316,6 +326,7 @@
 		AconfigValueSets: myAconfigValueSets,
 		Inherits:         myInherits,
 		Directories:      directories,
+		ValueDirectories: valueDirectories,
 		PriorStages:      SortedMapKeys(config.PriorStagesMap),
 	}
 
diff --git a/cmd/release_config/release_config_proto/build_flags_out.pb.go b/cmd/release_config/release_config_proto/build_flags_out.pb.go
index b246eb6..c63ea26 100644
--- a/cmd/release_config/release_config_proto/build_flags_out.pb.go
+++ b/cmd/release_config/release_config_proto/build_flags_out.pb.go
@@ -223,12 +223,18 @@
 	// The names of the release_config_artifacts from which we inherited.
 	// Included for reference only.
 	Inherits []string `protobuf:"bytes,5,rep,name=inherits" json:"inherits,omitempty"`
-	// The release config directories used for this config.
+	// The release config directories used for this config.  This includes
+	// directories that provide flag declarations, but do not provide any flag
+	// values specific to this release config.
 	// For example, "build/release".
 	Directories []string `protobuf:"bytes,6,rep,name=directories" json:"directories,omitempty"`
 	// Prior stage(s) for flag advancement (during development).
 	// Once a flag has met criteria in a prior stage, it can advance to this one.
 	PriorStages []string `protobuf:"bytes,7,rep,name=prior_stages,json=priorStages" json:"prior_stages,omitempty"`
+	// The release config directories that contribute directly to this release
+	// config.  The listed directories contain at least a `release_config` message
+	// for this release config.
+	ValueDirectories []string `protobuf:"bytes,8,rep,name=value_directories,json=valueDirectories" json:"value_directories,omitempty"`
 }
 
 func (x *ReleaseConfigArtifact) Reset() {
@@ -312,6 +318,13 @@
 	return nil
 }
 
+func (x *ReleaseConfigArtifact) GetValueDirectories() []string {
+	if x != nil {
+		return x.ValueDirectories
+	}
+	return nil
+}
+
 type ReleaseConfigsArtifact struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -412,7 +425,7 @@
 	0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
 	0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61,
 	0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x0e,
-	0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xb0,
+	0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xdd,
 	0x02, 0x0a, 0x17, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
 	0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
 	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f,
@@ -431,42 +444,44 @@
 	0x09, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x21,
 	0x0a, 0x0c, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x07,
 	0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x67, 0x65,
-	0x73, 0x52, 0x0e, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,
-	0x73, 0x22, 0xe8, 0x03, 0x0a, 0x18, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x73, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x5c,
-	0x0a, 0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x73, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63,
+	0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x52, 0x0e,
+	0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xe8,
+	0x03, 0x0a, 0x18, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x73, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x5c, 0x0a, 0x0e, 0x72,
+	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65,
+	0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x65,
+	0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x69, 0x0a, 0x15, 0x6f, 0x74, 0x68,
+	0x65, 0x72, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f,
+	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52,
+	0x13, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x17, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
+	0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x5f, 0x6d, 0x61, 0x70,
+	0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x50, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
 	0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x0d, 0x72,
-	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x69, 0x0a, 0x15,
-	0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x61, 0x6e,
-	0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61,
-	0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61,
-	0x63, 0x74, 0x52, 0x13, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
-	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x17, 0x72, 0x65, 0x6c, 0x65,
-	0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x5f,
-	0x6d, 0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x50, 0x2e, 0x61, 0x6e, 0x64, 0x72,
-	0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
-	0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
-	0x74, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d,
-	0x61, 0x70, 0x73, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x72, 0x65, 0x6c,
-	0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x73, 0x4d, 0x61,
-	0x70, 0x1a, 0x79, 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x4d, 0x61, 0x70, 0x73, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
-	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
-	0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x30, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
-	0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x72,
-	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61,
-	0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x33, 0x5a, 0x31,
-	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x72, 0x65,
-	0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x6c,
-	0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x73, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x2e, 0x52,
+	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x73,
+	0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
+	0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x73, 0x4d, 0x61, 0x70, 0x1a, 0x79,
+	0x0a, 0x19, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d,
+	0x61, 0x70, 0x73, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
+	0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x65,
+	0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61, 0x70, 0x52, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x33, 0x5a, 0x31, 0x61, 0x6e, 0x64,
+	0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61,
+	0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
+	0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
diff --git a/cmd/release_config/release_config_proto/build_flags_out.proto b/cmd/release_config/release_config_proto/build_flags_out.proto
index 2f1715b..4dc84e9 100644
--- a/cmd/release_config/release_config_proto/build_flags_out.proto
+++ b/cmd/release_config/release_config_proto/build_flags_out.proto
@@ -82,13 +82,20 @@
   // Included for reference only.
   repeated string inherits = 5;
 
-  // The release config directories used for this config.
+  // The release config directories used for this config.  This includes
+  // directories that provide flag declarations, but do not provide any flag
+  // values specific to this release config.
   // For example, "build/release".
   repeated string directories = 6;
 
   // Prior stage(s) for flag advancement (during development).
   // Once a flag has met criteria in a prior stage, it can advance to this one.
   repeated string prior_stages = 7;
+
+  // The release config directories that contribute directly to this release
+  // config.  The listed directories contain at least a `release_config` message
+  // for this release config.
+  repeated string value_directories = 8;
 }
 
 message release_configs_artifact {
diff --git a/docs/java.dot b/docs/java.dot
new file mode 100644
index 0000000..ad7628d
--- /dev/null
+++ b/docs/java.dot
@@ -0,0 +1,127 @@
+digraph java {
+	//rankdir="LR";
+	//splines="false";
+	//cluster=true;
+	//node [ ordering="in" ];
+	node [ shape="rect" style="rounded" color="blue" ];
+
+	{
+		rank="same";
+		lib_java_sources [ label="library\njava sources" group="lib" ];
+		lib2_java_sources [ label="library\njava sources" group="lib2" ];
+		app_java_sources [ label="app\njava sources" group="app" ];
+	}
+
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_java_classes [ label="library java\n.class files" ];
+		lib_java_headers [ label="library java\nheader .class files" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib_spacer [ style=invis width=4 ];
+		lib2_java_classes [ label="library java\n.class files" ];
+		lib2_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_combined_classes [ label="combined library\n.class files" ];
+		lib2_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		lib2_spacer [ style=invis width=4 ];
+		app_java_classes [ label="app java\n.class files" ];
+	}
+	{
+		rank="same";
+		app_combined_classes [ label="combined app and library\n.class files" ];
+	}
+	{
+		rank="same";
+		app_dex [ label="app classes.dex files" ];
+	}
+
+
+	node [ shape="rect" style="" color="black" ];
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_turbine_action [ label="turbine" ];
+		lib_javac_action [ label="javac" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib2_turbine_action [ label="turbine" ];
+		lib2_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib2_combine_action [ label="merge_zips" ];
+		lib2_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		app_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		app_combine_action [ label="merge_zips" ];
+	}
+	{
+		rank="same";
+		app_r8_action [ label="r8" ];
+	}
+
+	// library
+
+	lib_java_sources -> lib_turbine_action [ weight=100 ];
+	lib_turbine_action -> lib_java_headers [ weight=100 ];
+
+	lib_java_sources -> lib_javac_action [ weight=1000 ];
+	lib_javac_action -> lib_java_classes [ weight=100 ];
+
+	lib_java_headers -> lib_spacer [ style=invis ];
+
+	// library 2
+
+	lib_java_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_java_sources -> lib2_turbine_action [ weight=100 ];
+	lib2_turbine_action -> lib2_java_headers [ weight=100 ];
+
+	lib_java_headers -> lib2_javac_action [ weight=0 ];
+	lib2_java_sources -> lib2_javac_action [ weight=1000 ];
+	lib2_javac_action ->lib2_java_classes [ weight=100 ];
+
+	lib_java_classes -> lib2_combine_action [ weight=0 ];
+	lib2_java_classes -> lib2_combine_action [ weight=100 ];
+	lib2_combine_action -> lib2_combined_classes [ weight=100 ];
+
+	lib_java_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_java_headers -> lib2_combine_headers_action [ weight=100 ];
+	lib2_combine_headers_action -> lib2_combined_headers [ weight=100 ];
+
+	lib2_combined_headers -> lib2_spacer [ style=invis ];
+
+	// app
+
+	lib2_combined_headers -> app_javac_action [ weight=0 ];
+	app_java_sources -> app_javac_action [ weight=1000 ];
+	app_javac_action -> app_java_classes [ weight=100 ];
+
+	lib2_combined_classes -> app_combine_action [ weight=0 ];
+	app_java_classes -> app_combine_action [ weight=100 ];
+	app_combine_action -> app_combined_classes [ weight=100 ];
+
+	app_combined_classes -> app_r8_action;
+	app_r8_action -> app_dex [ weight=100 ];
+}
diff --git a/docs/kotlin.dot b/docs/kotlin.dot
new file mode 100644
index 0000000..7a23c16
--- /dev/null
+++ b/docs/kotlin.dot
@@ -0,0 +1,196 @@
+digraph java {
+	//rankdir="LR";
+	//splines="false";
+	//cluster=true;
+	ranksep="0.75 equally"
+	//node [ ordering="in" ];
+	node [ shape="rect" style="rounded" color="blue" ];
+	{
+		rank="same";
+		lib_java_sources [ label="library\njava sources" group="lib" ];
+		lib_kotlin_sources [ label="library\nkotlin sources" group="lib" ];
+		lib2_java_sources [ label="library\njava sources" group="lib2" ];
+		lib2_kotlin_sources [ label="library\nkotlin sources" group="lib2" ];
+		app_java_sources [ label="app\njava sources" group="app" ];
+		app_kotlin_sources [ label="app\nkotlin sources" group="app" ];
+	}
+
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_kotlin_classes [ label="library kotlin\n.class files" ];
+		lib_kotlin_headers [ label="library kotlin\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib_java_classes [ label="library java\n.class files" ];
+		lib_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib_combined_classes [ label="combined library\n.class files" ];
+		lib_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib_spacer [ style=invis width=4 ];
+		lib2_kotlin_classes [ label="library kotlin\n.class files" ];
+		lib2_kotlin_headers [ label="library kotlin\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_java_classes [ label="library java\n.class files" ];
+		lib2_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_combined_classes [ label="combined library\n.class files" ];
+		lib2_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		lib2_spacer [ style=invis width=4 ];
+		app_kotlin_classes [ label="app kotlin\n.class files" ];
+		app_kotlin_headers [ label="app kotlin\nheader .class files" ]	}
+	{
+		rank="same";
+		app_java_classes [ label="app java\n.class files" ];
+	}
+	{
+		rank="same";
+		app_combined_classes [ label="combined app and library\n.class files" ];
+	}
+	{
+		rank="same";
+		app_dex [ label="app classes.dex files" ];
+	}
+
+
+	node [ shape="rect" style="" color="black" ];
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		lib_turbine_action [ label="turbine" ];
+		lib_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib_combine_action [ label="merge_zips" ];
+		lib_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib2_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		lib2_turbine_action [ label="turbine" ];
+		lib2_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib2_combine_action [ label="merge_zips" ];
+		lib2_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		app_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		app_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		app_combine_action [ label="merge_zips" ];
+	}
+	{
+		rank="same";
+		app_r8_action [ label="r8" ];
+	}
+
+	// library
+
+	lib_kotlin_sources -> lib_kotlinc_action [ weight=100 ];
+	lib_java_sources -> lib_kotlinc_action;
+	lib_kotlinc_action -> lib_kotlin_classes, lib_kotlin_headers [ weight=100 ];
+
+	lib_kotlin_headers -> lib_turbine_action [ weight=0 ];
+	lib_java_sources -> lib_turbine_action [ weight=100 ];
+	lib_turbine_action -> lib_java_headers [ weight=100 ];
+
+	lib_kotlin_headers -> lib_javac_action [ weight=0 ];
+	lib_java_sources -> lib_javac_action [ weight=1000 ];
+	lib_javac_action -> lib_java_classes [ weight=100 ];
+
+	lib_kotlin_classes -> lib_combine_action [ weight = 0 ];
+	lib_java_classes -> lib_combine_action [ weight = 100 ];
+	lib_combine_action -> lib_combined_classes [ weight=100 ];
+
+	lib_kotlin_headers -> lib_combine_headers_action [ weight = 0 ];
+	lib_java_headers -> lib_combine_headers_action [ weight = 100 ];
+	lib_combine_headers_action -> lib_combined_headers [ weight=100 ];
+
+	lib_combined_headers -> lib_spacer [ style=invis ];
+
+	// library 2
+
+	lib_combined_headers -> lib2_kotlinc_action [ weight=0 ];
+	lib2_kotlin_sources -> lib2_kotlinc_action [ weight=100 ];
+	lib2_java_sources  -> lib2_kotlinc_action;
+	lib2_kotlinc_action -> lib2_kotlin_classes, lib2_kotlin_headers [ weight=100 ];
+
+	lib_combined_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_java_sources -> lib2_turbine_action [ weight=100 ];
+	lib2_turbine_action -> lib2_java_headers [ weight=100 ];
+
+	lib_combined_headers -> lib2_javac_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_javac_action [ weight=0 ];
+	lib2_java_sources -> lib2_javac_action [ weight=1000 ];
+	lib2_javac_action ->lib2_java_classes [ weight=100 ];
+
+	lib_combined_classes -> lib2_combine_action [ weight=0 ];
+	lib2_kotlin_classes -> lib2_combine_action [ weight=0 ];
+	lib2_java_classes -> lib2_combine_action [ weight=100 ];
+	lib2_combine_action -> lib2_combined_classes [ weight=100 ];
+
+	lib_combined_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_java_headers -> lib2_combine_headers_action [ weight=100 ];
+	lib2_combine_headers_action -> lib2_combined_headers [ weight=100 ];
+
+	lib2_combined_headers -> lib2_spacer [ style=invis ];
+
+	// app
+
+	lib2_combined_headers -> app_kotlinc_action [ weight=0 ];
+	app_kotlin_sources -> app_kotlinc_action [ weight=100 ];
+	app_java_sources -> app_kotlinc_action;
+	app_kotlinc_action -> app_kotlin_headers, app_kotlin_classes [ weight=100 ];
+
+	lib2_combined_headers -> app_javac_action [ weight=0 ];
+	app_kotlin_headers -> app_javac_action [ weight=0 ];
+	app_java_sources -> app_javac_action [ weight=1000 ];
+	app_javac_action -> app_java_classes [ weight=100 ];
+
+	lib2_combined_classes -> app_combine_action [ weight=0 ];
+	app_kotlin_classes -> app_combine_action [ weight=0 ];
+	app_java_classes -> app_combine_action [ weight=100 ];
+	app_combine_action -> app_combined_classes [ weight=100 ];
+
+	app_combined_classes -> app_r8_action;
+	app_r8_action -> app_dex [ weight=100 ];
+}
diff --git a/docs/kotlin_with_annotation_processors.dot b/docs/kotlin_with_annotation_processors.dot
new file mode 100644
index 0000000..70c9bf3
--- /dev/null
+++ b/docs/kotlin_with_annotation_processors.dot
@@ -0,0 +1,277 @@
+digraph java {
+	//rankdir="LR";
+	//splines="false";
+	//cluster=true;
+	ranksep="0.75 equally"
+	//node [ ordering="in" ];
+	node [ shape="rect" style="rounded" color="blue" ];
+	{
+		rank="same";
+		lib_java_sources [ label="library\njava sources" group="lib" ];
+		lib_kotlin_sources [ label="library\nkotlin sources" group="lib" ];
+		lib2_java_sources [ label="library\njava sources" group="lib2" ];
+		lib2_kotlin_sources [ label="library\nkotlin sources" group="lib2" ];
+		app_java_sources [ label="app\njava sources" group="app" ];
+		app_kotlin_sources [ label="app\nkotlin sources" group="app" ];
+	}
+
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_kotlin_stubs [ label="library\nkotlin stubs" ];
+	}
+	{
+		rank="same";
+		lib_apt_src_jar [ label="library annotation\nprocessor sources" ];
+	}
+	{
+		rank="same";
+		lib_kotlin_classes [ label="library kotlin\n.class files" ];
+		lib_kotlin_headers [ label="library kotlin\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib_java_classes [ label="library java\n.class files" ];
+		lib_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib_combined_classes [ label="combined library\n.class files" ];
+		lib_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib_spacer [ style=invis width=4 ];
+		lib2_kotlin_stubs [ label="library\nkotlin stubs" ];
+	}
+	{
+		rank="same";
+		lib2_apt_src_jar [ label="library annotation\nprocessor sources" ];
+	}
+	{
+		rank="same";
+		lib2_kotlin_classes [ label="library kotlin\n.class files" ];
+		lib2_kotlin_headers [ label="library kotlin\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_java_classes [ label="library java\n.class files" ];
+		lib2_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_combined_classes [ label="combined library\n.class files" ];
+		lib2_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		lib2_spacer [ style=invis width=4 ];
+		app_kotlin_stubs [ label="app\nkotlin stubs" ];
+	}
+	{
+		rank="same";
+		app_apt_src_jar [ label="app annotation\nprocessor sources" ];
+	}
+	{
+		rank="same";
+		app_kotlin_classes [ label="app kotlin\n.class files" ];
+		app_kotlin_headers [ label="app kotlin\nheader .class files" ]	}
+	{
+		rank="same";
+		app_java_classes [ label="app java\n.class files" ];
+	}
+	{
+		rank="same";
+		app_combined_classes [ label="combined app and library\n.class files" ];
+	}
+	{
+		rank="same";
+		app_dex [ label="app classes.dex files" ];
+	}
+
+
+	node [ shape="rect" style="" color="black" ];
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_kapt_action [ label="kapt" ];
+	}
+	{
+		rank="same";
+		lib_turbine_apt_action [ label="turbine apt" ];
+	}
+	{
+		rank="same";
+		lib_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		lib_turbine_action [ label="turbine" ];
+		lib_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib_combine_action [ label="merge_zips" ];
+		lib_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib2_kapt_action [ label="kapt" ];
+	}
+	{
+		rank="same";
+		lib2_turbine_apt_action [ label="turbine apt" ];
+	}
+	{
+		rank="same";
+		lib2_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		lib2_turbine_action [ label="turbine" ];
+		lib2_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib2_combine_action [ label="merge_zips" ];
+		lib2_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		app_kapt_action [ label="kapt" ];
+	}
+	{
+		rank="same";
+		app_turbine_apt_action [ label="turbine apt" ];
+	}
+	{
+		rank="same";
+		app_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		app_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		app_combine_action [ label="merge_zips" ];
+	}
+	{
+		rank="same";
+		app_r8_action [ label="r8" ];
+	}
+
+	// library
+
+	lib_kotlin_sources -> lib_kapt_action [ weight=0 ];
+	lib_java_sources -> lib_kapt_action;
+	lib_kapt_action -> lib_kotlin_stubs [ weight=100 ];
+
+	lib_kotlin_stubs -> lib_turbine_apt_action [ weight=100 ];
+	lib_turbine_apt_action -> lib_apt_src_jar [ weight=100 ];
+
+	lib_apt_src_jar -> lib_kotlinc_action [ weight=0 ];
+	lib_kotlin_sources -> lib_kotlinc_action [ weight=100 ];
+	lib_java_sources -> lib_kotlinc_action;
+	lib_kotlinc_action -> lib_kotlin_classes, lib_kotlin_headers [ weight=100 ];
+
+	lib_apt_src_jar -> lib_turbine_action [ weight=0 ];
+	lib_kotlin_headers -> lib_turbine_action [ weight=0 ];
+	lib_java_sources -> lib_turbine_action [ weight=100 ];
+	lib_turbine_action -> lib_java_headers [ weight=100 ];
+
+	lib_apt_src_jar -> lib_javac_action [ weight=0 ];
+	lib_kotlin_headers -> lib_javac_action [ weight=0 ];
+	lib_java_sources -> lib_javac_action [ weight=1000 ];
+	lib_javac_action -> lib_java_classes [ weight=100 ];
+
+	lib_kotlin_classes -> lib_combine_action [ weight = 0 ];
+	lib_java_classes -> lib_combine_action [ weight = 100 ];
+	lib_combine_action -> lib_combined_classes [ weight=100 ];
+
+	lib_kotlin_headers -> lib_combine_headers_action [ weight = 0 ];
+	lib_java_headers -> lib_combine_headers_action [ weight = 100 ];
+	lib_combine_headers_action -> lib_combined_headers [ weight=100 ];
+
+	lib_combined_headers -> lib_spacer [ style=invis ];
+
+	// library 2
+
+	lib_combined_headers -> lib2_kapt_action [ weight=0 ];
+	lib2_kotlin_sources -> lib2_kapt_action [ weight=0 ];
+	lib2_java_sources -> lib2_kapt_action;
+	lib2_kapt_action -> lib2_kotlin_stubs [ weight=100 ];
+
+	lib_combined_headers -> lib2_turbine_apt_action [ weight=0 ];
+	lib2_kotlin_stubs -> lib2_turbine_apt_action [ weight=100 ];
+	lib2_turbine_apt_action -> lib2_apt_src_jar [ weight=100 ];
+
+	lib_combined_headers -> lib2_kotlinc_action [ weight=0 ];
+	lib2_apt_src_jar -> lib2_kotlinc_action [ weight=0 ];
+	lib2_kotlin_sources -> lib2_kotlinc_action [ weight=100 ];
+	lib2_java_sources  -> lib2_kotlinc_action;
+	lib2_kotlinc_action -> lib2_kotlin_classes, lib2_kotlin_headers [ weight=100 ];
+
+	lib_combined_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_apt_src_jar -> lib2_turbine_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_java_sources -> lib2_turbine_action [ weight=100 ];
+	lib2_turbine_action -> lib2_java_headers [ weight=100 ];
+
+	lib_combined_headers -> lib2_javac_action [ weight=0 ];
+	lib2_apt_src_jar -> lib2_javac_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_javac_action [ weight=0 ];
+	lib2_java_sources -> lib2_javac_action [ weight=1000 ];
+	lib2_javac_action ->lib2_java_classes [ weight=100 ];
+
+	lib_combined_classes -> lib2_combine_action [ weight=0 ];
+	lib2_kotlin_classes -> lib2_combine_action [ weight=0 ];
+	lib2_java_classes -> lib2_combine_action [ weight=100 ];
+	lib2_combine_action -> lib2_combined_classes [ weight=100 ];
+
+	lib_combined_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_java_headers -> lib2_combine_headers_action [ weight=100 ];
+	lib2_combine_headers_action -> lib2_combined_headers [ weight=100 ];
+
+	lib2_combined_headers -> lib2_spacer [ style=invis ];
+
+	// app
+
+	lib2_combined_headers -> app_kapt_action [ weight=0 ];
+	app_kotlin_sources -> app_kapt_action [ weight=0 ];
+	app_java_sources -> app_kapt_action;
+	app_kapt_action -> app_kotlin_stubs [ weight=100 ];
+
+	lib2_combined_headers -> app_turbine_apt_action [ weight=0 ];
+	app_kotlin_stubs -> app_turbine_apt_action [ weight=100 ];
+	app_turbine_apt_action -> app_apt_src_jar [ weight=100 ];
+
+	lib2_combined_headers -> app_kotlinc_action [ weight=0 ];
+	app_apt_src_jar -> app_kotlinc_action [ weight=0 ];
+	app_kotlin_sources -> app_kotlinc_action [ weight=100 ];
+	app_java_sources -> app_kotlinc_action;
+	app_kotlinc_action -> app_kotlin_headers, app_kotlin_classes [ weight=100 ];
+
+	lib2_combined_headers -> app_javac_action [ weight=0 ];
+	app_apt_src_jar -> app_javac_action [ weight=0 ];
+	app_kotlin_headers -> app_javac_action [ weight=0 ];
+	app_java_sources -> app_javac_action [ weight=1000 ];
+	app_javac_action -> app_java_classes [ weight=100 ];
+
+	lib2_combined_classes -> app_combine_action [ weight=0 ];
+	app_kotlin_classes -> app_combine_action [ weight=0 ];
+	app_java_classes -> app_combine_action [ weight=100 ];
+	app_combine_action -> app_combined_classes [ weight=100 ];
+
+	app_combined_classes -> app_r8_action;
+	app_r8_action -> app_dex [ weight=100 ];
+}
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index d04b2d1..fc6d1f7 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -126,6 +126,15 @@
 	Relative_install_path *string `android:"arch_variant"`
 }
 
+type prebuiltRootProperties struct {
+	// Install this module to the root directory, without partition subdirs.  When this module is
+	// added to PRODUCT_PACKAGES, this module will be installed to $PRODUCT_OUT/root, which will
+	// then be copied to the root of system.img. When this module is packaged by other modules like
+	// android_filesystem, this module will be installed to the root ("/"), unlike normal
+	// prebuilt_root modules which are installed to the partition subdir (e.g. "/system/").
+	Install_in_root *bool
+}
+
 type PrebuiltEtcModule interface {
 	android.Module
 
@@ -140,7 +149,12 @@
 	android.ModuleBase
 	android.DefaultableModuleBase
 
-	properties       prebuiltEtcProperties
+	properties prebuiltEtcProperties
+
+	// rootProperties is used to return the value of the InstallInRoot() method. Currently, only
+	// prebuilt_avb and prebuilt_root modules use this.
+	rootProperties prebuiltRootProperties
+
 	subdirProperties prebuiltSubdirProperties
 
 	sourceFilePaths android.Paths
@@ -156,9 +170,6 @@
 	additionalDependencies *android.Paths
 
 	usedSrcsProperty bool
-	// installInRoot is used to return the value of the InstallInRoot() method. The default value is false.
-	// Currently, only prebuilt_avb can be set to true.
-	installInRoot bool
 
 	makeClass string
 }
@@ -246,7 +257,7 @@
 }
 
 func (p *PrebuiltEtc) InstallInRoot() bool {
-	return p.installInRoot
+	return proptools.Bool(p.rootProperties.Install_in_root)
 }
 
 func (p *PrebuiltEtc) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
@@ -502,21 +513,20 @@
 
 func InitPrebuiltEtcModule(p *PrebuiltEtc, dirBase string) {
 	p.installDirBase = dirBase
-	p.installInRoot = false
 	p.AddProperties(&p.properties)
 	p.AddProperties(&p.subdirProperties)
 }
 
 func InitPrebuiltRootModule(p *PrebuiltEtc) {
 	p.installDirBase = "."
-	p.installInRoot = false
 	p.AddProperties(&p.properties)
+	p.AddProperties(&p.rootProperties)
 }
 
 func InitPrebuiltAvbModule(p *PrebuiltEtc) {
 	p.installDirBase = "avb"
-	p.installInRoot = true
 	p.AddProperties(&p.properties)
+	p.rootProperties.Install_in_root = proptools.BoolPtr(true)
 }
 
 // prebuilt_etc is for a prebuilt artifact that is installed in
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 5add954..5c7ef43 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -312,6 +312,25 @@
 	}
 }
 
+func (f *filesystem) copyPackagingSpecs(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, rootDir, rebasedDir android.WritablePath) []string {
+	rootDirSpecs := make(map[string]android.PackagingSpec)
+	rebasedDirSpecs := make(map[string]android.PackagingSpec)
+
+	for rel, spec := range specs {
+		if spec.Partition() == "root" {
+			rootDirSpecs[rel] = spec
+		} else {
+			rebasedDirSpecs[rel] = spec
+		}
+	}
+
+	dirsToSpecs := make(map[android.WritablePath]map[string]android.PackagingSpec)
+	dirsToSpecs[rootDir] = rootDirSpecs
+	dirsToSpecs[rebasedDir] = rebasedDirSpecs
+
+	return f.CopySpecsToDirs(ctx, builder, dirsToSpecs)
+}
+
 func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
 	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
 	rebasedDir := rootDir
@@ -322,7 +341,7 @@
 	// Wipe the root dir to get rid of leftover files from prior builds
 	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
 	specs := f.gatherFilteredPackagingSpecs(ctx)
-	f.entries = f.CopySpecsToDir(ctx, builder, specs, rebasedDir)
+	f.entries = f.copyPackagingSpecs(ctx, builder, specs, rootDir, rebasedDir)
 
 	f.buildNonDepsFiles(ctx, builder, rootDir)
 	f.addMakeBuiltFiles(ctx, builder, rootDir)
@@ -465,7 +484,7 @@
 	// Wipe the root dir to get rid of leftover files from prior builds
 	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
 	specs := f.gatherFilteredPackagingSpecs(ctx)
-	f.entries = f.CopySpecsToDir(ctx, builder, specs, rebasedDir)
+	f.entries = f.copyPackagingSpecs(ctx, builder, specs, rootDir, rebasedDir)
 
 	f.buildNonDepsFiles(ctx, builder, rootDir)
 	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 15cacfb..69d922d 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -94,9 +94,10 @@
 	return output
 }
 
-// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
-// Note that "apex" module installs its contents to "apex"(fake partition) as well
+// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" / "root"
+// partition.  Note that "apex" module installs its contents to "apex"(fake partition) as well
 // for symbol lookup by imitating "activated" paths.
 func (s *systemImage) filterPackagingSpec(ps android.PackagingSpec) bool {
-	return s.filesystem.filterInstallablePackagingSpec(ps) && ps.Partition() == "system"
+	return s.filesystem.filterInstallablePackagingSpec(ps) &&
+		(ps.Partition() == "system" || ps.Partition() == "root")
 }
diff --git a/java/Android.bp b/java/Android.bp
index 54b36ab..a941754 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -87,6 +87,7 @@
         "app_set_test.go",
         "app_test.go",
         "code_metadata_test.go",
+        "container_test.go",
         "bootclasspath_fragment_test.go",
         "device_host_converter_test.go",
         "dex_test.go",
diff --git a/java/base.go b/java/base.go
index fc68d20..02dc3e3 100644
--- a/java/base.go
+++ b/java/base.go
@@ -552,6 +552,18 @@
 	aconfigCacheFiles android.Paths
 }
 
+var _ android.InstallableModule = (*Module)(nil)
+
+// To satisfy the InstallableModule interface
+func (j *Module) EnforceApiContainerChecks() bool {
+	return true
+}
+
+// Overrides android.ModuleBase.InstallInProduct()
+func (j *Module) InstallInProduct() bool {
+	return j.ProductSpecific()
+}
+
 func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
 	sdkVersion := j.SdkVersion(ctx)
 	if sdkVersion.Stable() {
diff --git a/java/container_test.go b/java/container_test.go
new file mode 100644
index 0000000..3441855
--- /dev/null
+++ b/java/container_test.go
@@ -0,0 +1,129 @@
+// Copyright 2024 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 java
+
+import (
+	"android/soong/android"
+	"fmt"
+	"testing"
+)
+
+var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
+	errorMessage := fmt.Sprintf("module %s container %s value differ", name, container)
+	android.AssertBoolEquals(t, errorMessage, expected, actual)
+}
+
+func TestJavaContainersModuleProperties(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["A.java"],
+		}
+		java_library {
+			name: "foo_vendor",
+			srcs: ["A.java"],
+			vendor: true,
+			sdk_version: "current",
+		}
+		java_library {
+			name: "foo_soc_specific",
+			srcs: ["A.java"],
+			soc_specific: true,
+			sdk_version: "current",
+		}
+		java_library {
+			name: "foo_product_specific",
+			srcs: ["A.java"],
+			product_specific: true,
+			sdk_version: "current",
+		}
+		java_test {
+			name: "foo_cts_test",
+			srcs: ["A.java"],
+			test_suites: [
+				"cts",
+			],
+		}
+		java_test {
+			name: "foo_non_cts_test",
+			srcs: ["A.java"],
+			test_suites: [
+				"general-tests",
+			],
+		}
+	`)
+
+	testcases := []struct {
+		moduleName         string
+		isSystemContainer  bool
+		isVendorContainer  bool
+		isProductContainer bool
+		isCts              bool
+	}{
+		{
+			moduleName:         "foo",
+			isSystemContainer:  true,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_vendor",
+			isSystemContainer:  false,
+			isVendorContainer:  true,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_soc_specific",
+			isSystemContainer:  false,
+			isVendorContainer:  true,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_product_specific",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: true,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_cts_test",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              true,
+		},
+		{
+			moduleName:         "foo_non_cts_test",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              false,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(c.moduleName, "android_common")
+		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
+		belongingContainers := containers.BelongingContainers()
+		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "vendor", c.isVendorContainer, android.InList(android.VendorContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "product", c.isProductContainer, android.InList(android.ProductContainer, belongingContainers))
+	}
+}
diff --git a/rust/test.go b/rust/test.go
index 3087d8d..b7ddd06 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -116,7 +116,8 @@
 }
 
 func (test *testDecorator) install(ctx ModuleContext) {
-	testInstallBase := "/data/local/tests/unrestricted"
+	// TODO: (b/167308193) Switch to /data/local/tests/unrestricted as the default install base.
+	testInstallBase := "/data/local/tmp"
 	if ctx.RustModule().InVendorOrProduct() {
 		testInstallBase = "/data/local/tests/vendor"
 	}