Merge "Collect kythe, obj and tidy files using ModuleProxy." into main
diff --git a/android/Android.bp b/android/Android.bp
index cf707bd..a9a3564 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -31,6 +31,7 @@
     srcs: [
         "aconfig_providers.go",
         "all_teams.go",
+        "android_info.go",
         "androidmk.go",
         "apex.go",
         "apex_contributions.go",
@@ -121,6 +122,7 @@
         "apex_test.go",
         "arch_test.go",
         "blueprint_e2e_test.go",
+        "build_prop_test.go",
         "config_test.go",
         "configured_jars_test.go",
         "csuite_config_test.go",
diff --git a/android/android_info.go b/android/android_info.go
new file mode 100644
index 0000000..dd78ee4
--- /dev/null
+++ b/android/android_info.go
@@ -0,0 +1,77 @@
+// Copyright 2024 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 android
+
+import (
+	"github.com/google/blueprint/proptools"
+)
+
+type androidInfoProperties struct {
+	// Name of output file. Defaults to module name
+	Stem *string
+
+	// Paths of board-info.txt files.
+	Board_info_files []string `android:"path"`
+
+	// Name of bootloader board. If board_info_files is empty, `board={bootloader_board_name}` will
+	// be printed to output. Ignored if board_info_files is not empty.
+	Bootloader_board_name *string
+}
+
+type androidInfoModule struct {
+	ModuleBase
+
+	properties androidInfoProperties
+}
+
+func (p *androidInfoModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if len(p.properties.Board_info_files) > 0 && p.properties.Bootloader_board_name != nil {
+		ctx.ModuleErrorf("Either Board_info_files or Bootloader_board_name should be set. Please remove one of them\n")
+		return
+	}
+	outName := proptools.StringDefault(p.properties.Stem, ctx.ModuleName()+".txt")
+	androidInfoTxt := PathForModuleOut(ctx, outName).OutputPath
+	androidInfoProp := androidInfoTxt.ReplaceExtension(ctx, "prop")
+
+	rule := NewRuleBuilder(pctx, ctx)
+
+	if boardInfoFiles := PathsForModuleSrc(ctx, p.properties.Board_info_files); len(boardInfoFiles) > 0 {
+		rule.Command().Text("cat").Inputs(boardInfoFiles).
+			Text(" | grep").FlagWithArg("-v ", "'#'").FlagWithOutput("> ", androidInfoTxt)
+	} else if bootloaderBoardName := proptools.String(p.properties.Bootloader_board_name); bootloaderBoardName != "" {
+		rule.Command().Text("echo").Text("'board="+bootloaderBoardName+"'").FlagWithOutput("> ", androidInfoTxt)
+	} else {
+		rule.Command().Text("echo").Text("''").FlagWithOutput("> ", androidInfoTxt)
+	}
+
+	rule.Build(ctx.ModuleName(), "generating android-info.prop")
+
+	// Create android_info.prop
+	rule = NewRuleBuilder(pctx, ctx)
+	rule.Command().Text("cat").Input(androidInfoTxt).
+		Text(" | grep 'require version-' | sed -e 's/require version-/ro.build.expect./g' >").Output(androidInfoProp)
+	rule.Build(ctx.ModuleName()+"prop", "generating android-info.prop")
+
+	ctx.SetOutputFiles(Paths{androidInfoProp}, "")
+}
+
+// android_info module generate a file named android-info.txt that contains various information
+// about the device we're building for.  This file is typically packaged up with everything else.
+func AndroidInfoFactory() Module {
+	module := &androidInfoModule{}
+	module.AddProperties(&module.properties)
+	InitAndroidModule(module)
+	return module
+}
diff --git a/android/base_module_context.go b/android/base_module_context.go
index 223b534..02c0158 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"regexp"
+	"slices"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -570,7 +571,7 @@
 }
 
 func (b *baseModuleContext) GetWalkPath() []Module {
-	return b.walkPath
+	return slices.Clone(b.walkPath)
 }
 
 func (b *baseModuleContext) GetTagPath() []blueprint.DependencyTag {
diff --git a/android/build_prop.go b/android/build_prop.go
index 7c3c506..5547680 100644
--- a/android/build_prop.go
+++ b/android/build_prop.go
@@ -19,8 +19,12 @@
 )
 
 func init() {
-	ctx := InitRegistrationContext
+	registerBuildPropComponents(InitRegistrationContext)
+}
+
+func registerBuildPropComponents(ctx RegistrationContext) {
 	ctx.RegisterModuleType("build_prop", BuildPropFactory)
+	ctx.RegisterModuleType("android_info", AndroidInfoFactory)
 }
 
 type buildPropProperties struct {
@@ -38,6 +42,10 @@
 	// Path to a JSON file containing product configs.
 	Product_config *string `android:"path"`
 
+	// Path to android-info.txt file containing board specific info.
+	// This is empty for build.prop of all partitions except vendor.
+	Android_info *string `android:"path"`
+
 	// Optional subdirectory under which this file is installed into
 	Relative_install_path *string
 }
@@ -66,7 +74,10 @@
 	} else if partition == "odm" {
 		return ctx.Config().OdmPropFiles(ctx)
 	} else if partition == "vendor" {
-		// TODO (b/375500423): Add android-info.txt to prop files
+		if p.properties.Android_info != nil {
+			androidInfo := PathForModuleSrc(ctx, proptools.String(p.properties.Android_info))
+			return append(ctx.Config().VendorPropFiles(ctx), androidInfo)
+		}
 		return ctx.Config().VendorPropFiles(ctx)
 	}
 	return nil
@@ -111,13 +122,12 @@
 }
 
 func (p *buildPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	p.outputFilePath = PathForModuleOut(ctx, "build.prop").OutputPath
-	if !ctx.Config().KatiEnabled() {
-		WriteFileRule(ctx, p.outputFilePath, "# no build.prop if kati is disabled")
-		ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
-		return
+	if !p.SocSpecific() && p.properties.Android_info != nil {
+		ctx.ModuleErrorf("Android_info cannot be set if build.prop is not installed in vendor partition")
 	}
 
+	p.outputFilePath = PathForModuleOut(ctx, "build.prop").OutputPath
+
 	partition := p.partition(ctx.DeviceConfig())
 	if !InList(partition, validPartitions) {
 		ctx.PropertyErrorf("partition", "unsupported partition %q: only %q are supported", partition, validPartitions)
diff --git a/android/build_prop_test.go b/android/build_prop_test.go
new file mode 100644
index 0000000..e75975a
--- /dev/null
+++ b/android/build_prop_test.go
@@ -0,0 +1,41 @@
+// 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 (
+	"testing"
+)
+
+func TestPropFileInputs(t *testing.T) {
+	bp := `
+build_prop {
+    name: "vendor-build.prop",
+    stem: "build.prop",
+    vendor: true,
+    android_info: ":board-info",
+    //product_config: ":product_config",
+}
+android_info {
+    name: "board-info",
+    stem: "android-info.txt",
+}
+`
+
+	res := GroupFixturePreparers(
+		FixtureRegisterWithContext(registerBuildPropComponents),
+	).RunTestWithBp(t, bp)
+	buildPropCmd := res.ModuleForTests("vendor-build.prop", "").Rule("vendor-build.prop_.vendor-build.prop").RuleParams.Command
+	AssertStringDoesContain(t, "Could not find android-info in prop files of vendor build.prop", buildPropCmd, "--prop-files=out/soong/.intermediates/board-info/android-info.prop")
+}
diff --git a/android/config.go b/android/config.go
index 16d77db..275c07f 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1522,6 +1522,13 @@
 	return "vendor"
 }
 
+func (c *deviceConfig) VendorDlkmPath() string {
+	if c.config.productVariables.VendorDlkmPath != nil {
+		return *c.config.productVariables.VendorDlkmPath
+	}
+	return "vendor_dlkm"
+}
+
 func (c *deviceConfig) BuildingVendorImage() bool {
 	return proptools.Bool(c.config.productVariables.BuildingVendorImage)
 }
@@ -1553,6 +1560,13 @@
 	return proptools.Bool(c.config.productVariables.BuildingOdmImage)
 }
 
+func (c *deviceConfig) OdmDlkmPath() string {
+	if c.config.productVariables.OdmDlkmPath != nil {
+		return *c.config.productVariables.OdmDlkmPath
+	}
+	return "odm_dlkm"
+}
+
 func (c *deviceConfig) ProductPath() string {
 	if c.config.productVariables.ProductPath != nil {
 		return *c.config.productVariables.ProductPath
@@ -1571,6 +1585,20 @@
 	return "system_ext"
 }
 
+func (c *deviceConfig) SystemDlkmPath() string {
+	if c.config.productVariables.SystemDlkmPath != nil {
+		return *c.config.productVariables.SystemDlkmPath
+	}
+	return "system_dlkm"
+}
+
+func (c *deviceConfig) OemPath() string {
+	if c.config.productVariables.OemPath != nil {
+		return *c.config.productVariables.OemPath
+	}
+	return "oem"
+}
+
 func (c *deviceConfig) BtConfigIncludeDir() string {
 	return String(c.config.productVariables.BtConfigIncludeDir)
 }
diff --git a/android/variable.go b/android/variable.go
index df9db7c..142fab9 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -339,12 +339,16 @@
 	HWASanExcludePaths []string `json:",omitempty"`
 
 	VendorPath           *string `json:",omitempty"`
+	VendorDlkmPath       *string `json:",omitempty"`
 	BuildingVendorImage  *bool   `json:",omitempty"`
 	OdmPath              *string `json:",omitempty"`
 	BuildingOdmImage     *bool   `json:",omitempty"`
+	OdmDlkmPath          *string `json:",omitempty"`
 	ProductPath          *string `json:",omitempty"`
 	BuildingProductImage *bool   `json:",omitempty"`
 	SystemExtPath        *string `json:",omitempty"`
+	SystemDlkmPath       *string `json:",omitempty"`
+	OemPath              *string `json:",omitempty"`
 
 	ClangTidy  *bool   `json:",omitempty"`
 	TidyChecks *string `json:",omitempty"`
@@ -604,6 +608,9 @@
 	VendorLinkerConfigSrcs  []string `json:",omitempty"`
 	ProductLinkerConfigSrcs []string `json:",omitempty"`
 
+	BoardInfoFiles      []string `json:",omitempty"`
+	BootLoaderBoardName string   `json:",omitempty"`
+
 	ProductCopyFiles map[string]string `json:",omitempty"`
 
 	BuildingSystemDlkmImage bool     `json:",omitempty"`
diff --git a/apex/Android.bp b/apex/Android.bp
index 0e2f564..870ca7e 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -7,6 +7,7 @@
     pkgPath: "android/soong/apex",
     deps: [
         "blueprint",
+        "blueprint-bpmodify",
         "soong",
         "soong-aconfig",
         "soong-aconfig-codegen",
diff --git a/apex/apex.go b/apex/apex.go
index 80af9c5..587f63f 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -20,10 +20,12 @@
 	"fmt"
 	"path/filepath"
 	"regexp"
+	"slices"
 	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -464,6 +466,12 @@
 	// GenerateAndroidBuildActions.
 	filesInfo []apexFile
 
+	// List of files that were excluded by the unwanted_transitive_deps property.
+	unwantedTransitiveFilesInfo []apexFile
+
+	// List of files that were excluded due to conflicts with other variants of the same module.
+	duplicateTransitiveFilesInfo []apexFile
+
 	// List of other module names that should be installed when this APEX gets installed (LOCAL_REQUIRED_MODULES).
 	makeModulesToInstall []string
 
@@ -1877,6 +1885,14 @@
 
 	// visitor skips these from this list of module names
 	unwantedTransitiveDeps []string
+
+	// unwantedTransitiveFilesInfo contains files that would have been in the apex
+	// except that they were listed in unwantedTransitiveDeps.
+	unwantedTransitiveFilesInfo []apexFile
+
+	// duplicateTransitiveFilesInfo contains files that would ahve been in the apex
+	// except that another variant of the same module was already in the apex.
+	duplicateTransitiveFilesInfo []apexFile
 }
 
 func (vctx *visitorContext) normalizeFileInfo(mctx android.ModuleContext) {
@@ -1887,6 +1903,7 @@
 		// Needs additional verification for the resulting APEX to ensure that skipped artifacts don't make problems.
 		// For example, DT_NEEDED modules should be found within the APEX unless they are marked in `requiredNativeLibs`.
 		if f.transitiveDep && f.module != nil && android.InList(mctx.OtherModuleName(f.module), vctx.unwantedTransitiveDeps) {
+			vctx.unwantedTransitiveFilesInfo = append(vctx.unwantedTransitiveFilesInfo, f)
 			continue
 		}
 		dest := filepath.Join(f.installDir, f.builtFile.Base())
@@ -1897,6 +1914,8 @@
 				mctx.ModuleErrorf("apex file %v is provided by two different files %v and %v",
 					dest, e.builtFile, f.builtFile)
 				return
+			} else {
+				vctx.duplicateTransitiveFilesInfo = append(vctx.duplicateTransitiveFilesInfo, f)
 			}
 			// If a module is directly included and also transitively depended on
 			// consider it as directly included.
@@ -1911,6 +1930,7 @@
 	for _, v := range encountered {
 		vctx.filesInfo = append(vctx.filesInfo, v)
 	}
+
 	sort.Slice(vctx.filesInfo, func(i, j int) bool {
 		// Sort by destination path so as to ensure consistent ordering even if the source of the files
 		// changes.
@@ -2341,6 +2361,8 @@
 	// 3) some fields in apexBundle struct are configured
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	a.filesInfo = vctx.filesInfo
+	a.unwantedTransitiveFilesInfo = vctx.unwantedTransitiveFilesInfo
+	a.duplicateTransitiveFilesInfo = vctx.duplicateTransitiveFilesInfo
 
 	a.setPayloadFsType(ctx)
 	a.setSystemLibLink(ctx)
@@ -2367,6 +2389,8 @@
 
 	a.setOutputFiles(ctx)
 	a.enforcePartitionTagOnApexSystemServerJar(ctx)
+
+	a.verifyNativeImplementationLibs(ctx)
 }
 
 // Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
@@ -2920,3 +2944,105 @@
 func (a *apexBundle) IsTestApex() bool {
 	return a.testApex
 }
+
+// verifyNativeImplementationLibs compares the list of transitive implementation libraries used to link native
+// libraries in the apex against the list of implementation libraries in the apex, ensuring that none of the
+// libraries in the apex have references to private APIs from outside the apex.
+func (a *apexBundle) verifyNativeImplementationLibs(ctx android.ModuleContext) {
+	var directImplementationLibs android.Paths
+	var transitiveImplementationLibs []depset.DepSet[android.Path]
+
+	if a.properties.IsCoverageVariant {
+		return
+	}
+
+	if a.testApex {
+		return
+	}
+
+	if a.UsePlatformApis() {
+		return
+	}
+
+	checkApexTag := func(tag blueprint.DependencyTag) bool {
+		switch tag {
+		case sharedLibTag, jniLibTag, executableTag, androidAppTag:
+			return true
+		default:
+			return false
+		}
+	}
+
+	checkTransitiveTag := func(tag blueprint.DependencyTag) bool {
+		switch {
+		case cc.IsSharedDepTag(tag), java.IsJniDepTag(tag), rust.IsRlibDepTag(tag), rust.IsDylibDepTag(tag), checkApexTag(tag):
+			return true
+		default:
+			return false
+		}
+	}
+
+	var appEmbeddedJNILibs android.Paths
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		tag := ctx.OtherModuleDependencyTag(dep)
+		if !checkApexTag(tag) {
+			return
+		}
+		if tag == sharedLibTag || tag == jniLibTag {
+			outputFile := android.OutputFileForModule(ctx, dep, "")
+			directImplementationLibs = append(directImplementationLibs, outputFile)
+		}
+		if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+			transitiveImplementationLibs = append(transitiveImplementationLibs, info.ImplementationDeps)
+		}
+		if info, ok := android.OtherModuleProvider(ctx, dep, java.AppInfoProvider); ok {
+			appEmbeddedJNILibs = append(appEmbeddedJNILibs, info.EmbeddedJNILibs...)
+		}
+	})
+
+	depSet := depset.New(depset.PREORDER, directImplementationLibs, transitiveImplementationLibs)
+	allImplementationLibs := depSet.ToList()
+
+	allFileInfos := slices.Concat(a.filesInfo, a.unwantedTransitiveFilesInfo, a.duplicateTransitiveFilesInfo)
+
+	for _, lib := range allImplementationLibs {
+		inApex := slices.ContainsFunc(allFileInfos, func(fi apexFile) bool {
+			return fi.builtFile == lib
+		})
+		inApkInApex := slices.Contains(appEmbeddedJNILibs, lib)
+
+		if !inApex && !inApkInApex {
+			ctx.ModuleErrorf("library in apex transitively linked against implementation library %q not in apex", lib)
+			var depPath []android.Module
+			ctx.WalkDeps(func(child, parent android.Module) bool {
+				if depPath != nil {
+					return false
+				}
+
+				tag := ctx.OtherModuleDependencyTag(child)
+
+				if parent == ctx.Module() {
+					if !checkApexTag(tag) {
+						return false
+					}
+				}
+
+				if checkTransitiveTag(tag) {
+					if android.OutputFileForModule(ctx, child, "") == lib {
+						depPath = ctx.GetWalkPath()
+					}
+					return true
+				}
+
+				return false
+			})
+			if depPath != nil {
+				ctx.ModuleErrorf("dependency path:")
+				for _, m := range depPath {
+					ctx.ModuleErrorf("   %s", ctx.OtherModuleName(m))
+				}
+				return
+			}
+		}
+	}
+}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 988c1ce..54c1fac 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -20,6 +20,7 @@
 	"path/filepath"
 	"reflect"
 	"regexp"
+	"slices"
 	"sort"
 	"strconv"
 	"strings"
@@ -28,6 +29,7 @@
 	"android/soong/aconfig/codegen"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/bpmodify"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -225,6 +227,10 @@
 	"system/sepolicy/apex/myapex-file_contexts": nil,
 })
 
+var prepareForTestWithOtherapex = android.FixtureMergeMockFs(android.MockFS{
+	"system/sepolicy/apex/otherapex-file_contexts": nil,
+})
+
 // ensure that 'result' equals 'expected'
 func ensureEquals(t *testing.T, result string, expected string) {
 	t.Helper()
@@ -12114,3 +12120,398 @@
 	fileList := android.ContentFromFileRuleForTests(t, result, partition.Output("fileList"))
 	android.AssertDeepEquals(t, "filesystem with apex", "apex/myapex.apex\n", fileList)
 }
+
+func TestApexVerifyNativeImplementationLibs(t *testing.T) {
+	t.Parallel()
+
+	extractDepenencyPathFromErrors := func(errs []error) []string {
+		i := slices.IndexFunc(errs, func(err error) bool {
+			return strings.Contains(err.Error(), "dependency path:")
+		})
+		if i < 0 {
+			return nil
+		}
+		var dependencyPath []string
+		for _, err := range errs[i+1:] {
+			s := err.Error()
+			lastSpace := strings.LastIndexByte(s, ' ')
+			if lastSpace >= 0 {
+				dependencyPath = append(dependencyPath, s[lastSpace+1:])
+			}
+		}
+		return dependencyPath
+	}
+
+	checkErrors := func(wantDependencyPath []string) func(t *testing.T, result *android.TestResult) {
+		return func(t *testing.T, result *android.TestResult) {
+			t.Helper()
+			if len(result.Errs) == 0 {
+				t.Fatalf("expected errors")
+			}
+			t.Log("found errors:")
+			for _, err := range result.Errs {
+				t.Log(err)
+			}
+			if g, w := result.Errs[0].Error(), "library in apex transitively linked against implementation library"; !strings.Contains(g, w) {
+				t.Fatalf("expected error %q, got %q", w, g)
+			}
+			dependencyPath := extractDepenencyPathFromErrors(result.Errs)
+			if g, w := dependencyPath, wantDependencyPath; !slices.Equal(g, w) {
+				t.Errorf("expected dependency path %q, got %q", w, g)
+			}
+		}
+	}
+
+	addToSharedLibs := func(module, lib string) func(bp *bpmodify.Blueprint) {
+		return func(bp *bpmodify.Blueprint) {
+			m := bp.ModulesByName(module)
+			props, err := m.GetOrCreateProperty(bpmodify.List, "shared_libs")
+			if err != nil {
+				panic(err)
+			}
+			props.AddStringToList(lib)
+		}
+	}
+
+	bpTemplate := `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		native_shared_libs: ["mylib"],
+		rust_dyn_libs: ["libmyrust"],
+		binaries: ["mybin", "myrustbin"],
+		jni_libs: ["libjni"],
+		apps: ["myapp"],
+		updatable: false,
+	}
+
+	apex {
+		name: "otherapex",
+		key: "myapex.key",
+		native_shared_libs: ["libotherapex"],
+		updatable: false,
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	cc_library {
+		name: "mylib",
+		srcs: ["foo.cpp"],
+		apex_available: ["myapex"],
+	}
+
+	cc_binary {
+		name: "mybin",
+		srcs: ["foo.cpp"],
+		apex_available: ["myapex"],
+	}
+
+	rust_library {
+		name: "libmyrust",
+		crate_name: "myrust",
+		srcs: ["src/lib.rs"],
+		rustlibs: ["libmyrust_transitive_dylib"],
+		rlibs: ["libmyrust_transitive_rlib"],
+		apex_available: ["myapex"],
+	}
+
+	rust_library{
+		name: "libmyrust_transitive_dylib",
+		crate_name: "myrust_transitive_dylib",
+		srcs: ["src/lib.rs"],
+		apex_available: ["myapex"],
+	}
+
+	rust_library {
+		name: "libmyrust_transitive_rlib",
+		crate_name: "myrust_transitive_rlib",
+		srcs: ["src/lib.rs"],
+		apex_available: ["myapex"],
+	}
+
+	rust_binary {
+		name: "myrustbin",
+		srcs: ["src/main.rs"],
+		apex_available: ["myapex"],
+	}
+
+	cc_library {
+		name: "libbar",
+		sdk_version: "current",
+		srcs: ["bar.cpp"],
+		apex_available: ["myapex"],
+		stl: "none",
+	}
+
+	android_app {
+		name: "myapp",
+		jni_libs: ["libembeddedjni"],
+		use_embedded_native_libs: true,
+		sdk_version: "current",
+		apex_available: ["myapex"],
+	}
+
+	cc_library {
+		name: "libembeddedjni",
+		sdk_version: "current",
+		srcs: ["bar.cpp"],
+		apex_available: ["myapex"],
+		stl: "none",
+	}
+
+	cc_library {
+		name: "libjni",
+		sdk_version: "current",
+		srcs: ["bar.cpp"],
+		apex_available: ["myapex"],
+		stl: "none",
+	}
+
+	cc_library {
+		name: "libotherapex",
+		sdk_version: "current",
+		srcs: ["otherapex.cpp"],
+		apex_available: ["otherapex"],
+		stubs: {
+			symbol_file: "libotherapex.map.txt",
+			versions: ["1", "2", "3"],
+		},
+		stl: "none",
+	}
+
+	cc_library {
+		name: "libplatform",
+		sdk_version: "current",
+		srcs: ["libplatform.cpp"],
+		stubs: {
+			symbol_file: "libplatform.map.txt",
+			versions: ["1", "2", "3"],
+		},
+		stl: "none",
+		system_shared_libs: [],
+	}
+	`
+
+	testCases := []struct {
+		name           string
+		bpModifier     func(bp *bpmodify.Blueprint)
+		dependencyPath []string
+	}{
+		{
+			name:           "library dependency in other apex",
+			bpModifier:     addToSharedLibs("mylib", "libotherapex#impl"),
+			dependencyPath: []string{"myapex", "mylib", "libotherapex"},
+		},
+		{
+			name: "transitive library dependency in other apex",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("mylib", "libbar")(bp)
+				addToSharedLibs("libbar", "libotherapex#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "mylib", "libbar", "libotherapex"},
+		},
+		{
+			name:           "library dependency in platform",
+			bpModifier:     addToSharedLibs("mylib", "libplatform#impl"),
+			dependencyPath: []string{"myapex", "mylib", "libplatform"},
+		},
+		{
+			name:           "jni library dependency in other apex",
+			bpModifier:     addToSharedLibs("libjni", "libotherapex#impl"),
+			dependencyPath: []string{"myapex", "libjni", "libotherapex"},
+		},
+		{
+			name: "transitive jni library dependency in other apex",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("libjni", "libbar")(bp)
+				addToSharedLibs("libbar", "libotherapex#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "libjni", "libbar", "libotherapex"},
+		},
+		{
+			name:           "jni library dependency in platform",
+			bpModifier:     addToSharedLibs("libjni", "libplatform#impl"),
+			dependencyPath: []string{"myapex", "libjni", "libplatform"},
+		},
+		{
+			name: "transitive jni library dependency in platform",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("libjni", "libbar")(bp)
+				addToSharedLibs("libbar", "libplatform#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "libjni", "libbar", "libplatform"},
+		},
+		// TODO: embedded JNI in apps should be checked too, but Soong currently just packages the transitive
+		//  JNI libraries even if they came from another apex.
+		//{
+		//	name:           "app jni library dependency in other apex",
+		//	bpModifier:     addToSharedLibs("libembeddedjni", "libotherapex#impl"),
+		//	dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libotherapex"},
+		//},
+		//{
+		//	name: "transitive app jni library dependency in other apex",
+		//	bpModifier: func(bp *bpmodify.Blueprint) {
+		//		addToSharedLibs("libembeddedjni", "libbar")(bp)
+		//		addToSharedLibs("libbar", "libotherapex#impl")(bp)
+		//	},
+		//	dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libbar", "libotherapex"},
+		//},
+		//{
+		//	name:           "app jni library dependency in platform",
+		//	bpModifier:     addToSharedLibs("libembeddedjni", "libplatform#impl"),
+		//	dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libplatform"},
+		//},
+		//{
+		//	name: "transitive app jni library dependency in platform",
+		//	bpModifier: func(bp *bpmodify.Blueprint) {
+		//		addToSharedLibs("libembeddedjni", "libbar")(bp)
+		//		addToSharedLibs("libbar", "libplatform#impl")(bp)
+		//	},
+		//	dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libbar", "libplatform"},
+		//},
+		{
+			name:           "binary dependency in other apex",
+			bpModifier:     addToSharedLibs("mybin", "libotherapex#impl"),
+			dependencyPath: []string{"myapex", "mybin", "libotherapex"},
+		},
+		{
+			name: "transitive binary dependency in other apex",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("mybin", "libbar")(bp)
+				addToSharedLibs("libbar", "libotherapex#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "mybin", "libbar", "libotherapex"},
+		},
+		{
+			name:           "binary dependency in platform",
+			bpModifier:     addToSharedLibs("mybin", "libplatform#impl"),
+			dependencyPath: []string{"myapex", "mybin", "libplatform"},
+		},
+		{
+			name: "transitive binary dependency in platform",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("mybin", "libbar")(bp)
+				addToSharedLibs("libbar", "libplatform#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "mybin", "libbar", "libplatform"},
+		},
+
+		{
+			name:           "rust library dependency in other apex",
+			bpModifier:     addToSharedLibs("libmyrust", "libotherapex#impl"),
+			dependencyPath: []string{"myapex", "libmyrust", "libotherapex"},
+		},
+		{
+			name: "transitive rust library dependency in other apex",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("libmyrust", "libbar")(bp)
+				addToSharedLibs("libbar", "libotherapex#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "libmyrust", "libbar", "libotherapex"},
+		},
+		{
+			name:           "rust library dependency in platform",
+			bpModifier:     addToSharedLibs("libmyrust", "libplatform#impl"),
+			dependencyPath: []string{"myapex", "libmyrust", "libplatform"},
+		},
+		{
+			name: "transitive rust library dependency in platform",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("libmyrust", "libbar")(bp)
+				addToSharedLibs("libbar", "libplatform#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "libmyrust", "libbar", "libplatform"},
+		},
+		{
+			name: "transitive rust library dylib dependency in other apex",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("libmyrust_transitive_dylib", "libotherapex#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_dylib", "libotherapex"},
+		},
+		{
+			name: "transitive rust library dylib dependency in platform",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("libmyrust_transitive_dylib", "libplatform#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_dylib", "libplatform"},
+		},
+		{
+			name: "transitive rust library rlib dependency in other apex",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("libmyrust_transitive_rlib", "libotherapex#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_rlib", "libotherapex"},
+		},
+		{
+			name: "transitive rust library rlib dependency in platform",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("libmyrust_transitive_rlib", "libplatform#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_rlib", "libplatform"},
+		},
+		{
+			name:           "rust binary dependency in other apex",
+			bpModifier:     addToSharedLibs("myrustbin", "libotherapex#impl"),
+			dependencyPath: []string{"myapex", "myrustbin", "libotherapex"},
+		},
+		{
+			name: "transitive rust binary dependency in other apex",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("myrustbin", "libbar")(bp)
+				addToSharedLibs("libbar", "libotherapex#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "myrustbin", "libbar", "libotherapex"},
+		},
+		{
+			name:           "rust binary dependency in platform",
+			bpModifier:     addToSharedLibs("myrustbin", "libplatform#impl"),
+			dependencyPath: []string{"myapex", "myrustbin", "libplatform"},
+		},
+		{
+			name: "transitive rust binary dependency in platform",
+			bpModifier: func(bp *bpmodify.Blueprint) {
+				addToSharedLibs("myrustbin", "libbar")(bp)
+				addToSharedLibs("libbar", "libplatform#impl")(bp)
+			},
+			dependencyPath: []string{"myapex", "myrustbin", "libbar", "libplatform"},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			bp, err := bpmodify.NewBlueprint("", []byte(bpTemplate))
+			if err != nil {
+				t.Fatal(err)
+			}
+			if testCase.bpModifier != nil {
+				func() {
+					defer func() {
+						if r := recover(); r != nil {
+							t.Fatalf("panic in bpModifier: %v", r)
+						}
+					}()
+					testCase.bpModifier(bp)
+				}()
+			}
+			android.GroupFixturePreparers(
+				android.PrepareForTestWithAndroidBuildComponents,
+				cc.PrepareForTestWithCcBuildComponents,
+				java.PrepareForTestWithDexpreopt,
+				rust.PrepareForTestWithRustDefaultModules,
+				PrepareForTestWithApexBuildComponents,
+				prepareForTestWithMyapex,
+				prepareForTestWithOtherapex,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.BuildId = proptools.StringPtr("TEST.BUILD_ID")
+				}),
+			).ExtendWithErrorHandler(android.FixtureCustomErrorHandler(checkErrors(testCase.dependencyPath))).
+				RunTestWithBp(t, bp.String())
+		})
+	}
+}
diff --git a/apex/classpath_element_test.go b/apex/classpath_element_test.go
index f8e8899..f367174 100644
--- a/apex/classpath_element_test.go
+++ b/apex/classpath_element_test.go
@@ -45,10 +45,7 @@
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithArtApex,
 		prepareForTestWithMyapex,
-		// For otherapex.
-		android.FixtureMergeMockFs(android.MockFS{
-			"system/sepolicy/apex/otherapex-file_contexts": nil,
-		}),
+		prepareForTestWithOtherapex,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("foo", "othersdklibrary"),
 		java.FixtureConfigureApexBootJars("myapex:bar"),
diff --git a/cc/cc.go b/cc/cc.go
index 069a861..c6bc30e 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -19,8 +19,10 @@
 // is handled in builder.go
 
 import (
+	"errors"
 	"fmt"
 	"io"
+	"slices"
 	"strconv"
 	"strings"
 
@@ -229,6 +231,9 @@
 	// LLNDK headers for the ABI checker to check LLNDK implementation library.
 	LlndkIncludeDirs       android.Paths
 	LlndkSystemIncludeDirs android.Paths
+
+	directImplementationDeps     android.Paths
+	transitiveImplementationDeps []depset.DepSet[android.Path]
 }
 
 // LocalOrGlobalFlags contains flags that need to have values set globally by the build system or locally by the module
@@ -2052,6 +2057,10 @@
 		c.outputFile = android.OptionalPathForPath(outputFile)
 
 		c.maybeUnhideFromMake()
+
+		android.SetProvider(ctx, ImplementationDepInfoProvider, &ImplementationDepInfo{
+			ImplementationDeps: depset.New(depset.PREORDER, deps.directImplementationDeps, deps.transitiveImplementationDeps),
+		})
 	}
 
 	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: deps.GeneratedSources.Strings()})
@@ -2292,6 +2301,10 @@
 	deps.RuntimeLibs = android.LastUniqueStrings(deps.RuntimeLibs)
 	deps.LlndkHeaderLibs = android.LastUniqueStrings(deps.LlndkHeaderLibs)
 
+	if err := checkConflictingExplicitVersions(deps.SharedLibs); err != nil {
+		ctx.PropertyErrorf("shared_libs", "%s", err.Error())
+	}
+
 	for _, lib := range deps.ReexportSharedLibHeaders {
 		if !inList(lib, deps.SharedLibs) {
 			ctx.PropertyErrorf("export_shared_lib_headers", "Shared library not in shared_libs: '%s'", lib)
@@ -2319,6 +2332,26 @@
 	return deps
 }
 
+func checkConflictingExplicitVersions(libs []string) error {
+	withoutVersion := func(s string) string {
+		name, _ := StubsLibNameAndVersion(s)
+		return name
+	}
+	var errs []error
+	for i, lib := range libs {
+		libName := withoutVersion(lib)
+		libsToCompare := libs[i+1:]
+		j := slices.IndexFunc(libsToCompare, func(s string) bool {
+			return withoutVersion(s) == libName
+		})
+		if j >= 0 {
+			errs = append(errs, fmt.Errorf("duplicate shared libraries with different explicit versions: %q and %q",
+				lib, libsToCompare[j]))
+		}
+	}
+	return errors.Join(errs...)
+}
+
 func (c *Module) beginMutator(actx android.BottomUpMutatorContext) {
 	ctx := &baseModuleContext{
 		BaseModuleContext: actx,
@@ -2380,6 +2413,9 @@
 
 	if version != "" && canBeOrLinkAgainstVersionVariants(mod) {
 		// Version is explicitly specified. i.e. libFoo#30
+		if version == "impl" {
+			version = ""
+		}
 		variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
 		if tag, ok := depTag.(libraryDependencyTag); ok {
 			tag.explicitlyVersioned = true
@@ -3078,6 +3114,13 @@
 				linkFile = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
 				depFile = sharedLibraryInfo.TableOfContents
 
+				if !sharedLibraryInfo.IsStubs {
+					depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+					if info, ok := android.OtherModuleProvider(ctx, dep, ImplementationDepInfoProvider); ok {
+						depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+					}
+				}
+
 				ptr = &depPaths.SharedLibs
 				switch libDepTag.Order {
 				case earlyLibraryDependency:
diff --git a/cc/cc_test.go b/cc/cc_test.go
index e906706..22f7c9f 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -3321,3 +3321,20 @@
 		t.Errorf("expected %q in cflags, got %q", "-Xclang -verify", cFlags_cv)
 	}
 }
+
+func TestCheckConflictingExplicitVersions(t *testing.T) {
+	PrepareForIntegrationTestWithCc.
+		ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
+			`shared_libs: duplicate shared libraries with different explicit versions: "libbar" and "libbar#impl"`,
+		)).
+		RunTestWithBp(t, `
+			cc_library {
+				name: "libfoo",
+				shared_libs: ["libbar", "libbar#impl"],
+			}
+
+			cc_library {
+				name: "libbar",
+			}
+		`)
+}
diff --git a/cc/library.go b/cc/library.go
index 1f21614..7dffa72 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1194,6 +1194,7 @@
 		SharedLibrary:                        unstrippedOutputFile,
 		TransitiveStaticLibrariesForOrdering: transitiveStaticLibrariesForOrdering,
 		Target:                               ctx.Target(),
+		IsStubs:                              library.buildStubs(),
 	})
 
 	addStubDependencyProviders(ctx)
diff --git a/cc/linkable.go b/cc/linkable.go
index cd33e28..ef204eb 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -317,7 +317,9 @@
 	SharedLibrary android.Path
 	Target        android.Target
 
-	TableOfContents android.OptionalPath
+	TableOfContents    android.OptionalPath
+	IsStubs            bool
+	ImplementationDeps depset.DepSet[string]
 
 	// should be obtained from static analogue
 	TransitiveStaticLibrariesForOrdering depset.DepSet[android.Path]
@@ -386,3 +388,9 @@
 }
 
 var FlagExporterInfoProvider = blueprint.NewProvider[FlagExporterInfo]()
+
+var ImplementationDepInfoProvider = blueprint.NewProvider[*ImplementationDepInfo]()
+
+type ImplementationDepInfo struct {
+	ImplementationDeps depset.DepSet[android.Path]
+}
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 7c87297..ba4c662 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -221,6 +221,7 @@
 				Target:        ctx.Target(),
 
 				TableOfContents: p.tocFile,
+				IsStubs:         p.buildStubs(),
 			})
 
 			return outputFile
@@ -232,6 +233,7 @@
 		android.SetProvider(ctx, SharedLibraryInfoProvider, SharedLibraryInfo{
 			SharedLibrary: latestStub,
 			Target:        ctx.Target(),
+			IsStubs:       true,
 		})
 
 		return latestStub
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index e7dff40..4a2adf0 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -166,6 +166,7 @@
 			Target:        ctx.Target(),
 
 			TableOfContents: p.tocFile,
+			IsStubs:         false,
 		})
 
 		p.libraryDecorator.flagExporter.setProvider(ctx)
diff --git a/etc/adb_keys.go b/etc/adb_keys.go
index a2df41c..73bc347 100644
--- a/etc/adb_keys.go
+++ b/etc/adb_keys.go
@@ -36,6 +36,11 @@
 
 func (m *AdbKeysModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	productVariables := ctx.Config().ProductVariables()
+
+	if !m.ProductSpecific() {
+		ctx.ModuleErrorf("adb_keys module type must set product_specific to true")
+	}
+
 	if !(android.Bool(productVariables.Debuggable) && len(android.String(productVariables.AdbKeys)) > 0) {
 		m.SkipInstall()
 		return
@@ -48,7 +53,7 @@
 		Output: m.outputPath,
 		Input:  input.Path(),
 	})
-	m.installPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().ProductPath(), "etc/security")
+	m.installPath = android.PathForModuleInstall(ctx, "etc/security")
 	ctx.InstallFile(m.installPath, "adb_keys", m.outputPath)
 }
 
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 861ca3c..938f3bd 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -134,6 +134,18 @@
 
 	// Install symlinks to the installed file.
 	Symlinks []string `android:"arch_variant"`
+
+	// Install to partition system_dlkm when set to true.
+	System_dlkm_specific *bool `android:"arch_variant"`
+
+	// Install to partition vendor_dlkm when set to true.
+	Vendor_dlkm_specific *bool `android:"arch_variant"`
+
+	// Install to partition odm_dlkm when set to true.
+	Odm_dlkm_specific *bool `android:"arch_variant"`
+
+	// Install to partition oem when set to true.
+	Oem_specific *bool `android:"arch_variant"`
 }
 
 type prebuiltSubdirProperties struct {
@@ -371,6 +383,16 @@
 		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
 	}
 	baseInstallDirPath := android.PathForModuleInstall(ctx, p.installBaseDir(ctx), p.SubDir())
+	// TODO(b/377304441)
+	if android.Bool(p.properties.System_dlkm_specific) {
+		baseInstallDirPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().SystemDlkmPath(), p.installBaseDir(ctx), p.SubDir())
+	} else if android.Bool(p.properties.Vendor_dlkm_specific) {
+		baseInstallDirPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().VendorDlkmPath(), p.installBaseDir(ctx), p.SubDir())
+	} else if android.Bool(p.properties.Odm_dlkm_specific) {
+		baseInstallDirPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().OdmDlkmPath(), p.installBaseDir(ctx), p.SubDir())
+	} else if android.Bool(p.properties.Oem_specific) {
+		baseInstallDirPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().OemPath(), p.installBaseDir(ctx), p.SubDir())
+	}
 
 	filename := proptools.String(p.properties.Filename)
 	filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
diff --git a/fsgen/Android.bp b/fsgen/Android.bp
index e3cbdb3..690ad28 100644
--- a/fsgen/Android.bp
+++ b/fsgen/Android.bp
@@ -14,6 +14,8 @@
     ],
     srcs: [
         "filesystem_creator.go",
+        "fsgen_mutators.go",
+        "prebuilt_etc_modules_gen.go",
     ],
     testSrcs: [
         "filesystem_creator_test.go",
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index d8f679f..bdffabf 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -18,13 +18,10 @@
 	"crypto/sha256"
 	"fmt"
 	"path/filepath"
-	"slices"
 	"strconv"
 	"strings"
-	"sync"
 
 	"android/soong/android"
-	"android/soong/etc"
 	"android/soong/filesystem"
 	"android/soong/kernel"
 
@@ -44,597 +41,6 @@
 	ctx.PreDepsMutators(RegisterCollectFileSystemDepsMutators)
 }
 
-func RegisterCollectFileSystemDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("fs_collect_deps", collectDepsMutator).MutatesGlobalState()
-	ctx.BottomUp("fs_set_deps", setDepsMutator)
-}
-
-var fsGenStateOnceKey = android.NewOnceKey("FsGenState")
-var fsGenRemoveOverridesOnceKey = android.NewOnceKey("FsGenRemoveOverrides")
-
-// Map of partition module name to its partition that may be generated by Soong.
-// Note that it is not guaranteed that all modules returned by this function are successfully
-// created.
-func getAllSoongGeneratedPartitionNames(config android.Config, partitions []string) map[string]string {
-	ret := map[string]string{}
-	for _, partition := range partitions {
-		ret[generatedModuleNameForPartition(config, partition)] = partition
-	}
-	return ret
-}
-
-type depCandidateProps struct {
-	Namespace string
-	Multilib  string
-	Arch      []android.ArchType
-}
-
-// Map of module name to depCandidateProps
-type multilibDeps map[string]*depCandidateProps
-
-// Information necessary to generate the filesystem modules, including details about their
-// dependencies
-type FsGenState struct {
-	// List of modules in `PRODUCT_PACKAGES` and `PRODUCT_PACKAGES_DEBUG`
-	depCandidates []string
-	// Map of names of partition to the information of modules to be added as deps
-	fsDeps map[string]*multilibDeps
-	// List of name of partitions to be generated by the filesystem_creator module
-	soongGeneratedPartitions []string
-	// Mutex to protect the fsDeps
-	fsDepsMutex sync.Mutex
-	// Map of _all_ soong module names to their corresponding installation properties
-	moduleToInstallationProps map[string]installationProperties
-}
-
-type installationProperties struct {
-	Required  []string
-	Overrides []string
-}
-
-func defaultDepCandidateProps(config android.Config) *depCandidateProps {
-	return &depCandidateProps{
-		Namespace: ".",
-		Arch:      []android.ArchType{config.BuildArch},
-	}
-}
-
-type srcBaseFileInstallBaseFileTuple struct {
-	srcBaseFile     string
-	installBaseFile string
-}
-
-// prebuilt src files grouped by the install partitions.
-// Each groups are a mapping of the relative install path to the name of the files
-type prebuiltSrcGroupByInstallPartition struct {
-	system     map[string][]srcBaseFileInstallBaseFileTuple
-	system_ext map[string][]srcBaseFileInstallBaseFileTuple
-	product    map[string][]srcBaseFileInstallBaseFileTuple
-	vendor     map[string][]srcBaseFileInstallBaseFileTuple
-}
-
-func newPrebuiltSrcGroupByInstallPartition() *prebuiltSrcGroupByInstallPartition {
-	return &prebuiltSrcGroupByInstallPartition{
-		system:     map[string][]srcBaseFileInstallBaseFileTuple{},
-		system_ext: map[string][]srcBaseFileInstallBaseFileTuple{},
-		product:    map[string][]srcBaseFileInstallBaseFileTuple{},
-		vendor:     map[string][]srcBaseFileInstallBaseFileTuple{},
-	}
-}
-
-func isSubdirectory(parent, child string) bool {
-	rel, err := filepath.Rel(parent, child)
-	if err != nil {
-		return false
-	}
-	return !strings.HasPrefix(rel, "..")
-}
-
-func appendIfCorrectInstallPartition(partitionToInstallPathList []partitionToInstallPath, destPath, srcPath string, srcGroup *prebuiltSrcGroupByInstallPartition) {
-	for _, part := range partitionToInstallPathList {
-		partition := part.name
-		installPath := part.installPath
-
-		if isSubdirectory(installPath, destPath) {
-			relativeInstallPath, _ := filepath.Rel(installPath, destPath)
-			relativeInstallDir := filepath.Dir(relativeInstallPath)
-			var srcMap map[string][]srcBaseFileInstallBaseFileTuple
-			switch partition {
-			case "system":
-				srcMap = srcGroup.system
-			case "system_ext":
-				srcMap = srcGroup.system_ext
-			case "product":
-				srcMap = srcGroup.product
-			case "vendor":
-				srcMap = srcGroup.vendor
-			}
-			if srcMap != nil {
-				srcMap[relativeInstallDir] = append(srcMap[relativeInstallDir], srcBaseFileInstallBaseFileTuple{
-					srcBaseFile:     filepath.Base(srcPath),
-					installBaseFile: filepath.Base(destPath),
-				})
-			}
-			return
-		}
-	}
-}
-
-func uniqueExistingProductCopyFileMap(ctx android.LoadHookContext) map[string]string {
-	seen := make(map[string]bool)
-	filtered := make(map[string]string)
-
-	for src, dest := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles {
-		if _, ok := seen[dest]; !ok {
-			if optionalPath := android.ExistentPathForSource(ctx, src); optionalPath.Valid() {
-				seen[dest] = true
-				filtered[src] = dest
-			}
-		}
-	}
-
-	return filtered
-}
-
-type partitionToInstallPath struct {
-	name        string
-	installPath string
-}
-
-func processProductCopyFiles(ctx android.LoadHookContext) map[string]*prebuiltSrcGroupByInstallPartition {
-	// Filter out duplicate dest entries and non existing src entries
-	productCopyFileMap := uniqueExistingProductCopyFileMap(ctx)
-
-	// System is intentionally added at the last to consider the scenarios where
-	// non-system partitions are installed as part of the system partition
-	partitionToInstallPathList := []partitionToInstallPath{
-		{name: "vendor", installPath: ctx.DeviceConfig().VendorPath()},
-		{name: "product", installPath: ctx.DeviceConfig().ProductPath()},
-		{name: "system_ext", installPath: ctx.DeviceConfig().SystemExtPath()},
-		{name: "system", installPath: "system"},
-	}
-
-	groupedSources := map[string]*prebuiltSrcGroupByInstallPartition{}
-	for _, src := range android.SortedKeys(productCopyFileMap) {
-		dest := productCopyFileMap[src]
-		srcFileDir := filepath.Dir(src)
-		if _, ok := groupedSources[srcFileDir]; !ok {
-			groupedSources[srcFileDir] = newPrebuiltSrcGroupByInstallPartition()
-		}
-		appendIfCorrectInstallPartition(partitionToInstallPathList, dest, filepath.Base(src), groupedSources[srcFileDir])
-	}
-
-	return groupedSources
-}
-
-type prebuiltModuleProperties struct {
-	Name *string
-
-	Soc_specific        *bool
-	Product_specific    *bool
-	System_ext_specific *bool
-
-	Srcs []string
-	Dsts []string
-
-	No_full_install *bool
-
-	NamespaceExportedToMake bool
-
-	Visibility []string
-}
-
-// Split relative_install_path to a separate struct, because it is not supported for every
-// modules listed in [etcInstallPathToFactoryMap]
-type prebuiltSubdirProperties struct {
-	// If the base file name of the src and dst all match, dsts property does not need to be
-	// set, and only relative_install_path can be set.
-	Relative_install_path *string
-}
-
-var (
-	etcInstallPathToFactoryList = map[string]android.ModuleFactory{
-		"":                etc.PrebuiltRootFactory,
-		"avb":             etc.PrebuiltAvbFactory,
-		"bin":             etc.PrebuiltBinaryFactory,
-		"bt_firmware":     etc.PrebuiltBtFirmwareFactory,
-		"cacerts":         etc.PrebuiltEtcCaCertsFactory,
-		"dsp":             etc.PrebuiltDSPFactory,
-		"etc":             etc.PrebuiltEtcFactory,
-		"etc/dsp":         etc.PrebuiltDSPFactory,
-		"etc/firmware":    etc.PrebuiltFirmwareFactory,
-		"firmware":        etc.PrebuiltFirmwareFactory,
-		"fonts":           etc.PrebuiltFontFactory,
-		"framework":       etc.PrebuiltFrameworkFactory,
-		"lib":             etc.PrebuiltRenderScriptBitcodeFactory,
-		"lib64":           etc.PrebuiltRenderScriptBitcodeFactory,
-		"lib/rfsa":        etc.PrebuiltRFSAFactory,
-		"media":           etc.PrebuiltMediaFactory,
-		"odm":             etc.PrebuiltOdmFactory,
-		"overlay":         etc.PrebuiltOverlayFactory,
-		"priv-app":        etc.PrebuiltPrivAppFactory,
-		"res":             etc.PrebuiltResFactory,
-		"rfs":             etc.PrebuiltRfsFactory,
-		"tts":             etc.PrebuiltVoicepackFactory,
-		"usr/share":       etc.PrebuiltUserShareFactory,
-		"usr/hyphen-data": etc.PrebuiltUserHyphenDataFactory,
-		"usr/keylayout":   etc.PrebuiltUserKeyLayoutFactory,
-		"usr/keychars":    etc.PrebuiltUserKeyCharsFactory,
-		"usr/srec":        etc.PrebuiltUserSrecFactory,
-		"usr/idc":         etc.PrebuiltUserIdcFactory,
-		"vendor_dlkm":     etc.PrebuiltVendorDlkmFactory,
-		"wallpaper":       etc.PrebuiltWallpaperFactory,
-		"wlc_upt":         etc.PrebuiltWlcUptFactory,
-	}
-)
-
-func createPrebuiltEtcModule(ctx android.LoadHookContext, partition, srcDir, destDir string, destFiles []srcBaseFileInstallBaseFileTuple) string {
-	moduleProps := &prebuiltModuleProperties{}
-	propsList := []interface{}{moduleProps}
-
-	// generated module name follows the pattern:
-	// <install partition>-<src file path>-<relative install path from partition root>-<install file extension>
-	// Note that all path separators are replaced with "_" in the name
-	moduleName := partition
-	if !android.InList(srcDir, []string{"", "."}) {
-		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(srcDir, string(filepath.Separator), "_"))
-	}
-	if !android.InList(destDir, []string{"", "."}) {
-		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(destDir, string(filepath.Separator), "_"))
-	}
-	if len(destFiles) > 0 {
-		if ext := filepath.Ext(destFiles[0].srcBaseFile); ext != "" {
-			moduleName += fmt.Sprintf("-%s", strings.TrimPrefix(ext, "."))
-		}
-	}
-	moduleProps.Name = proptools.StringPtr(moduleName)
-
-	allCopyFileNamesUnchanged := true
-	var srcBaseFiles, installBaseFiles []string
-	for _, tuple := range destFiles {
-		if tuple.srcBaseFile != tuple.installBaseFile {
-			allCopyFileNamesUnchanged = false
-		}
-		srcBaseFiles = append(srcBaseFiles, tuple.srcBaseFile)
-		installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
-	}
-
-	// Find out the most appropriate module type to generate
-	var etcInstallPathKey string
-	for _, etcInstallPath := range android.SortedKeys(etcInstallPathToFactoryList) {
-		// Do not break when found but iterate until the end to find a module with more
-		// specific install path
-		if strings.HasPrefix(destDir, etcInstallPath) {
-			etcInstallPathKey = etcInstallPath
-		}
-	}
-	destDir, _ = filepath.Rel(etcInstallPathKey, destDir)
-
-	// Set partition specific properties
-	switch partition {
-	case "system_ext":
-		moduleProps.System_ext_specific = proptools.BoolPtr(true)
-	case "product":
-		moduleProps.Product_specific = proptools.BoolPtr(true)
-	case "vendor":
-		moduleProps.Soc_specific = proptools.BoolPtr(true)
-	}
-
-	// Set appropriate srcs, dsts, and releative_install_path based on
-	// the source and install file names
-	if allCopyFileNamesUnchanged {
-		moduleProps.Srcs = srcBaseFiles
-
-		// Specify relative_install_path if it is not installed in the root directory of the
-		// partition
-		if !android.InList(destDir, []string{"", "."}) {
-			propsList = append(propsList, &prebuiltSubdirProperties{
-				Relative_install_path: proptools.StringPtr(destDir),
-			})
-		}
-	} else {
-		moduleProps.Srcs = srcBaseFiles
-		dsts := []string{}
-		for _, installBaseFile := range installBaseFiles {
-			dsts = append(dsts, filepath.Join(destDir, installBaseFile))
-		}
-		moduleProps.Dsts = dsts
-	}
-
-	moduleProps.No_full_install = proptools.BoolPtr(true)
-	moduleProps.NamespaceExportedToMake = true
-	moduleProps.Visibility = []string{"//visibility:public"}
-
-	ctx.CreateModuleInDirectory(etcInstallPathToFactoryList[etcInstallPathKey], srcDir, propsList...)
-
-	return moduleName
-}
-
-func createPrebuiltEtcModulesForPartition(ctx android.LoadHookContext, partition, srcDir string, destDirFilesMap map[string][]srcBaseFileInstallBaseFileTuple) (ret []string) {
-	for _, destDir := range android.SortedKeys(destDirFilesMap) {
-		ret = append(ret, createPrebuiltEtcModule(ctx, partition, srcDir, destDir, destDirFilesMap[destDir]))
-	}
-	return ret
-}
-
-// Creates prebuilt_* modules based on the install paths and returns the list of generated
-// module names
-func createPrebuiltEtcModules(ctx android.LoadHookContext) (ret []string) {
-	groupedSources := processProductCopyFiles(ctx)
-	for _, srcDir := range android.SortedKeys(groupedSources) {
-		groupedSource := groupedSources[srcDir]
-		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system", srcDir, groupedSource.system)...)
-		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system_ext", srcDir, groupedSource.system_ext)...)
-		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "product", srcDir, groupedSource.product)...)
-		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "vendor", srcDir, groupedSource.vendor)...)
-	}
-
-	return ret
-}
-
-func generatedPartitions(ctx android.LoadHookContext) []string {
-	generatedPartitions := []string{"system"}
-	if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
-		generatedPartitions = append(generatedPartitions, "system_ext")
-	}
-	if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
-		generatedPartitions = append(generatedPartitions, "vendor")
-	}
-	if ctx.DeviceConfig().BuildingProductImage() && ctx.DeviceConfig().ProductPath() == "product" {
-		generatedPartitions = append(generatedPartitions, "product")
-	}
-	if ctx.DeviceConfig().BuildingOdmImage() && ctx.DeviceConfig().OdmPath() == "odm" {
-		generatedPartitions = append(generatedPartitions, "odm")
-	}
-	if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingSystemDlkmImage {
-		generatedPartitions = append(generatedPartitions, "system_dlkm")
-	}
-	return generatedPartitions
-}
-
-func createFsGenState(ctx android.LoadHookContext, generatedPrebuiltEtcModuleNames []string) *FsGenState {
-	return ctx.Config().Once(fsGenStateOnceKey, func() interface{} {
-		partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
-		candidates := android.FirstUniqueStrings(android.Concat(partitionVars.ProductPackages, partitionVars.ProductPackagesDebug))
-		candidates = android.Concat(candidates, generatedPrebuiltEtcModuleNames)
-
-		return &FsGenState{
-			depCandidates: candidates,
-			fsDeps: map[string]*multilibDeps{
-				// These additional deps are added according to the cuttlefish system image bp.
-				"system": {
-					"com.android.apex.cts.shim.v1_prebuilt":     defaultDepCandidateProps(ctx.Config()),
-					"dex_bootjars":                              defaultDepCandidateProps(ctx.Config()),
-					"framework_compatibility_matrix.device.xml": defaultDepCandidateProps(ctx.Config()),
-					"init.environ.rc-soong":                     defaultDepCandidateProps(ctx.Config()),
-					"libclang_rt.asan":                          defaultDepCandidateProps(ctx.Config()),
-					"libcompiler_rt":                            defaultDepCandidateProps(ctx.Config()),
-					"libdmabufheap":                             defaultDepCandidateProps(ctx.Config()),
-					"libgsi":                                    defaultDepCandidateProps(ctx.Config()),
-					"llndk.libraries.txt":                       defaultDepCandidateProps(ctx.Config()),
-					"logpersist.start":                          defaultDepCandidateProps(ctx.Config()),
-					"update_engine_sideload":                    defaultDepCandidateProps(ctx.Config()),
-				},
-				"vendor": {
-					"fs_config_files_vendor":                               defaultDepCandidateProps(ctx.Config()),
-					"fs_config_dirs_vendor":                                defaultDepCandidateProps(ctx.Config()),
-					generatedModuleName(ctx.Config(), "vendor-build.prop"): defaultDepCandidateProps(ctx.Config()),
-				},
-				"odm": {
-					// fs_config_* files are automatically installed for all products with odm partitions.
-					// https://cs.android.com/android/_/android/platform/build/+/e4849e87ab660b59a6501b3928693db065ee873b:tools/fs_config/Android.mk;l=34;drc=8d6481b92c4b4e9b9f31a61545b6862090fcc14b;bpv=1;bpt=0
-					"fs_config_files_odm": defaultDepCandidateProps(ctx.Config()),
-					"fs_config_dirs_odm":  defaultDepCandidateProps(ctx.Config()),
-				},
-				"product": {},
-				"system_ext": {
-					// VNDK apexes are automatically included.
-					// This hardcoded list will need to be updated if `PRODUCT_EXTRA_VNDK_VERSIONS` is updated.
-					// https://cs.android.com/android/_/android/platform/build/+/adba533072b00c53ac0f198c550a3cbd7a00e4cd:core/main.mk;l=984;bpv=1;bpt=0;drc=174db7b179592cf07cbfd2adb0119486fda911e7
-					"com.android.vndk.v30": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v31": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v32": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v33": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v34": defaultDepCandidateProps(ctx.Config()),
-				},
-				"system_dlkm": {},
-			},
-			soongGeneratedPartitions:  generatedPartitions(ctx),
-			fsDepsMutex:               sync.Mutex{},
-			moduleToInstallationProps: map[string]installationProperties{},
-		}
-	}).(*FsGenState)
-}
-
-func checkDepModuleInMultipleNamespaces(mctx android.BottomUpMutatorContext, foundDeps multilibDeps, module string, partitionName string) {
-	otherNamespace := mctx.Namespace().Path
-	if val, found := foundDeps[module]; found && otherNamespace != "." && !android.InList(val.Namespace, []string{".", otherNamespace}) {
-		mctx.ModuleErrorf("found in multiple namespaces(%s and %s) when including in %s partition", val.Namespace, otherNamespace, partitionName)
-	}
-}
-
-func appendDepIfAppropriate(mctx android.BottomUpMutatorContext, deps *multilibDeps, installPartition string) {
-	checkDepModuleInMultipleNamespaces(mctx, *deps, mctx.Module().Name(), installPartition)
-	if _, ok := (*deps)[mctx.Module().Name()]; ok {
-		// Prefer the namespace-specific module over the platform module
-		if mctx.Namespace().Path != "." {
-			(*deps)[mctx.Module().Name()].Namespace = mctx.Namespace().Path
-		}
-		(*deps)[mctx.Module().Name()].Arch = append((*deps)[mctx.Module().Name()].Arch, mctx.Module().Target().Arch.ArchType)
-	} else {
-		multilib, _ := mctx.Module().DecodeMultilib(mctx)
-		(*deps)[mctx.Module().Name()] = &depCandidateProps{
-			Namespace: mctx.Namespace().Path,
-			Multilib:  multilib,
-			Arch:      []android.ArchType{mctx.Module().Target().Arch.ArchType},
-		}
-	}
-}
-
-func collectDepsMutator(mctx android.BottomUpMutatorContext) {
-	fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
-
-	m := mctx.Module()
-	if m.Target().Os.Class == android.Device && slices.Contains(fsGenState.depCandidates, m.Name()) {
-		installPartition := m.PartitionTag(mctx.DeviceConfig())
-		fsGenState.fsDepsMutex.Lock()
-		// Only add the module as dependency when:
-		// - its enabled
-		// - its namespace is included in PRODUCT_SOONG_NAMESPACES
-		if m.Enabled(mctx) && m.ExportedToMake() {
-			appendDepIfAppropriate(mctx, fsGenState.fsDeps[installPartition], installPartition)
-		}
-		fsGenState.fsDepsMutex.Unlock()
-	}
-	// store the map of module to (required,overrides) even if the module is not in PRODUCT_PACKAGES.
-	// the module might be installed transitively.
-	if m.Target().Os.Class == android.Device && m.Enabled(mctx) && m.ExportedToMake() {
-		fsGenState.fsDepsMutex.Lock()
-		fsGenState.moduleToInstallationProps[m.Name()] = installationProperties{
-			Required:  m.RequiredModuleNames(mctx),
-			Overrides: m.Overrides(),
-		}
-		fsGenState.fsDepsMutex.Unlock()
-	}
-}
-
-type depsStruct struct {
-	Deps []string
-}
-
-type multilibDepsStruct struct {
-	Common   depsStruct
-	Lib32    depsStruct
-	Lib64    depsStruct
-	Both     depsStruct
-	Prefer32 depsStruct
-}
-
-type packagingPropsStruct struct {
-	High_priority_deps []string
-	Deps               []string
-	Multilib           multilibDepsStruct
-}
-
-func fullyQualifiedModuleName(moduleName, namespace string) string {
-	if namespace == "." {
-		return moduleName
-	}
-	return fmt.Sprintf("//%s:%s", namespace, moduleName)
-}
-
-func getBitness(archTypes []android.ArchType) (ret []string) {
-	for _, archType := range archTypes {
-		if archType.Multilib == "" {
-			ret = append(ret, android.COMMON_VARIANT)
-		} else {
-			ret = append(ret, archType.Bitness())
-		}
-	}
-	return ret
-}
-
-func setDepsMutator(mctx android.BottomUpMutatorContext) {
-	removeOverriddenDeps(mctx)
-	fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
-	fsDeps := fsGenState.fsDeps
-	soongGeneratedPartitionMap := getAllSoongGeneratedPartitionNames(mctx.Config(), fsGenState.soongGeneratedPartitions)
-	m := mctx.Module()
-	if partition, ok := soongGeneratedPartitionMap[m.Name()]; ok {
-		depsStruct := generateDepStruct(*fsDeps[partition])
-		if err := proptools.AppendMatchingProperties(m.GetProperties(), depsStruct, nil); err != nil {
-			mctx.ModuleErrorf(err.Error())
-		}
-	}
-}
-
-// removeOverriddenDeps collects PRODUCT_PACKAGES and (transitive) required deps.
-// it then removes any modules which appear in `overrides` of the above list.
-func removeOverriddenDeps(mctx android.BottomUpMutatorContext) {
-	mctx.Config().Once(fsGenRemoveOverridesOnceKey, func() interface{} {
-		fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
-		fsDeps := fsGenState.fsDeps
-		overridden := map[string]bool{}
-		allDeps := []string{}
-
-		// Step 1: Initialization: Append PRODUCT_PACKAGES to the queue
-		for _, fsDep := range fsDeps {
-			for depName, _ := range *fsDep {
-				allDeps = append(allDeps, depName)
-			}
-		}
-
-		// Step 2: Process the queue, and add required modules to the queue.
-		i := 0
-		for {
-			if i == len(allDeps) {
-				break
-			}
-			depName := allDeps[i]
-			for _, overrides := range fsGenState.moduleToInstallationProps[depName].Overrides {
-				overridden[overrides] = true
-			}
-			// add required dep to the queue.
-			allDeps = append(allDeps, fsGenState.moduleToInstallationProps[depName].Required...)
-			i += 1
-		}
-
-		// Step 3: Delete all the overridden modules.
-		for overridden, _ := range overridden {
-			for partition, _ := range fsDeps {
-				delete(*fsDeps[partition], overridden)
-			}
-		}
-		return nil
-	})
-}
-
-var HighPriorityDeps = []string{}
-
-func generateDepStruct(deps map[string]*depCandidateProps) *packagingPropsStruct {
-	depsStruct := packagingPropsStruct{}
-	for depName, depProps := range deps {
-		bitness := getBitness(depProps.Arch)
-		fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
-		if android.InList(depName, HighPriorityDeps) {
-			depsStruct.High_priority_deps = append(depsStruct.High_priority_deps, fullyQualifiedDepName)
-		} else if android.InList("32", bitness) && android.InList("64", bitness) {
-			// If both 32 and 64 bit variants are enabled for this module
-			switch depProps.Multilib {
-			case string(android.MultilibBoth):
-				depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
-			case string(android.MultilibCommon), string(android.MultilibFirst):
-				depsStruct.Deps = append(depsStruct.Deps, fullyQualifiedDepName)
-			case "32":
-				depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
-			case "64", "darwin_universal":
-				depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
-			case "prefer32", "first_prefer32":
-				depsStruct.Multilib.Prefer32.Deps = append(depsStruct.Multilib.Prefer32.Deps, fullyQualifiedDepName)
-			default:
-				depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
-			}
-		} else if android.InList("64", bitness) {
-			// If only 64 bit variant is enabled
-			depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
-		} else if android.InList("32", bitness) {
-			// If only 32 bit variant is enabled
-			depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
-		} else {
-			// If only common variant is enabled
-			depsStruct.Multilib.Common.Deps = append(depsStruct.Multilib.Common.Deps, fullyQualifiedDepName)
-		}
-	}
-	depsStruct.Deps = android.SortedUniqueStrings(depsStruct.Deps)
-	depsStruct.Multilib.Lib32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib32.Deps)
-	depsStruct.Multilib.Lib64.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib64.Deps)
-	depsStruct.Multilib.Prefer32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Prefer32.Deps)
-	depsStruct.Multilib.Both.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Both.Deps)
-	depsStruct.Multilib.Common.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Common.Deps)
-
-	return &depsStruct
-}
-
 type filesystemCreatorProps struct {
 	Generated_partition_types   []string `blueprint:"mutated"`
 	Unsupported_partition_types []string `blueprint:"mutated"`
@@ -799,23 +205,7 @@
 	}
 	module.HideFromMake()
 	if partitionType == "vendor" {
-		// Create a build prop for vendor
-		vendorBuildProps := &struct {
-			Name           *string
-			Vendor         *bool
-			Stem           *string
-			Product_config *string
-		}{
-			Name:           proptools.StringPtr(generatedModuleName(ctx.Config(), "vendor-build.prop")),
-			Vendor:         proptools.BoolPtr(true),
-			Stem:           proptools.StringPtr("build.prop"),
-			Product_config: proptools.StringPtr(":product_config"),
-		}
-		vendorBuildProp := ctx.CreateModule(
-			android.BuildPropFactory,
-			vendorBuildProps,
-		)
-		vendorBuildProp.HideFromMake()
+		f.createVendorBuildProp(ctx)
 	}
 	return true
 }
@@ -855,6 +245,50 @@
 	}
 }
 
+// Create a build_prop and android_info module. This will be used to create /vendor/build.prop
+func (f *filesystemCreator) createVendorBuildProp(ctx android.LoadHookContext) {
+	// Create a android_info for vendor
+	// The board info files might be in a directory outside the root soong namespace, so create
+	// the module in "."
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	androidInfoProps := &struct {
+		Name                  *string
+		Board_info_files      []string
+		Bootloader_board_name *string
+	}{
+		Name:             proptools.StringPtr(generatedModuleName(ctx.Config(), "android-info.prop")),
+		Board_info_files: partitionVars.BoardInfoFiles,
+	}
+	if len(androidInfoProps.Board_info_files) == 0 {
+		androidInfoProps.Bootloader_board_name = proptools.StringPtr(partitionVars.BootLoaderBoardName)
+	}
+	androidInfoProp := ctx.CreateModuleInDirectory(
+		android.AndroidInfoFactory,
+		".",
+		androidInfoProps,
+	)
+	androidInfoProp.HideFromMake()
+	// Create a build prop for vendor
+	vendorBuildProps := &struct {
+		Name           *string
+		Vendor         *bool
+		Stem           *string
+		Product_config *string
+		Android_info   *string
+	}{
+		Name:           proptools.StringPtr(generatedModuleName(ctx.Config(), "vendor-build.prop")),
+		Vendor:         proptools.BoolPtr(true),
+		Stem:           proptools.StringPtr("build.prop"),
+		Product_config: proptools.StringPtr(":product_config"),
+		Android_info:   proptools.StringPtr(":" + androidInfoProp.Name()),
+	}
+	vendorBuildProp := ctx.CreateModule(
+		android.BuildPropFactory,
+		vendorBuildProps,
+	)
+	vendorBuildProp.HideFromMake()
+}
+
 // createLinkerConfigSourceFilegroups creates filegroup modules to generate linker.config.pb for the following partitions
 // 1. vendor: Using PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS (space separated file list)
 // 1. product: Using PRODUCT_PRODUCT_LINKER_CONFIG_FRAGMENTS (space separated file list)
@@ -1048,9 +482,6 @@
 	if !fsTypeSupported {
 		return ""
 	}
-	if partitionType == "vendor" || partitionType == "odm" {
-		return "" // TODO: Handle struct props
-	}
 
 	baseProps := generateBaseProps(proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), partitionType)))
 	deps := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).fsDeps[partitionType]
@@ -1058,7 +489,8 @@
 
 	result, err := proptools.RepackProperties([]interface{}{baseProps, fsProps, depProps})
 	if err != nil {
-		ctx.ModuleErrorf(err.Error())
+		ctx.ModuleErrorf("%s", err.Error())
+		return ""
 	}
 
 	moduleType := "android_filesystem"
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
new file mode 100644
index 0000000..bf07ab3
--- /dev/null
+++ b/fsgen/fsgen_mutators.go
@@ -0,0 +1,343 @@
+// Copyright (C) 2024 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 fsgen
+
+import (
+	"android/soong/android"
+	"fmt"
+	"slices"
+	"sync"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func RegisterCollectFileSystemDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.BottomUp("fs_collect_deps", collectDepsMutator).MutatesGlobalState()
+	ctx.BottomUp("fs_set_deps", setDepsMutator)
+}
+
+var fsGenStateOnceKey = android.NewOnceKey("FsGenState")
+var fsGenRemoveOverridesOnceKey = android.NewOnceKey("FsGenRemoveOverrides")
+
+// Map of partition module name to its partition that may be generated by Soong.
+// Note that it is not guaranteed that all modules returned by this function are successfully
+// created.
+func getAllSoongGeneratedPartitionNames(config android.Config, partitions []string) map[string]string {
+	ret := map[string]string{}
+	for _, partition := range partitions {
+		ret[generatedModuleNameForPartition(config, partition)] = partition
+	}
+	return ret
+}
+
+type depCandidateProps struct {
+	Namespace string
+	Multilib  string
+	Arch      []android.ArchType
+}
+
+// Map of module name to depCandidateProps
+type multilibDeps map[string]*depCandidateProps
+
+// Information necessary to generate the filesystem modules, including details about their
+// dependencies
+type FsGenState struct {
+	// List of modules in `PRODUCT_PACKAGES` and `PRODUCT_PACKAGES_DEBUG`
+	depCandidates []string
+	// Map of names of partition to the information of modules to be added as deps
+	fsDeps map[string]*multilibDeps
+	// List of name of partitions to be generated by the filesystem_creator module
+	soongGeneratedPartitions []string
+	// Mutex to protect the fsDeps
+	fsDepsMutex sync.Mutex
+	// Map of _all_ soong module names to their corresponding installation properties
+	moduleToInstallationProps map[string]installationProperties
+}
+
+type installationProperties struct {
+	Required  []string
+	Overrides []string
+}
+
+func defaultDepCandidateProps(config android.Config) *depCandidateProps {
+	return &depCandidateProps{
+		Namespace: ".",
+		Arch:      []android.ArchType{config.BuildArch},
+	}
+}
+
+func generatedPartitions(ctx android.LoadHookContext) []string {
+	generatedPartitions := []string{"system"}
+	if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
+		generatedPartitions = append(generatedPartitions, "system_ext")
+	}
+	if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
+		generatedPartitions = append(generatedPartitions, "vendor")
+	}
+	if ctx.DeviceConfig().BuildingProductImage() && ctx.DeviceConfig().ProductPath() == "product" {
+		generatedPartitions = append(generatedPartitions, "product")
+	}
+	if ctx.DeviceConfig().BuildingOdmImage() && ctx.DeviceConfig().OdmPath() == "odm" {
+		generatedPartitions = append(generatedPartitions, "odm")
+	}
+	if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingSystemDlkmImage {
+		generatedPartitions = append(generatedPartitions, "system_dlkm")
+	}
+	return generatedPartitions
+}
+
+func createFsGenState(ctx android.LoadHookContext, generatedPrebuiltEtcModuleNames []string) *FsGenState {
+	return ctx.Config().Once(fsGenStateOnceKey, func() interface{} {
+		partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+		candidates := android.FirstUniqueStrings(android.Concat(partitionVars.ProductPackages, partitionVars.ProductPackagesDebug))
+		candidates = android.Concat(candidates, generatedPrebuiltEtcModuleNames)
+
+		return &FsGenState{
+			depCandidates: candidates,
+			fsDeps: map[string]*multilibDeps{
+				// These additional deps are added according to the cuttlefish system image bp.
+				"system": {
+					"com.android.apex.cts.shim.v1_prebuilt":     defaultDepCandidateProps(ctx.Config()),
+					"dex_bootjars":                              defaultDepCandidateProps(ctx.Config()),
+					"framework_compatibility_matrix.device.xml": defaultDepCandidateProps(ctx.Config()),
+					"init.environ.rc-soong":                     defaultDepCandidateProps(ctx.Config()),
+					"libclang_rt.asan":                          defaultDepCandidateProps(ctx.Config()),
+					"libcompiler_rt":                            defaultDepCandidateProps(ctx.Config()),
+					"libdmabufheap":                             defaultDepCandidateProps(ctx.Config()),
+					"libgsi":                                    defaultDepCandidateProps(ctx.Config()),
+					"llndk.libraries.txt":                       defaultDepCandidateProps(ctx.Config()),
+					"logpersist.start":                          defaultDepCandidateProps(ctx.Config()),
+					"update_engine_sideload":                    defaultDepCandidateProps(ctx.Config()),
+				},
+				"vendor": {
+					"fs_config_files_vendor":                               defaultDepCandidateProps(ctx.Config()),
+					"fs_config_dirs_vendor":                                defaultDepCandidateProps(ctx.Config()),
+					generatedModuleName(ctx.Config(), "vendor-build.prop"): defaultDepCandidateProps(ctx.Config()),
+				},
+				"odm": {
+					// fs_config_* files are automatically installed for all products with odm partitions.
+					// https://cs.android.com/android/_/android/platform/build/+/e4849e87ab660b59a6501b3928693db065ee873b:tools/fs_config/Android.mk;l=34;drc=8d6481b92c4b4e9b9f31a61545b6862090fcc14b;bpv=1;bpt=0
+					"fs_config_files_odm": defaultDepCandidateProps(ctx.Config()),
+					"fs_config_dirs_odm":  defaultDepCandidateProps(ctx.Config()),
+				},
+				"product": {},
+				"system_ext": {
+					// VNDK apexes are automatically included.
+					// This hardcoded list will need to be updated if `PRODUCT_EXTRA_VNDK_VERSIONS` is updated.
+					// https://cs.android.com/android/_/android/platform/build/+/adba533072b00c53ac0f198c550a3cbd7a00e4cd:core/main.mk;l=984;bpv=1;bpt=0;drc=174db7b179592cf07cbfd2adb0119486fda911e7
+					"com.android.vndk.v30": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v31": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v32": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v33": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v34": defaultDepCandidateProps(ctx.Config()),
+				},
+				"system_dlkm": {},
+			},
+			soongGeneratedPartitions:  generatedPartitions(ctx),
+			fsDepsMutex:               sync.Mutex{},
+			moduleToInstallationProps: map[string]installationProperties{},
+		}
+	}).(*FsGenState)
+}
+
+func checkDepModuleInMultipleNamespaces(mctx android.BottomUpMutatorContext, foundDeps multilibDeps, module string, partitionName string) {
+	otherNamespace := mctx.Namespace().Path
+	if val, found := foundDeps[module]; found && otherNamespace != "." && !android.InList(val.Namespace, []string{".", otherNamespace}) {
+		mctx.ModuleErrorf("found in multiple namespaces(%s and %s) when including in %s partition", val.Namespace, otherNamespace, partitionName)
+	}
+}
+
+func appendDepIfAppropriate(mctx android.BottomUpMutatorContext, deps *multilibDeps, installPartition string) {
+	moduleName := mctx.ModuleName()
+	checkDepModuleInMultipleNamespaces(mctx, *deps, moduleName, installPartition)
+	if _, ok := (*deps)[moduleName]; ok {
+		// Prefer the namespace-specific module over the platform module
+		if mctx.Namespace().Path != "." {
+			(*deps)[moduleName].Namespace = mctx.Namespace().Path
+		}
+		(*deps)[moduleName].Arch = append((*deps)[moduleName].Arch, mctx.Module().Target().Arch.ArchType)
+	} else {
+		multilib, _ := mctx.Module().DecodeMultilib(mctx)
+		(*deps)[moduleName] = &depCandidateProps{
+			Namespace: mctx.Namespace().Path,
+			Multilib:  multilib,
+			Arch:      []android.ArchType{mctx.Module().Target().Arch.ArchType},
+		}
+	}
+}
+
+func collectDepsMutator(mctx android.BottomUpMutatorContext) {
+	fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+
+	m := mctx.Module()
+	if m.Target().Os.Class == android.Device && slices.Contains(fsGenState.depCandidates, mctx.ModuleName()) {
+		installPartition := m.PartitionTag(mctx.DeviceConfig())
+		fsGenState.fsDepsMutex.Lock()
+		// Only add the module as dependency when:
+		// - its enabled
+		// - its namespace is included in PRODUCT_SOONG_NAMESPACES
+		if m.Enabled(mctx) && m.ExportedToMake() {
+			appendDepIfAppropriate(mctx, fsGenState.fsDeps[installPartition], installPartition)
+		}
+		fsGenState.fsDepsMutex.Unlock()
+	}
+	// store the map of module to (required,overrides) even if the module is not in PRODUCT_PACKAGES.
+	// the module might be installed transitively.
+	if m.Target().Os.Class == android.Device && m.Enabled(mctx) && m.ExportedToMake() {
+		fsGenState.fsDepsMutex.Lock()
+		fsGenState.moduleToInstallationProps[m.Name()] = installationProperties{
+			Required:  m.RequiredModuleNames(mctx),
+			Overrides: m.Overrides(),
+		}
+		fsGenState.fsDepsMutex.Unlock()
+	}
+}
+
+type depsStruct struct {
+	Deps []string
+}
+
+type multilibDepsStruct struct {
+	Common   depsStruct
+	Lib32    depsStruct
+	Lib64    depsStruct
+	Both     depsStruct
+	Prefer32 depsStruct
+}
+
+type packagingPropsStruct struct {
+	High_priority_deps []string
+	Deps               []string
+	Multilib           multilibDepsStruct
+}
+
+func fullyQualifiedModuleName(moduleName, namespace string) string {
+	if namespace == "." {
+		return moduleName
+	}
+	return fmt.Sprintf("//%s:%s", namespace, moduleName)
+}
+
+func getBitness(archTypes []android.ArchType) (ret []string) {
+	for _, archType := range archTypes {
+		if archType.Multilib == "" {
+			ret = append(ret, android.COMMON_VARIANT)
+		} else {
+			ret = append(ret, archType.Bitness())
+		}
+	}
+	return ret
+}
+
+func setDepsMutator(mctx android.BottomUpMutatorContext) {
+	removeOverriddenDeps(mctx)
+	fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+	fsDeps := fsGenState.fsDeps
+	soongGeneratedPartitionMap := getAllSoongGeneratedPartitionNames(mctx.Config(), fsGenState.soongGeneratedPartitions)
+	m := mctx.Module()
+	if partition, ok := soongGeneratedPartitionMap[m.Name()]; ok {
+		depsStruct := generateDepStruct(*fsDeps[partition])
+		if err := proptools.AppendMatchingProperties(m.GetProperties(), depsStruct, nil); err != nil {
+			mctx.ModuleErrorf(err.Error())
+		}
+	}
+}
+
+// removeOverriddenDeps collects PRODUCT_PACKAGES and (transitive) required deps.
+// it then removes any modules which appear in `overrides` of the above list.
+func removeOverriddenDeps(mctx android.BottomUpMutatorContext) {
+	mctx.Config().Once(fsGenRemoveOverridesOnceKey, func() interface{} {
+		fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+		fsDeps := fsGenState.fsDeps
+		overridden := map[string]bool{}
+		allDeps := []string{}
+
+		// Step 1: Initialization: Append PRODUCT_PACKAGES to the queue
+		for _, fsDep := range fsDeps {
+			for depName, _ := range *fsDep {
+				allDeps = append(allDeps, depName)
+			}
+		}
+
+		// Step 2: Process the queue, and add required modules to the queue.
+		i := 0
+		for {
+			if i == len(allDeps) {
+				break
+			}
+			depName := allDeps[i]
+			for _, overrides := range fsGenState.moduleToInstallationProps[depName].Overrides {
+				overridden[overrides] = true
+			}
+			// add required dep to the queue.
+			allDeps = append(allDeps, fsGenState.moduleToInstallationProps[depName].Required...)
+			i += 1
+		}
+
+		// Step 3: Delete all the overridden modules.
+		for overridden, _ := range overridden {
+			for partition, _ := range fsDeps {
+				delete(*fsDeps[partition], overridden)
+			}
+		}
+		return nil
+	})
+}
+
+var HighPriorityDeps = []string{}
+
+func generateDepStruct(deps map[string]*depCandidateProps) *packagingPropsStruct {
+	depsStruct := packagingPropsStruct{}
+	for depName, depProps := range deps {
+		bitness := getBitness(depProps.Arch)
+		fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
+		if android.InList(depName, HighPriorityDeps) {
+			depsStruct.High_priority_deps = append(depsStruct.High_priority_deps, fullyQualifiedDepName)
+		} else if android.InList("32", bitness) && android.InList("64", bitness) {
+			// If both 32 and 64 bit variants are enabled for this module
+			switch depProps.Multilib {
+			case string(android.MultilibBoth):
+				depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
+			case string(android.MultilibCommon), string(android.MultilibFirst):
+				depsStruct.Deps = append(depsStruct.Deps, fullyQualifiedDepName)
+			case "32":
+				depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
+			case "64", "darwin_universal":
+				depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
+			case "prefer32", "first_prefer32":
+				depsStruct.Multilib.Prefer32.Deps = append(depsStruct.Multilib.Prefer32.Deps, fullyQualifiedDepName)
+			default:
+				depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
+			}
+		} else if android.InList("64", bitness) {
+			// If only 64 bit variant is enabled
+			depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
+		} else if android.InList("32", bitness) {
+			// If only 32 bit variant is enabled
+			depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
+		} else {
+			// If only common variant is enabled
+			depsStruct.Multilib.Common.Deps = append(depsStruct.Multilib.Common.Deps, fullyQualifiedDepName)
+		}
+	}
+	depsStruct.Deps = android.SortedUniqueStrings(depsStruct.Deps)
+	depsStruct.Multilib.Lib32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib32.Deps)
+	depsStruct.Multilib.Lib64.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib64.Deps)
+	depsStruct.Multilib.Prefer32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Prefer32.Deps)
+	depsStruct.Multilib.Both.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Both.Deps)
+	depsStruct.Multilib.Common.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Common.Deps)
+
+	return &depsStruct
+}
diff --git a/fsgen/prebuilt_etc_modules_gen.go b/fsgen/prebuilt_etc_modules_gen.go
new file mode 100644
index 0000000..1df7ec8
--- /dev/null
+++ b/fsgen/prebuilt_etc_modules_gen.go
@@ -0,0 +1,298 @@
+// Copyright (C) 2024 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 fsgen
+
+import (
+	"android/soong/android"
+	"android/soong/etc"
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+type srcBaseFileInstallBaseFileTuple struct {
+	srcBaseFile     string
+	installBaseFile string
+}
+
+// prebuilt src files grouped by the install partitions.
+// Each groups are a mapping of the relative install path to the name of the files
+type prebuiltSrcGroupByInstallPartition struct {
+	system     map[string][]srcBaseFileInstallBaseFileTuple
+	system_ext map[string][]srcBaseFileInstallBaseFileTuple
+	product    map[string][]srcBaseFileInstallBaseFileTuple
+	vendor     map[string][]srcBaseFileInstallBaseFileTuple
+}
+
+func newPrebuiltSrcGroupByInstallPartition() *prebuiltSrcGroupByInstallPartition {
+	return &prebuiltSrcGroupByInstallPartition{
+		system:     map[string][]srcBaseFileInstallBaseFileTuple{},
+		system_ext: map[string][]srcBaseFileInstallBaseFileTuple{},
+		product:    map[string][]srcBaseFileInstallBaseFileTuple{},
+		vendor:     map[string][]srcBaseFileInstallBaseFileTuple{},
+	}
+}
+
+func isSubdirectory(parent, child string) bool {
+	rel, err := filepath.Rel(parent, child)
+	if err != nil {
+		return false
+	}
+	return !strings.HasPrefix(rel, "..")
+}
+
+func appendIfCorrectInstallPartition(partitionToInstallPathList []partitionToInstallPath, destPath, srcPath string, srcGroup *prebuiltSrcGroupByInstallPartition) {
+	for _, part := range partitionToInstallPathList {
+		partition := part.name
+		installPath := part.installPath
+
+		if isSubdirectory(installPath, destPath) {
+			relativeInstallPath, _ := filepath.Rel(installPath, destPath)
+			relativeInstallDir := filepath.Dir(relativeInstallPath)
+			var srcMap map[string][]srcBaseFileInstallBaseFileTuple
+			switch partition {
+			case "system":
+				srcMap = srcGroup.system
+			case "system_ext":
+				srcMap = srcGroup.system_ext
+			case "product":
+				srcMap = srcGroup.product
+			case "vendor":
+				srcMap = srcGroup.vendor
+			}
+			if srcMap != nil {
+				srcMap[relativeInstallDir] = append(srcMap[relativeInstallDir], srcBaseFileInstallBaseFileTuple{
+					srcBaseFile:     filepath.Base(srcPath),
+					installBaseFile: filepath.Base(destPath),
+				})
+			}
+			return
+		}
+	}
+}
+
+func uniqueExistingProductCopyFileMap(ctx android.LoadHookContext) map[string]string {
+	seen := make(map[string]bool)
+	filtered := make(map[string]string)
+
+	for src, dest := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles {
+		if _, ok := seen[dest]; !ok {
+			if optionalPath := android.ExistentPathForSource(ctx, src); optionalPath.Valid() {
+				seen[dest] = true
+				filtered[src] = dest
+			}
+		}
+	}
+
+	return filtered
+}
+
+type partitionToInstallPath struct {
+	name        string
+	installPath string
+}
+
+func processProductCopyFiles(ctx android.LoadHookContext) map[string]*prebuiltSrcGroupByInstallPartition {
+	// Filter out duplicate dest entries and non existing src entries
+	productCopyFileMap := uniqueExistingProductCopyFileMap(ctx)
+
+	// System is intentionally added at the last to consider the scenarios where
+	// non-system partitions are installed as part of the system partition
+	partitionToInstallPathList := []partitionToInstallPath{
+		{name: "vendor", installPath: ctx.DeviceConfig().VendorPath()},
+		{name: "product", installPath: ctx.DeviceConfig().ProductPath()},
+		{name: "system_ext", installPath: ctx.DeviceConfig().SystemExtPath()},
+		{name: "system", installPath: "system"},
+	}
+
+	groupedSources := map[string]*prebuiltSrcGroupByInstallPartition{}
+	for _, src := range android.SortedKeys(productCopyFileMap) {
+		dest := productCopyFileMap[src]
+		srcFileDir := filepath.Dir(src)
+		if _, ok := groupedSources[srcFileDir]; !ok {
+			groupedSources[srcFileDir] = newPrebuiltSrcGroupByInstallPartition()
+		}
+		appendIfCorrectInstallPartition(partitionToInstallPathList, dest, filepath.Base(src), groupedSources[srcFileDir])
+	}
+
+	return groupedSources
+}
+
+type prebuiltModuleProperties struct {
+	Name *string
+
+	Soc_specific        *bool
+	Product_specific    *bool
+	System_ext_specific *bool
+
+	Srcs []string
+	Dsts []string
+
+	No_full_install *bool
+
+	NamespaceExportedToMake bool
+
+	Visibility []string
+}
+
+// Split relative_install_path to a separate struct, because it is not supported for every
+// modules listed in [etcInstallPathToFactoryMap]
+type prebuiltSubdirProperties struct {
+	// If the base file name of the src and dst all match, dsts property does not need to be
+	// set, and only relative_install_path can be set.
+	Relative_install_path *string
+}
+
+var (
+	etcInstallPathToFactoryList = map[string]android.ModuleFactory{
+		"":                etc.PrebuiltRootFactory,
+		"avb":             etc.PrebuiltAvbFactory,
+		"bin":             etc.PrebuiltBinaryFactory,
+		"bt_firmware":     etc.PrebuiltBtFirmwareFactory,
+		"cacerts":         etc.PrebuiltEtcCaCertsFactory,
+		"dsp":             etc.PrebuiltDSPFactory,
+		"etc":             etc.PrebuiltEtcFactory,
+		"etc/dsp":         etc.PrebuiltDSPFactory,
+		"etc/firmware":    etc.PrebuiltFirmwareFactory,
+		"firmware":        etc.PrebuiltFirmwareFactory,
+		"fonts":           etc.PrebuiltFontFactory,
+		"framework":       etc.PrebuiltFrameworkFactory,
+		"lib":             etc.PrebuiltRenderScriptBitcodeFactory,
+		"lib64":           etc.PrebuiltRenderScriptBitcodeFactory,
+		"lib/rfsa":        etc.PrebuiltRFSAFactory,
+		"media":           etc.PrebuiltMediaFactory,
+		"odm":             etc.PrebuiltOdmFactory,
+		"overlay":         etc.PrebuiltOverlayFactory,
+		"priv-app":        etc.PrebuiltPrivAppFactory,
+		"res":             etc.PrebuiltResFactory,
+		"rfs":             etc.PrebuiltRfsFactory,
+		"tts":             etc.PrebuiltVoicepackFactory,
+		"usr/share":       etc.PrebuiltUserShareFactory,
+		"usr/hyphen-data": etc.PrebuiltUserHyphenDataFactory,
+		"usr/keylayout":   etc.PrebuiltUserKeyLayoutFactory,
+		"usr/keychars":    etc.PrebuiltUserKeyCharsFactory,
+		"usr/srec":        etc.PrebuiltUserSrecFactory,
+		"usr/idc":         etc.PrebuiltUserIdcFactory,
+		"vendor_dlkm":     etc.PrebuiltVendorDlkmFactory,
+		"wallpaper":       etc.PrebuiltWallpaperFactory,
+		"wlc_upt":         etc.PrebuiltWlcUptFactory,
+	}
+)
+
+func createPrebuiltEtcModule(ctx android.LoadHookContext, partition, srcDir, destDir string, destFiles []srcBaseFileInstallBaseFileTuple) string {
+	moduleProps := &prebuiltModuleProperties{}
+	propsList := []interface{}{moduleProps}
+
+	// generated module name follows the pattern:
+	// <install partition>-<src file path>-<relative install path from partition root>-<install file extension>
+	// Note that all path separators are replaced with "_" in the name
+	moduleName := partition
+	if !android.InList(srcDir, []string{"", "."}) {
+		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(srcDir, string(filepath.Separator), "_"))
+	}
+	if !android.InList(destDir, []string{"", "."}) {
+		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(destDir, string(filepath.Separator), "_"))
+	}
+	if len(destFiles) > 0 {
+		if ext := filepath.Ext(destFiles[0].srcBaseFile); ext != "" {
+			moduleName += fmt.Sprintf("-%s", strings.TrimPrefix(ext, "."))
+		}
+	}
+	moduleProps.Name = proptools.StringPtr(moduleName)
+
+	allCopyFileNamesUnchanged := true
+	var srcBaseFiles, installBaseFiles []string
+	for _, tuple := range destFiles {
+		if tuple.srcBaseFile != tuple.installBaseFile {
+			allCopyFileNamesUnchanged = false
+		}
+		srcBaseFiles = append(srcBaseFiles, tuple.srcBaseFile)
+		installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
+	}
+
+	// Find out the most appropriate module type to generate
+	var etcInstallPathKey string
+	for _, etcInstallPath := range android.SortedKeys(etcInstallPathToFactoryList) {
+		// Do not break when found but iterate until the end to find a module with more
+		// specific install path
+		if strings.HasPrefix(destDir, etcInstallPath) {
+			etcInstallPathKey = etcInstallPath
+		}
+	}
+	destDir, _ = filepath.Rel(etcInstallPathKey, destDir)
+
+	// Set partition specific properties
+	switch partition {
+	case "system_ext":
+		moduleProps.System_ext_specific = proptools.BoolPtr(true)
+	case "product":
+		moduleProps.Product_specific = proptools.BoolPtr(true)
+	case "vendor":
+		moduleProps.Soc_specific = proptools.BoolPtr(true)
+	}
+
+	// Set appropriate srcs, dsts, and releative_install_path based on
+	// the source and install file names
+	if allCopyFileNamesUnchanged {
+		moduleProps.Srcs = srcBaseFiles
+
+		// Specify relative_install_path if it is not installed in the root directory of the
+		// partition
+		if !android.InList(destDir, []string{"", "."}) {
+			propsList = append(propsList, &prebuiltSubdirProperties{
+				Relative_install_path: proptools.StringPtr(destDir),
+			})
+		}
+	} else {
+		moduleProps.Srcs = srcBaseFiles
+		dsts := []string{}
+		for _, installBaseFile := range installBaseFiles {
+			dsts = append(dsts, filepath.Join(destDir, installBaseFile))
+		}
+		moduleProps.Dsts = dsts
+	}
+
+	moduleProps.No_full_install = proptools.BoolPtr(true)
+	moduleProps.NamespaceExportedToMake = true
+	moduleProps.Visibility = []string{"//visibility:public"}
+
+	ctx.CreateModuleInDirectory(etcInstallPathToFactoryList[etcInstallPathKey], srcDir, propsList...)
+
+	return moduleName
+}
+
+func createPrebuiltEtcModulesForPartition(ctx android.LoadHookContext, partition, srcDir string, destDirFilesMap map[string][]srcBaseFileInstallBaseFileTuple) (ret []string) {
+	for _, destDir := range android.SortedKeys(destDirFilesMap) {
+		ret = append(ret, createPrebuiltEtcModule(ctx, partition, srcDir, destDir, destDirFilesMap[destDir]))
+	}
+	return ret
+}
+
+// Creates prebuilt_* modules based on the install paths and returns the list of generated
+// module names
+func createPrebuiltEtcModules(ctx android.LoadHookContext) (ret []string) {
+	groupedSources := processProductCopyFiles(ctx)
+	for _, srcDir := range android.SortedKeys(groupedSources) {
+		groupedSource := groupedSources[srcDir]
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system", srcDir, groupedSource.system)...)
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system_ext", srcDir, groupedSource.system_ext)...)
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "product", srcDir, groupedSource.product)...)
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "vendor", srcDir, groupedSource.vendor)...)
+	}
+
+	return ret
+}
diff --git a/java/app.go b/java/app.go
index e01a2ba..6572dbd 100644
--- a/java/app.go
+++ b/java/app.go
@@ -67,6 +67,9 @@
 
 	// TestHelperApp is true if the module is a android_test_helper_app
 	TestHelperApp bool
+
+	// EmbeddedJNILibs is the list of paths to JNI libraries that were embedded in the APK.
+	EmbeddedJNILibs android.Paths
 }
 
 var AppInfoProvider = blueprint.NewProvider[*AppInfo]()
@@ -405,9 +408,18 @@
 	a.checkEmbedJnis(ctx)
 	a.generateAndroidBuildActions(ctx)
 	a.generateJavaUsedByApex(ctx)
+
+	var embeddedJniLibs []android.Path
+
+	if a.embeddedJniLibs {
+		for _, jni := range a.jniLibs {
+			embeddedJniLibs = append(embeddedJniLibs, jni.path)
+		}
+	}
 	android.SetProvider(ctx, AppInfoProvider, &AppInfo{
-		Updatable:     Bool(a.appProperties.Updatable),
-		TestHelperApp: false,
+		Updatable:       Bool(a.appProperties.Updatable),
+		TestHelperApp:   false,
+		EmbeddedJNILibs: embeddedJniLibs,
 	})
 }
 
@@ -1070,12 +1082,12 @@
 			app.SdkVersion(ctx).Kind != android.SdkCorePlatform && !app.RequiresStableAPIs(ctx)
 	}
 	jniLib, prebuiltJniPackages := collectJniDeps(ctx, shouldCollectRecursiveNativeDeps,
-		checkNativeSdkVersion, func(dep cc.LinkableInterface) bool {
-			return !dep.IsNdk(ctx.Config()) && !dep.IsStubs()
-		})
+		checkNativeSdkVersion, func(dep cc.LinkableInterface) bool { return !dep.IsNdk(ctx.Config()) && !dep.IsStubs() })
 
 	var certificates []Certificate
 
+	var directImplementationDeps android.Paths
+	var transitiveImplementationDeps []depset.DepSet[android.Path]
 	ctx.VisitDirectDeps(func(module android.Module) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
@@ -1087,7 +1099,18 @@
 				ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", otherName)
 			}
 		}
+
+		if IsJniDepTag(tag) {
+			directImplementationDeps = append(directImplementationDeps, android.OutputFileForModule(ctx, module, ""))
+			if info, ok := android.OtherModuleProvider(ctx, module, cc.ImplementationDepInfoProvider); ok {
+				transitiveImplementationDeps = append(transitiveImplementationDeps, info.ImplementationDeps)
+			}
+		}
 	})
+	android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+		ImplementationDeps: depset.New(depset.PREORDER, directImplementationDeps, transitiveImplementationDeps),
+	})
+
 	return jniLib, prebuiltJniPackages, certificates
 }
 
diff --git a/java/java.go b/java/java.go
index 53b3481..86f7845 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1610,6 +1610,8 @@
 		j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
 	})
 
+	var directImplementationDeps android.Paths
+	var transitiveImplementationDeps []depset.DepSet[android.Path]
 	ctx.VisitDirectDepsWithTag(jniLibTag, func(dep android.Module) {
 		sharedLibInfo, _ := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider)
 		if sharedLibInfo.SharedLibrary != nil {
@@ -1628,11 +1630,20 @@
 				Output: relocatedLib,
 			})
 			j.data = append(j.data, relocatedLib)
+
+			directImplementationDeps = append(directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+			if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+				transitiveImplementationDeps = append(transitiveImplementationDeps, info.ImplementationDeps)
+			}
 		} else {
 			ctx.PropertyErrorf("jni_libs", "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
 		}
 	})
 
+	android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+		ImplementationDeps: depset.New(depset.PREORDER, directImplementationDeps, transitiveImplementationDeps),
+	})
+
 	j.Library.GenerateAndroidBuildActions(ctx)
 }
 
diff --git a/rust/library.go b/rust/library.go
index 20cd2af..bd3359b 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -617,6 +617,9 @@
 			TableOfContents: android.OptionalPathForPath(tocFile),
 			SharedLibrary:   outputFile,
 			Target:          ctx.Target(),
+			// TODO: when rust supports stubs uses the stubs state rather than inferring it from
+			//  apex_exclude.
+			IsStubs: Bool(library.Properties.Apex_exclude),
 		})
 	}
 
diff --git a/rust/rust.go b/rust/rust.go
index 9e06cd4..ed38ad7 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -454,6 +454,9 @@
 	// Paths to generated source files
 	SrcDeps          android.Paths
 	srcProviderFiles android.Paths
+
+	directImplementationDeps     android.Paths
+	transitiveImplementationDeps []depset.DepSet[android.Path]
 }
 
 type RustLibraries []RustLibrary
@@ -991,6 +994,10 @@
 
 		}
 
+		android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+			ImplementationDeps: depset.New(depset.PREORDER, deps.directImplementationDeps, deps.transitiveImplementationDeps),
+		})
+
 		ctx.Phony("rust", ctx.RustModule().OutputFile().Path())
 	}
 
@@ -1244,6 +1251,11 @@
 				mod.Properties.AndroidMkDylibs = append(mod.Properties.AndroidMkDylibs, makeLibName)
 				mod.Properties.SnapshotDylibs = append(mod.Properties.SnapshotDylibs, cc.BaseLibName(depName))
 
+				depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+				if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+					depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+				}
+
 			case depTag == rlibDepTag:
 				rlib, ok := rustDep.compiler.(libraryInterface)
 				if !ok || !rlib.rlib() {
@@ -1259,6 +1271,11 @@
 				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
 				depPaths.exportedLinkDirs = append(depPaths.exportedLinkDirs, linkPathFromFilePath(rustDep.OutputFile().Path()))
 
+				// rlibs are not installed, so don't add the output file to directImplementationDeps
+				if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+					depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+				}
+
 			case depTag == procMacroDepTag:
 				directProcMacroDeps = append(directProcMacroDeps, rustDep)
 				mod.Properties.AndroidMkProcMacroLibs = append(mod.Properties.AndroidMkProcMacroLibs, makeLibName)
@@ -1391,6 +1408,13 @@
 				// dependency crosses the APEX boundaries).
 				sharedLibraryInfo, exportedInfo := cc.ChooseStubOrImpl(ctx, dep)
 
+				if !sharedLibraryInfo.IsStubs {
+					depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+					if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+						depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+					}
+				}
+
 				// Re-get linkObject as ChooseStubOrImpl actually tells us which
 				// object (either from stub or non-stub) to use.
 				linkObject = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
diff --git a/tests/lib.sh b/tests/lib.sh
index 4c320d0..0e26de5 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -91,7 +91,6 @@
 }
 
 function create_mock_soong {
-  create_mock_bazel
   copy_directory build/blueprint
   copy_directory build/soong
   copy_directory build/make
@@ -143,41 +142,6 @@
   USE_RBE=false TARGET_PRODUCT=aosp_arm TARGET_RELEASE=trunk_staging TARGET_BUILD_VARIANT=userdebug build/soong/soong_ui.bash --make-mode --skip-ninja --skip-config --soong-only --skip-soong-tests "$@"
 }
 
-function create_mock_bazel {
-  copy_directory build/bazel
-  copy_directory build/bazel_common_rules
-
-  # This requires pulling more tools into the mock top to build partitions
-  delete_directory build/bazel/examples/partitions
-
-  symlink_directory packages/modules/common/build
-  symlink_directory prebuilts/bazel
-  symlink_directory prebuilts/clang
-  symlink_directory prebuilts/jdk
-  symlink_directory external/bazel-skylib
-  symlink_directory external/bazelbuild-rules_android
-  symlink_directory external/bazelbuild-rules_go
-  symlink_directory external/bazelbuild-rules_license
-  symlink_directory external/bazelbuild-kotlin-rules
-  symlink_directory external/bazelbuild-rules_cc
-  symlink_directory external/bazelbuild-rules_python
-  symlink_directory external/bazelbuild-rules_java
-  symlink_directory external/bazelbuild-rules_rust
-  symlink_directory external/bazelbuild-rules_testing
-  symlink_directory external/rust/crates/tinyjson
-
-  symlink_file WORKSPACE
-  symlink_file BUILD
-}
-
-function run_bazel {
-  # Remove the ninja_build output marker file to communicate to buildbot that this is not a regular Ninja build, and its
-  # output should not be parsed as such.
-  rm -rf out/ninja_build
-
-  build/bazel/bin/bazel "$@"
-}
-
 function run_ninja {
   build/soong/soong_ui.bash --make-mode --skip-config --soong-only --skip-soong-tests "$@"
 }