Merge "Specify padding_size argument in soong vbmeta images generation" into main
diff --git a/android/android_info.go b/android/android_info.go
index 225c8f0..561fa4c 100644
--- a/android/android_info.go
+++ b/android/android_info.go
@@ -82,18 +82,6 @@
 
 	ctx.SetOutputFiles(Paths{androidInfoProp}, "")
 	ctx.SetOutputFiles(Paths{androidInfoTxt}, ".txt")
-
-	builder := NewRuleBuilder(pctx, ctx)
-	builder.Command().Text("touch").Output(timestamp)
-	if !ctx.Config().KatiEnabled() {
-		cpPath := PathForModuleInPartitionInstall(ctx, "").Join(ctx, androidInfoTxtName)
-		builder.Command().
-			Text("rsync").
-			Flag("-a").
-			Input(androidInfoTxt).
-			Text(cpPath.String())
-	}
-	builder.Build("copy_android_info", "Copy android-info.txt")
 }
 
 // android_info module generate a file named android-info.txt that contains various information
diff --git a/android/module.go b/android/module.go
index 2dd2f19..b9489b4 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1850,7 +1850,7 @@
 	Srcs Paths
 }
 
-var SourceFilesInfoKey = blueprint.NewProvider[SourceFilesInfo]()
+var SourceFilesInfoProvider = blueprint.NewProvider[SourceFilesInfo]()
 
 // FinalModuleBuildTargetsInfo is used by buildTargetSingleton to create checkbuild and
 // per-directory build targets. Only set on the final variant of each module
@@ -1872,6 +1872,7 @@
 	BaseModuleName          string
 	CanHaveApexVariants     bool
 	MinSdkVersion           string
+	SdkVersion              string
 	NotAvailableForPlatform bool
 	// There some subtle differences between this one and the one above.
 	NotInPlatform bool
@@ -1890,15 +1891,16 @@
 
 type PrebuiltModuleInfo struct {
 	SourceExists bool
+	UsePrebuilt  bool
 }
 
 var PrebuiltModuleInfoProvider = blueprint.NewProvider[PrebuiltModuleInfo]()
 
-type HostToolProviderData struct {
+type HostToolProviderInfo struct {
 	HostToolPath OptionalPath
 }
 
-var HostToolProviderKey = blueprint.NewProvider[HostToolProviderData]()
+var HostToolProviderInfoProvider = blueprint.NewProvider[HostToolProviderInfo]()
 
 type SourceFileGenerator interface {
 	GeneratedSourceFiles() Paths
@@ -2085,7 +2087,7 @@
 	}
 
 	if sourceFileProducer, ok := m.module.(SourceFileProducer); ok {
-		SetProvider(ctx, SourceFilesInfoKey, SourceFilesInfo{Srcs: sourceFileProducer.Srcs()})
+		SetProvider(ctx, SourceFilesInfoProvider, SourceFilesInfo{Srcs: sourceFileProducer.Srcs()})
 	}
 
 	if ctx.IsFinalModule(m.module) {
@@ -2183,6 +2185,17 @@
 		commonData.MinSdkVersion = mm.MinSdkVersion()
 	}
 
+	if mm, ok := m.module.(interface {
+		SdkVersion(ctx EarlyModuleContext) ApiLevel
+	}); ok {
+		ver := mm.SdkVersion(ctx)
+		if !ver.IsNone() {
+			commonData.SdkVersion = ver.String()
+		}
+	} else if mm, ok := m.module.(interface{ SdkVersion() string }); ok {
+		commonData.SdkVersion = mm.SdkVersion()
+	}
+
 	if m.commonProperties.ForcedDisabled {
 		commonData.Enabled = false
 	} else {
@@ -2200,10 +2213,11 @@
 	if p, ok := m.module.(PrebuiltInterface); ok && p.Prebuilt() != nil {
 		SetProvider(ctx, PrebuiltModuleInfoProvider, PrebuiltModuleInfo{
 			SourceExists: p.Prebuilt().SourceExists(),
+			UsePrebuilt:  p.Prebuilt().UsePrebuilt(),
 		})
 	}
 	if h, ok := m.module.(HostToolProvider); ok {
-		SetProvider(ctx, HostToolProviderKey, HostToolProviderData{
+		SetProvider(ctx, HostToolProviderInfoProvider, HostToolProviderInfo{
 			HostToolPath: h.HostToolPath()})
 	}
 
@@ -2786,7 +2800,7 @@
 			if sourceFileProducer, ok := module.(SourceFileProducer); ok {
 				return sourceFileProducer.Srcs(), nil
 			}
-		} else if sourceFiles, ok := OtherModuleProvider(octx, module, SourceFilesInfoKey); ok {
+		} else if sourceFiles, ok := OtherModuleProvider(octx, module, SourceFilesInfoProvider); ok {
 			if tag != "" {
 				return nil, fmt.Errorf("module %q is a SourceFileProducer, which does not support tag %q", pathContextName(ctx, module), tag)
 			}
diff --git a/android/module_proxy.go b/android/module_proxy.go
index 8cc8fa1..afca0d7 100644
--- a/android/module_proxy.go
+++ b/android/module_proxy.go
@@ -11,6 +11,10 @@
 
 var _ Module = (*ModuleProxy)(nil)
 
+func (m ModuleProxy) IsNil() bool {
+	return m.module.IsNil()
+}
+
 func (m ModuleProxy) Name() string {
 	return m.module.Name()
 }
diff --git a/android/proto.go b/android/proto.go
index 66faa20..91d6732 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -75,7 +75,7 @@
 	}
 
 	ctx.VisitDirectDepsProxyWithTag(ProtoPluginDepTag, func(dep ModuleProxy) {
-		if h, ok := OtherModuleProvider(ctx, dep, HostToolProviderKey); !ok || !h.HostToolPath.Valid() {
+		if h, ok := OtherModuleProvider(ctx, dep, HostToolProviderInfoProvider); !ok || !h.HostToolPath.Valid() {
 			ctx.PropertyErrorf("proto.plugin", "module %q is not a host tool provider",
 				ctx.OtherModuleName(dep))
 		} else {
diff --git a/cc/cc.go b/cc/cc.go
index 57b5e3c..b525ccb 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -136,7 +136,11 @@
 	UnstrippedOutputFile android.Path
 	OutputFile           android.OptionalPath
 	CoverageFiles        android.Paths
-	SAbiDumpFiles        android.Paths
+	// CoverageOutputFile returns the output archive of gcno coverage information files.
+	CoverageOutputFile android.OptionalPath
+	SAbiDumpFiles      android.Paths
+	// Partition returns the partition string for this module.
+	Partition            string
 	CcLibrary            bool
 	CcLibraryInterface   bool
 	RustLibraryInterface bool
@@ -148,7 +152,9 @@
 	BaseModuleName       string
 	HasNonSystemVariants bool
 	IsLlndk              bool
-	InVendorOrProduct    bool
+	// True if the library is in the configs known NDK list.
+	IsNdk             bool
+	InVendorOrProduct bool
 	// SubName returns the modules SubName, used for image and NDK/SDK variations.
 	SubName             string
 	InRamdisk           bool
@@ -162,6 +168,8 @@
 	RelativeInstallPath string
 	// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
 	RustApexExclude bool
+	// Bootstrap tests if this module is allowed to use non-APEX version of libraries.
+	Bootstrap bool
 }
 
 var LinkableInfoProvider = blueprint.NewProvider[*LinkableInfo]()
@@ -2261,7 +2269,7 @@
 		android.SetProvider(ctx, CcObjectInfoProvider, ccObjectInfo)
 	}
 
-	linkableInfo := CreateCommonLinkableInfo(c)
+	linkableInfo := CreateCommonLinkableInfo(ctx, c)
 	if lib, ok := c.linker.(VersionedInterface); ok {
 		linkableInfo.StubsVersion = lib.StubsVersion()
 	}
@@ -2348,18 +2356,21 @@
 	}
 }
 
-func CreateCommonLinkableInfo(mod VersionedLinkableInterface) *LinkableInfo {
+func CreateCommonLinkableInfo(ctx android.ModuleContext, mod VersionedLinkableInterface) *LinkableInfo {
 	return &LinkableInfo{
 		StaticExecutable:     mod.StaticExecutable(),
 		HasStubsVariants:     mod.HasStubsVariants(),
 		OutputFile:           mod.OutputFile(),
 		UnstrippedOutputFile: mod.UnstrippedOutputFile(),
+		CoverageOutputFile:   mod.CoverageOutputFile(),
+		Partition:            mod.Partition(),
 		IsStubs:              mod.IsStubs(),
 		CcLibrary:            mod.CcLibrary(),
 		CcLibraryInterface:   mod.CcLibraryInterface(),
 		RustLibraryInterface: mod.RustLibraryInterface(),
 		BaseModuleName:       mod.BaseModuleName(),
 		IsLlndk:              mod.IsLlndk(),
+		IsNdk:                mod.IsNdk(ctx.Config()),
 		HasNonSystemVariants: mod.HasNonSystemVariants(),
 		SubName:              mod.SubName(),
 		InVendorOrProduct:    mod.InVendorOrProduct(),
@@ -2373,6 +2384,7 @@
 		RelativeInstallPath:  mod.RelativeInstallPath(),
 		// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
 		RustApexExclude: mod.RustApexExclude(),
+		Bootstrap:       mod.Bootstrap(),
 	}
 }
 
@@ -3601,14 +3613,23 @@
 	return depPaths
 }
 
-func ShouldUseStubForApex(ctx android.ModuleContext, parent, dep android.Module) bool {
+func ShouldUseStubForApex(ctx android.ModuleContext, parent android.Module, dep android.ModuleProxy) bool {
 	inVendorOrProduct := false
 	bootstrap := false
-	if linkable, ok := parent.(LinkableInterface); !ok {
-		ctx.ModuleErrorf("Not a Linkable module: %q", ctx.ModuleName())
+	if ctx.EqualModules(ctx.Module(), parent) {
+		if linkable, ok := parent.(LinkableInterface); !ok {
+			ctx.ModuleErrorf("Not a Linkable module: %q", ctx.ModuleName())
+		} else {
+			inVendorOrProduct = linkable.InVendorOrProduct()
+			bootstrap = linkable.Bootstrap()
+		}
 	} else {
-		inVendorOrProduct = linkable.InVendorOrProduct()
-		bootstrap = linkable.Bootstrap()
+		if linkable, ok := android.OtherModuleProvider(ctx, parent, LinkableInfoProvider); !ok {
+			ctx.ModuleErrorf("Not a Linkable module: %q", ctx.ModuleName())
+		} else {
+			inVendorOrProduct = linkable.InVendorOrProduct
+			bootstrap = linkable.Bootstrap
+		}
 	}
 
 	apexInfo, _ := android.OtherModuleProvider(ctx, parent, android.ApexInfoProvider)
@@ -3645,7 +3666,7 @@
 // library bar which provides stable interface and exists in the platform, foo uses the stub variant
 // of bar. If bar doesn't provide a stable interface (i.e. buildStubs() == false) or is in the
 // same APEX as foo, the non-stub variant of bar is used.
-func ChooseStubOrImpl(ctx android.ModuleContext, dep android.Module) (SharedLibraryInfo, FlagExporterInfo) {
+func ChooseStubOrImpl(ctx android.ModuleContext, dep android.ModuleProxy) (SharedLibraryInfo, FlagExporterInfo) {
 	depTag := ctx.OtherModuleDependencyTag(dep)
 	libDepTag, ok := depTag.(libraryDependencyTag)
 	if !ok || !libDepTag.shared() {
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index ee23b51..c5cafb1 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -507,36 +507,37 @@
 	// final variants, while prebuilt_postdeps needs to run before many of the
 	// PostDeps mutators, like the APEX mutators). Hence we need to dig out the
 	// prebuilt explicitly here instead.
-	var dex2oatModule android.Module
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		if parent == ctx.Module() && ctx.OtherModuleDependencyTag(child) == Dex2oatDepTag {
+	var dex2oatModule android.ModuleProxy
+	ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+		prebuiltInfo, isPrebuilt := android.OtherModuleProvider(ctx, child, android.PrebuiltModuleInfoProvider)
+		if ctx.EqualModules(parent, ctx.Module()) && ctx.OtherModuleDependencyTag(child) == Dex2oatDepTag {
 			// Found the source module, or prebuilt module that has replaced the source.
 			dex2oatModule = child
-			if android.IsModulePrebuilt(child) {
+			if isPrebuilt {
 				return false // If it's the prebuilt we're done.
 			} else {
 				return true // Recurse to check if the source has a prebuilt dependency.
 			}
 		}
-		if parent == dex2oatModule && ctx.OtherModuleDependencyTag(child) == android.PrebuiltDepTag {
-			if p := android.GetEmbeddedPrebuilt(child); p != nil && p.UsePrebuilt() {
+		if ctx.EqualModules(parent, dex2oatModule) && ctx.OtherModuleDependencyTag(child) == android.PrebuiltDepTag {
+			if isPrebuilt && prebuiltInfo.UsePrebuilt {
 				dex2oatModule = child // Found a prebuilt that should be used.
 			}
 		}
 		return false
 	})
 
-	if dex2oatModule == nil {
+	if dex2oatModule.IsNil() {
 		// If this happens there's probably a missing call to AddToolDeps in DepsMutator.
 		panic(fmt.Sprintf("Failed to lookup %s dependency", dex2oatBin))
 	}
 
-	dex2oatPath := dex2oatModule.(android.HostToolProvider).HostToolPath()
-	if !dex2oatPath.Valid() {
+	dex2oatPath, ok := android.OtherModuleProvider(ctx, dex2oatModule, android.HostToolProviderInfoProvider)
+	if !ok || !dex2oatPath.HostToolPath.Valid() {
 		panic(fmt.Sprintf("Failed to find host tool path in %s", dex2oatModule))
 	}
 
-	return dex2oatPath.Path()
+	return dex2oatPath.HostToolPath.Path()
 }
 
 // createGlobalSoongConfig creates a GlobalSoongConfig from the current context.
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index 127faa7..986b72e 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -17,6 +17,7 @@
     srcs: [
         "aconfig_files.go",
         "android_device.go",
+        "android_device_product_out.go",
         "avb_add_hash_footer.go",
         "avb_gen_vbmeta_image.go",
         "bootimg.go",
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index 080e6b7..e340602 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -62,6 +62,12 @@
 	Bootloader *string `android:"path"`
 	// Path to android-info.txt file containing board specific info.
 	Android_info *string `android:"path"`
+	// If this is the "main" android_device target for the build, i.e. the one that gets built
+	// when running a plain `m` command. Currently, this is the autogenerated android_device module
+	// in soong-only builds, but in the future when we check in android_device modules, the main
+	// one will be determined based on the lunch product. TODO: Figure out how to make this
+	// blueprint:"mutated" and still set it from filesystem_creator
+	Main_device *bool
 }
 
 type androidDevice struct {
@@ -70,9 +76,6 @@
 	partitionProps PartitionNameProperties
 
 	deviceProps DeviceProperties
-
-	// copyToProductOutTimestamp for copying necessary files to PRODUCT_OUT
-	copyToProductOutTimestamp android.WritablePath
 }
 
 func AndroidDeviceFactory() android.Module {
@@ -82,7 +85,7 @@
 	return module
 }
 
-var numAutogeneratedAndroidDevicesOnceKey android.OnceKey = android.NewOnceKey("num_auto_generated_anroid_devices")
+var numMainAndroidDevicesOnceKey android.OnceKey = android.NewOnceKey("num_auto_generated_anroid_devices")
 
 type partitionDepTagType struct {
 	blueprint.BaseDependencyTag
@@ -123,25 +126,19 @@
 	}
 }
 
-func (a *androidDevice) copyToProductOut(ctx android.ModuleContext, builder *android.RuleBuilder, src android.Path, dest string) {
-	destPath := android.PathForModuleInPartitionInstall(ctx, "").Join(ctx, dest)
-	builder.Command().Text("rsync").Flag("-a").Flag("--checksum").Input(src).Text(destPath.String())
-}
-
-func (a *androidDevice) copyFilesToProductOut(ctx android.ModuleContext) {
-	a.copyToProductOutTimestamp = android.PathForModuleOut(ctx, "timestamp")
-	builder := android.NewRuleBuilder(pctx, ctx)
-	builder.Command().Text("touch").Output(a.copyToProductOutTimestamp)
-
-	// List all individual files to be copied to PRODUCT_OUT here
-	if a.deviceProps.Bootloader != nil {
-		a.copyToProductOut(ctx, builder, android.PathForModuleSrc(ctx, proptools.String(a.deviceProps.Bootloader)), "bootloader")
+func (a *androidDevice) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if proptools.Bool(a.deviceProps.Main_device) {
+		numMainAndroidDevices := ctx.Config().Once(numMainAndroidDevicesOnceKey, func() interface{} {
+			return &atomic.Int32{}
+		}).(*atomic.Int32)
+		total := numMainAndroidDevices.Add(1)
+		if total > 1 {
+			// There should only be 1 main android_device module. That one will be
+			// made the default thing to build in soong-only builds.
+			ctx.ModuleErrorf("There cannot be more than 1 main android_device module")
+		}
 	}
 
-	builder.Build("copy_to_product_out", "Copy files to PRODUCT_OUT")
-}
-
-func (a *androidDevice) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.buildTargetFilesZip(ctx)
 	var deps []android.Path
 	if proptools.String(a.partitionProps.Super_partition_name) != "" {
@@ -193,36 +190,26 @@
 		deps = append(deps, imageOutput.DefaultOutputFiles[0])
 	})
 
-	a.copyFilesToProductOut(ctx)
-
 	allImagesStamp := android.PathForModuleOut(ctx, "all_images_stamp")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:       android.Touch,
-		Output:     allImagesStamp,
-		Implicits:  deps,
-		Validation: a.copyToProductOutTimestamp,
-	})
-	ctx.SetOutputFiles(android.Paths{allImagesStamp}, "")
-	ctx.CheckbuildFile(allImagesStamp)
-
-	if ctx.OtherModuleIsAutoGenerated(ctx.Module()) {
-		numAutogeneratedAndroidDevices := ctx.Config().Once(numAutogeneratedAndroidDevicesOnceKey, func() interface{} {
-			return &atomic.Int32{}
-		}).(*atomic.Int32)
-		total := numAutogeneratedAndroidDevices.Add(1)
-		if total > 1 {
-			// There should only be 1 autogenerated android_device module. That one will be
-			// made the default thing to build in soong-only builds.
-			ctx.ModuleErrorf("There cannot be more than 1 autogenerated android_device module")
-		}
-	}
-
-	if !ctx.Config().KatiEnabled() && ctx.OtherModuleIsAutoGenerated(ctx.Module()) {
+	var validations android.Paths
+	if !ctx.Config().KatiEnabled() && proptools.Bool(a.deviceProps.Main_device) {
 		// In soong-only builds, build this module by default.
 		// This is the analogue to this make code:
 		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/main.mk;l=1396;drc=6595459cdd8164a6008335f6372c9f97b9094060
 		ctx.Phony("droidcore-unbundled", allImagesStamp)
+
+		validations = append(validations, a.copyFilesToProductOutForSoongOnly(ctx))
 	}
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        android.Touch,
+		Output:      allImagesStamp,
+		Implicits:   deps,
+		Validations: validations,
+	})
+
+	// Checkbuilding it causes soong to make a phony, so you can say `m <module name>`
+	ctx.CheckbuildFile(allImagesStamp)
 }
 
 // Helper structs for target_files.zip creation
@@ -335,7 +322,7 @@
 
 	if a.deviceProps.Android_info != nil {
 		builder.Command().Textf("mkdir -p %s/OTA", targetFilesDir)
-		builder.Command().Textf("cp ").Input(android.PathForModuleSrc(ctx, proptools.String(a.deviceProps.Android_info))).Textf(" %s/OTA/android-info.txt", targetFilesDir)
+		builder.Command().Textf("cp ").Input(android.PathForModuleSrc(ctx, *a.deviceProps.Android_info)).Textf(" %s/OTA/android-info.txt", targetFilesDir)
 	}
 
 	a.copyImagesToTargetZip(ctx, builder, targetFilesDir)
diff --git a/filesystem/android_device_product_out.go b/filesystem/android_device_product_out.go
new file mode 100644
index 0000000..916c45a
--- /dev/null
+++ b/filesystem/android_device_product_out.go
@@ -0,0 +1,214 @@
+// Copyright (C) 2025 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 filesystem
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	copyStagingDirRule = pctx.AndroidStaticRule("copy_staging_dir", blueprint.RuleParams{
+		Command: "rsync -a --checksum $dir/ $dest && touch $out",
+	}, "dir", "dest")
+)
+
+func (a *androidDevice) copyToProductOut(ctx android.ModuleContext, builder *android.RuleBuilder, src android.Path, dest string) {
+	destPath := android.PathForModuleInPartitionInstall(ctx, "").Join(ctx, dest)
+	builder.Command().Text("rsync").Flag("-a").Flag("--checksum").Input(src).Text(destPath.String())
+}
+
+func (a *androidDevice) copyFilesToProductOutForSoongOnly(ctx android.ModuleContext) android.Path {
+	filesystemInfos := a.getFsInfos(ctx)
+
+	// The current logic to copy the staging directories to PRODUCT_OUT isn't very sound.
+	// We only track dependencies on the image file, so if the image file wasn't changed, the
+	// staging directory won't be re-copied. If you do an installclean, it would remove the copied
+	// staging directories but not affect the intermediates path image file, so the next build
+	// wouldn't re-copy them. As a hack, create a presence detector that would be deleted on
+	// an installclean to use as a dep for the staging dir copies.
+	productOutPresenceDetector := android.PathForModuleInPartitionInstall(ctx, "", "product_out_presence_detector.txt")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   android.Touch,
+		Output: productOutPresenceDetector,
+	})
+
+	var deps android.Paths
+
+	for _, partition := range android.SortedKeys(filesystemInfos) {
+		info := filesystemInfos[partition]
+		imgInstallPath := android.PathForModuleInPartitionInstall(ctx, "", partition+".img")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  info.Output,
+			Output: imgInstallPath,
+		})
+		dirStamp := android.PathForModuleOut(ctx, partition+"_staging_dir_copy_stamp.txt")
+		dirInstallPath := android.PathForModuleInPartitionInstall(ctx, "", partition)
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   copyStagingDirRule,
+			Output: dirStamp,
+			Implicits: []android.Path{
+				info.Output,
+				productOutPresenceDetector,
+			},
+			Args: map[string]string{
+				"dir":  info.RebasedDir.String(),
+				"dest": dirInstallPath.String(),
+			},
+		})
+
+		// Make it so doing `m <moduleName>` or `m <partitionType>` will copy the files to
+		// PRODUCT_OUT
+		ctx.Phony(info.ModuleName, dirStamp, imgInstallPath)
+		ctx.Phony(partition, dirStamp, imgInstallPath)
+
+		deps = append(deps, imgInstallPath, dirStamp)
+	}
+
+	// List all individual files to be copied to PRODUCT_OUT here
+	if a.deviceProps.Bootloader != nil {
+		bootloaderInstallPath := android.PathForModuleInPartitionInstall(ctx, "", "bootloader")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  android.PathForModuleSrc(ctx, *a.deviceProps.Bootloader),
+			Output: bootloaderInstallPath,
+		})
+		deps = append(deps, bootloaderInstallPath)
+	}
+
+	copyBootImg := func(prop *string, type_ string) {
+		if proptools.String(prop) != "" {
+			partition := ctx.GetDirectDepWithTag(*prop, filesystemDepTag)
+			if info, ok := android.OtherModuleProvider(ctx, partition, BootimgInfoProvider); ok {
+				installPath := android.PathForModuleInPartitionInstall(ctx, "", type_+".img")
+				ctx.Build(pctx, android.BuildParams{
+					Rule:   android.Cp,
+					Input:  info.Output,
+					Output: installPath,
+				})
+				deps = append(deps, installPath)
+			} else {
+				ctx.ModuleErrorf("%s does not set BootimgInfo\n", *prop)
+			}
+		}
+	}
+
+	copyBootImg(a.partitionProps.Init_boot_partition_name, "init_boot")
+	copyBootImg(a.partitionProps.Boot_partition_name, "boot")
+	copyBootImg(a.partitionProps.Vendor_boot_partition_name, "vendor_boot")
+
+	for _, vbmetaModName := range a.partitionProps.Vbmeta_partitions {
+		partition := ctx.GetDirectDepWithTag(vbmetaModName, filesystemDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, partition, vbmetaPartitionProvider); ok {
+			installPath := android.PathForModuleInPartitionInstall(ctx, "", info.Name+".img")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  info.Output,
+				Output: installPath,
+			})
+			deps = append(deps, installPath)
+		} else {
+			ctx.ModuleErrorf("%s does not set vbmetaPartitionProvider\n", vbmetaModName)
+		}
+	}
+
+	if proptools.String(a.partitionProps.Super_partition_name) != "" {
+		partition := ctx.GetDirectDepWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, partition, SuperImageProvider); ok {
+			installPath := android.PathForModuleInPartitionInstall(ctx, "", "super.img")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  info.SuperImage,
+				Output: installPath,
+			})
+			deps = append(deps, installPath)
+		} else {
+			ctx.ModuleErrorf("%s does not set SuperImageProvider\n", *a.partitionProps.Super_partition_name)
+		}
+	}
+
+	if proptools.String(a.deviceProps.Android_info) != "" {
+		installPath := android.PathForModuleInPartitionInstall(ctx, "", "android_info.txt")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  android.PathForModuleSrc(ctx, *a.deviceProps.Android_info),
+			Output: installPath,
+		})
+		deps = append(deps, installPath)
+	}
+
+	copyToProductOutTimestamp := android.PathForModuleOut(ctx, "product_out_copy_timestamp")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:      android.Touch,
+		Output:    copyToProductOutTimestamp,
+		Implicits: deps,
+	})
+
+	return copyToProductOutTimestamp
+}
+
+// Returns a mapping from partition type -> FilesystemInfo. This includes filesystems that are
+// nested inside of other partitions, such as the partitions inside super.img, or ramdisk inside
+// of boot.
+func (a *androidDevice) getFsInfos(ctx android.ModuleContext) map[string]FilesystemInfo {
+	type propToType struct {
+		prop *string
+		ty   string
+	}
+
+	filesystemInfos := make(map[string]FilesystemInfo)
+
+	partitionDefinitions := []propToType{
+		propToType{a.partitionProps.System_partition_name, "system"},
+		propToType{a.partitionProps.System_ext_partition_name, "system_ext"},
+		propToType{a.partitionProps.Product_partition_name, "product"},
+		propToType{a.partitionProps.Vendor_partition_name, "vendor"},
+		propToType{a.partitionProps.Odm_partition_name, "odm"},
+		propToType{a.partitionProps.Recovery_partition_name, "recovery"},
+		propToType{a.partitionProps.System_dlkm_partition_name, "system_dlkm"},
+		propToType{a.partitionProps.Vendor_dlkm_partition_name, "vendor_dlkm"},
+		propToType{a.partitionProps.Odm_dlkm_partition_name, "odm_dlkm"},
+		propToType{a.partitionProps.Userdata_partition_name, "userdata"},
+		// filesystemInfo from init_boot and vendor_boot actually are re-exports of the ramdisk
+		// images inside of them
+		propToType{a.partitionProps.Init_boot_partition_name, "ramdisk"},
+		propToType{a.partitionProps.Vendor_boot_partition_name, "vendor_ramdisk"},
+	}
+	for _, partitionDefinition := range partitionDefinitions {
+		if proptools.String(partitionDefinition.prop) != "" {
+			partition := ctx.GetDirectDepWithTag(*partitionDefinition.prop, filesystemDepTag)
+			if info, ok := android.OtherModuleProvider(ctx, partition, FilesystemProvider); ok {
+				filesystemInfos[partitionDefinition.ty] = info
+			} else {
+				ctx.ModuleErrorf("Super partition %s does not set FilesystemProvider\n", partition.Name())
+			}
+		}
+	}
+	if a.partitionProps.Super_partition_name != nil {
+		superPartition := ctx.GetDirectDepWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, superPartition, SuperImageProvider); ok {
+			for partition := range info.SubImageInfo {
+				filesystemInfos[partition] = info.SubImageInfo[partition]
+			}
+		} else {
+			ctx.ModuleErrorf("Super partition %s does not set SuperImageProvider\n", superPartition.Name())
+		}
+	}
+
+	return filesystemInfos
+}
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 803e981..98af479 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -420,10 +420,6 @@
 		cmd.FlagWithArg("--rollback_index ", strconv.FormatInt(*b.properties.Avb_rollback_index, 10))
 	}
 
-	if !ctx.Config().KatiEnabled() {
-		copyImageFileToProductOut(ctx, builder, b.bootImageType.String(), output)
-	}
-
 	builder.Build("add_avb_footer", fmt.Sprintf("Adding avb footer to %s", b.BaseModuleName()))
 	return output
 }
@@ -439,10 +435,6 @@
 		Implicits(toolDeps).
 		Output(output)
 
-	if !ctx.Config().KatiEnabled() {
-		copyImageFileToProductOut(ctx, builder, b.bootImageType.String(), output)
-	}
-
 	builder.Build("sign_bootimg", fmt.Sprintf("Signing %s", b.BaseModuleName()))
 	return output
 }
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 37a2965..306710c 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -373,9 +373,17 @@
 	// to add a dependency on the Output file, as you cannot add dependencies on directories
 	// in ninja.
 	RootDir android.Path
+	// The rebased staging directory used to build the output filesystem. If consuming this, make
+	// sure to add a dependency on the Output file, as you cannot add dependencies on directories
+	// in ninja. In many cases this is the same as RootDir, only in the system partition is it
+	// different. There, it points to the "system" sub-directory of RootDir.
+	RebasedDir android.Path
 	// A text file with block data of the .img file
 	// This is an implicit output of `build_image`
 	MapFile android.Path
+	// Name of the module that produced this FilesystemInfo origionally. (though it may be
+	// re-exported by super images or boot images)
+	ModuleName string
 }
 
 var FilesystemProvider = blueprint.NewProvider[FilesystemInfo]()
@@ -463,16 +471,17 @@
 	}
 
 	var rootDir android.OutputPath
+	var rebasedDir android.OutputPath
 	var mapFile android.Path
 	var outputHermetic android.Path
 	switch f.fsType(ctx) {
 	case ext4Type, erofsType, f2fsType:
-		f.output, outputHermetic, rootDir = f.buildImageUsingBuildImage(ctx)
+		f.output, outputHermetic, rootDir, rebasedDir = f.buildImageUsingBuildImage(ctx)
 		mapFile = f.getMapFile(ctx)
 	case compressedCpioType:
-		f.output, rootDir = f.buildCpioImage(ctx, true)
+		f.output, rootDir, rebasedDir = f.buildCpioImage(ctx, true)
 	case cpioType:
-		f.output, rootDir = f.buildCpioImage(ctx, false)
+		f.output, rootDir, rebasedDir = f.buildCpioImage(ctx, false)
 	default:
 		return
 	}
@@ -492,6 +501,8 @@
 		Output:       f.output,
 		FileListFile: fileListFile,
 		RootDir:      rootDir,
+		RebasedDir:   rebasedDir,
+		ModuleName:   ctx.ModuleName(),
 	}
 	if mapFile != nil {
 		fsInfo.MapFile = mapFile
@@ -640,24 +651,11 @@
 	return f.CopySpecsToDirs(ctx, builder, dirsToSpecs)
 }
 
-func (f *filesystem) copyFilesToProductOut(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
-	if !(f.Name() == ctx.Config().SoongDefinedSystemImage() || proptools.Bool(f.properties.Is_auto_generated)) {
-		return
-	}
-	installPath := android.PathForModuleInPartitionInstall(ctx, f.partitionName())
-	builder.Command().Textf("rsync --checksum %s %s", rebasedDir, installPath)
-}
-
-func copyImageFileToProductOut(ctx android.ModuleContext, builder *android.RuleBuilder, partition string, output android.Path) {
-	copyDir := android.PathForModuleInPartitionInstall(ctx, "").Join(ctx, fmt.Sprintf("%s.img", partition))
-	builder.Command().Textf("rsync -a %s %s", output, copyDir)
-}
-
 func (f *filesystem) rootDirString() string {
 	return f.partitionName()
 }
 
-func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) (android.Path, android.Path, android.OutputPath) {
+func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) (android.Path, android.Path, android.OutputPath, android.OutputPath) {
 	rootDir := android.PathForModuleOut(ctx, f.rootDirString()).OutputPath
 	rebasedDir := rootDir
 	if f.properties.Base_dir != nil {
@@ -675,7 +673,6 @@
 	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
 	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
 	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
-	f.copyFilesToProductOut(ctx, builder, rebasedDir)
 
 	// run host_init_verifier
 	// Ideally we should have a concept of pluggable linters that verify the generated image.
@@ -720,10 +717,6 @@
 		Output(outputHermetic).
 		Text(rootDir.String()) // directory where to find fs_config_files|dirs
 
-	if !ctx.Config().KatiEnabled() {
-		copyImageFileToProductOut(ctx, builder, f.partitionName(), output)
-	}
-
 	if f.properties.Partition_size != nil {
 		assertMaxImageSize(builder, output, *f.properties.Partition_size, false)
 	}
@@ -731,7 +724,7 @@
 	// rootDir is not deleted. Might be useful for quick inspection.
 	builder.Build("build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
 
-	return output, outputHermetic, rootDir
+	return output, outputHermetic, rootDir, rebasedDir
 }
 
 func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.Path {
@@ -925,7 +918,7 @@
 	return rootDirs, partitions
 }
 
-func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) (android.Path, android.OutputPath) {
+func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) (android.Path, android.OutputPath, android.OutputPath) {
 	if proptools.Bool(f.properties.Use_avb) {
 		ctx.PropertyErrorf("use_avb", "signing compresed cpio image using avbtool is not supported."+
 			"Consider adding this to bootimg module and signing the entire boot image.")
@@ -955,7 +948,6 @@
 	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
 	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
 	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
-	f.copyFilesToProductOut(ctx, builder, rebasedDir)
 
 	rootDirs, partitions := includeFilesRootDir(ctx)
 
@@ -986,7 +978,7 @@
 	// rootDir is not deleted. Might be useful for quick inspection.
 	builder.Build("build_cpio_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
 
-	return output, rootDir
+	return output, rootDir, rebasedDir
 }
 
 var validPartitions = []string{
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 5f51d34..6d845f7 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -286,10 +286,6 @@
 		FlagWithArg("-s ", strconv.Itoa(vbmetaMaxSize)).
 		Output(output)
 
-	if !ctx.Config().KatiEnabled() {
-		copyImageFileToProductOut(ctx, builder, v.partitionName(), output)
-	}
-
 	builder.Build("vbmeta", fmt.Sprintf("vbmeta %s", ctx.ModuleName()))
 
 	v.installDir = android.PathForModuleInstall(ctx, "etc")
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 9aed460..fbe9078 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -263,7 +263,9 @@
 	}
 	partitionProps.Vbmeta_partitions = vbmetaPartitions
 
-	deviceProps := &filesystem.DeviceProperties{}
+	deviceProps := &filesystem.DeviceProperties{
+		Main_device: proptools.BoolPtr(true),
+	}
 	if bootloader, ok := f.createBootloaderFilegroup(ctx); ok {
 		deviceProps.Bootloader = proptools.StringPtr(":" + bootloader)
 	}
diff --git a/fsgen/vbmeta_partitions.go b/fsgen/vbmeta_partitions.go
index d146cbb..a75f59c 100644
--- a/fsgen/vbmeta_partitions.go
+++ b/fsgen/vbmeta_partitions.go
@@ -173,6 +173,7 @@
 			Rollback_index:     ri,
 			Chained_partitions: chainedPartitions,
 			Partitions:         proptools.NewSimpleConfigurable(partitionModules),
+			Partition_name:     proptools.StringPtr("vbmeta"),
 		}, &struct {
 			Name *string
 		}{
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 9a8524b..65f74ce 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -349,7 +349,7 @@
 				// replaced the dependency.
 				module := android.PrebuiltGetPreferred(ctx, proxy)
 				tool := ctx.OtherModuleName(module)
-				if h, ok := android.OtherModuleProvider(ctx, module, android.HostToolProviderKey); ok {
+				if h, ok := android.OtherModuleProvider(ctx, module, android.HostToolProviderInfoProvider); ok {
 					// A HostToolProvider provides the path to a tool, which will be copied
 					// into the sandbox.
 					if !android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey).Enabled {
diff --git a/java/aar.go b/java/aar.go
index 0db195c..b982b95 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -1455,7 +1455,7 @@
 	var transitiveStaticLibsImplementationJars []depset.DepSet[android.Path]
 	var transitiveStaticLibsResourceJars []depset.DepSet[android.Path]
 
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 			tag := ctx.OtherModuleDependencyTag(module)
 			switch tag {
diff --git a/java/app.go b/java/app.go
index 6a4cd4b..f4c658c 100644
--- a/java/app.go
+++ b/java/app.go
@@ -673,7 +673,7 @@
 
 func (a *AndroidApp) proguardBuildActions(ctx android.ModuleContext) {
 	var staticLibProguardFlagFiles android.Paths
-	ctx.VisitDirectDeps(func(m android.Module) {
+	ctx.VisitDirectDepsProxy(func(m android.ModuleProxy) {
 		depProguardInfo, _ := android.OtherModuleProvider(ctx, m, ProguardSpecInfoProvider)
 		staticLibProguardFlagFiles = append(staticLibProguardFlagFiles, depProguardInfo.UnconditionallyExportedProguardFlags.ToList()...)
 		if ctx.OtherModuleDependencyTag(m) == staticLibTag {
@@ -1113,18 +1113,23 @@
 			app.SdkVersion(ctx).Kind != android.SdkCorePlatform && !app.RequiresStableAPIs(ctx)
 	}
 	jniLib, prebuiltJniPackages := collectJniDeps(ctx, shouldCollectRecursiveNativeDeps,
-		checkNativeSdkVersion, func(parent, child android.Module) bool {
+		checkNativeSdkVersion, func(parent, child android.ModuleProxy) bool {
 			apkInApex := ctx.Module().(android.ApexModule).NotInPlatform()
-			childLinkable, _ := child.(cc.LinkableInterface)
-			parentLinkable, _ := parent.(cc.LinkableInterface)
-			useStubsOfDep := childLinkable.IsStubs()
-			if apkInApex && parentLinkable != nil {
-				vintf := childLinkable.(cc.VersionedLinkableInterface).VersionedInterface()
+			childLinkable, _ := android.OtherModuleProvider(ctx, child, cc.LinkableInfoProvider)
+			parentIsLinkable := false
+			if ctx.EqualModules(ctx.Module(), parent) {
+				parentLinkable, _ := ctx.Module().(cc.LinkableInterface)
+				parentIsLinkable = parentLinkable != nil
+			} else {
+				_, parentIsLinkable = android.OtherModuleProvider(ctx, parent, cc.LinkableInfoProvider)
+			}
+			useStubsOfDep := childLinkable.IsStubs
+			if apkInApex && parentIsLinkable {
 				// APK-in-APEX
 				// If the parent is a linkable interface, use stubs if the dependency edge crosses an apex boundary.
-				useStubsOfDep = useStubsOfDep || (vintf.HasStubsVariants() && cc.ShouldUseStubForApex(ctx, parent, child))
+				useStubsOfDep = useStubsOfDep || (childLinkable.HasStubsVariants && cc.ShouldUseStubForApex(ctx, parent, child))
 			}
-			return !childLinkable.IsNdk(ctx.Config()) && !useStubsOfDep
+			return !childLinkable.IsNdk && !useStubsOfDep
 		})
 
 	var certificates []Certificate
@@ -1160,22 +1165,22 @@
 func collectJniDeps(ctx android.ModuleContext,
 	shouldCollectRecursiveNativeDeps bool,
 	checkNativeSdkVersion bool,
-	filter func(parent, child android.Module) bool) ([]jniLib, android.Paths) {
+	filter func(parent, child android.ModuleProxy) bool) ([]jniLib, android.Paths) {
 	var jniLibs []jniLib
 	var prebuiltJniPackages android.Paths
 	seenModulePaths := make(map[string]bool)
 
-	ctx.WalkDeps(func(module android.Module, parent android.Module) bool {
+	ctx.WalkDepsProxy(func(module, parent android.ModuleProxy) bool {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
 
 		if IsJniDepTag(tag) || cc.IsSharedDepTag(tag) {
-			if dep, ok := module.(cc.LinkableInterface); ok {
+			if dep, ok := android.OtherModuleProvider(ctx, module, cc.LinkableInfoProvider); ok {
 				if filter != nil && !filter(parent, module) {
 					return false
 				}
 
-				lib := dep.OutputFile()
+				lib := dep.OutputFile
 				if lib.Valid() {
 					path := lib.Path()
 					if seenModulePaths[path.String()] {
@@ -1183,7 +1188,8 @@
 					}
 					seenModulePaths[path.String()] = true
 
-					if checkNativeSdkVersion && dep.SdkVersion() == "" {
+					commonInfo := android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey)
+					if checkNativeSdkVersion && commonInfo.SdkVersion == "" {
 						ctx.PropertyErrorf("jni_libs", "JNI dependency %q uses platform APIs, but this module does not",
 							otherName)
 					}
@@ -1191,11 +1197,11 @@
 					jniLibs = append(jniLibs, jniLib{
 						name:           ctx.OtherModuleName(module),
 						path:           path,
-						target:         module.Target(),
-						coverageFile:   dep.CoverageOutputFile(),
-						unstrippedFile: dep.UnstrippedOutputFile(),
-						partition:      dep.Partition(),
-						installPaths:   android.OtherModuleProviderOrDefault(ctx, dep, android.InstallFilesProvider).InstallFiles,
+						target:         commonInfo.Target,
+						coverageFile:   dep.CoverageOutputFile,
+						unstrippedFile: dep.UnstrippedOutputFile,
+						partition:      dep.Partition,
+						installPaths:   android.OtherModuleProviderOrDefault(ctx, module, android.InstallFilesProvider).InstallFiles,
 					})
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{otherName})
diff --git a/java/base.go b/java/base.go
index 1e0d4bf..8e013b9 100644
--- a/java/base.go
+++ b/java/base.go
@@ -2151,7 +2151,7 @@
 	directStaticLibs := android.Paths{}
 	transitiveLibs := []depset.DepSet[android.Path]{}
 	transitiveStaticLibs := []depset.DepSet[android.Path]{}
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		// don't add deps of the prebuilt version of the same library
 		if ctx.ModuleName() == android.RemoveOptionalPrebuiltPrefix(module.Name()) {
 			return
@@ -2275,7 +2275,7 @@
 
 func (j *Module) collectTransitiveSrcFiles(ctx android.ModuleContext, mine android.Paths) {
 	var fromDeps []depset.DepSet[android.Path]
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(module)
 		if tag == staticLibTag {
 			if depInfo, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
@@ -2433,7 +2433,7 @@
 	var transitiveStaticJarsImplementationLibs []depset.DepSet[android.Path]
 	var transitiveStaticJarsResourceLibs []depset.DepSet[android.Path]
 
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
 
@@ -2467,7 +2467,7 @@
 				deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars...)
 				transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
 			case sdkLibTag, libTag, instrumentationForTag:
-				if _, ok := module.(*Plugin); ok {
+				if _, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
 					ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a libs dependency", otherName)
 				}
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
@@ -2485,7 +2485,7 @@
 				deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars...)
 				transitiveJava9ClasspathHeaderJars = append(transitiveJava9ClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
 			case staticLibTag:
-				if _, ok := module.(*Plugin); ok {
+				if _, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
 					ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a static_libs dependency", otherName)
 				}
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
@@ -2505,40 +2505,40 @@
 				transitiveStaticJarsImplementationLibs = append(transitiveStaticJarsImplementationLibs, dep.TransitiveStaticLibsImplementationJars)
 				transitiveStaticJarsResourceLibs = append(transitiveStaticJarsResourceLibs, dep.TransitiveStaticLibsResourceJars)
 			case pluginTag:
-				if plugin, ok := module.(*Plugin); ok {
-					if plugin.pluginProperties.Processor_class != nil {
-						addPlugins(&deps, dep.ImplementationAndResourcesJars, *plugin.pluginProperties.Processor_class)
+				if plugin, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
+					if plugin.ProcessorClass != nil {
+						addPlugins(&deps, dep.ImplementationAndResourcesJars, *plugin.ProcessorClass)
 					} else {
 						addPlugins(&deps, dep.ImplementationAndResourcesJars)
 					}
 					// Turbine doesn't run annotation processors, so any module that uses an
 					// annotation processor that generates API is incompatible with the turbine
 					// optimization.
-					deps.disableTurbine = deps.disableTurbine || Bool(plugin.pluginProperties.Generates_api)
+					deps.disableTurbine = deps.disableTurbine || plugin.GeneratesApi
 				} else {
 					ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName)
 				}
 			case errorpronePluginTag:
-				if _, ok := module.(*Plugin); ok {
+				if _, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
 					deps.errorProneProcessorPath = append(deps.errorProneProcessorPath, dep.ImplementationAndResourcesJars...)
 				} else {
 					ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName)
 				}
 			case exportedPluginTag:
-				if plugin, ok := module.(*Plugin); ok {
+				if plugin, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
 					j.exportedPluginJars = append(j.exportedPluginJars, dep.ImplementationAndResourcesJars...)
-					if plugin.pluginProperties.Processor_class != nil {
-						j.exportedPluginClasses = append(j.exportedPluginClasses, *plugin.pluginProperties.Processor_class)
+					if plugin.ProcessorClass != nil {
+						j.exportedPluginClasses = append(j.exportedPluginClasses, *plugin.ProcessorClass)
 					}
 					// Turbine doesn't run annotation processors, so any module that uses an
 					// annotation processor that generates API is incompatible with the turbine
 					// optimization.
-					j.exportedDisableTurbine = Bool(plugin.pluginProperties.Generates_api)
+					j.exportedDisableTurbine = plugin.GeneratesApi
 				} else {
 					ctx.PropertyErrorf("exported_plugins", "%q is not a java_plugin module", otherName)
 				}
 			case kotlinPluginTag:
-				if _, ok := module.(*KotlinPlugin); ok {
+				if _, ok := android.OtherModuleProvider(ctx, module, KotlinPluginInfoProvider); ok {
 					deps.kotlinPlugins = append(deps.kotlinPlugins, dep.ImplementationAndResourcesJars...)
 				} else {
 					ctx.PropertyErrorf("kotlin_plugins", "%q is not a kotlin_plugin module", otherName)
@@ -2550,21 +2550,21 @@
 					JavaInfo: dep,
 				})
 			}
-		} else if dep, ok := module.(android.SourceFileProducer); ok {
+		} else if dep, ok := android.OtherModuleProvider(ctx, module, android.SourceFilesInfoProvider); ok {
 			switch tag {
 			case sdkLibTag, libTag:
-				checkProducesJars(ctx, dep)
-				deps.classpath = append(deps.classpath, dep.Srcs()...)
-				deps.dexClasspath = append(deps.classpath, dep.Srcs()...)
+				checkProducesJars(ctx, dep, module)
+				deps.classpath = append(deps.classpath, dep.Srcs...)
+				deps.dexClasspath = append(deps.classpath, dep.Srcs...)
 				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars,
-					depset.New(depset.PREORDER, dep.Srcs(), nil))
+					depset.New(depset.PREORDER, dep.Srcs, nil))
 			case staticLibTag:
-				checkProducesJars(ctx, dep)
-				deps.classpath = append(deps.classpath, dep.Srcs()...)
-				deps.staticJars = append(deps.staticJars, dep.Srcs()...)
-				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.Srcs()...)
+				checkProducesJars(ctx, dep, module)
+				deps.classpath = append(deps.classpath, dep.Srcs...)
+				deps.staticJars = append(deps.staticJars, dep.Srcs...)
+				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.Srcs...)
 
-				depHeaderJars := depset.New(depset.PREORDER, dep.Srcs(), nil)
+				depHeaderJars := depset.New(depset.PREORDER, dep.Srcs, nil)
 				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, depHeaderJars)
 				transitiveStaticJarsHeaderLibs = append(transitiveStaticJarsHeaderLibs, depHeaderJars)
 				transitiveStaticJarsImplementationLibs = append(transitiveStaticJarsImplementationLibs, depHeaderJars)
@@ -2740,7 +2740,7 @@
 	module := ctx.Module()
 	moduleName := module.Name()
 
-	ctx.VisitDirectDeps(func(m android.Module) {
+	ctx.VisitDirectDepsProxy(func(m android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(m)
 		// This logic mirrors that in (*Module).collectDeps above.  There are several places
 		// where we explicitly return RenameUseExclude, even though it is the default, to
@@ -2779,10 +2779,8 @@
 					//fmt.Printf("collectDirectDepsProviders: %v -> %v StubsLinkType unknown\n", module, m)
 					// Fall through to the heuristic logic.
 				}
-				switch reflect.TypeOf(m).String() {
-				case "*java.GeneratedJavaLibraryModule":
+				if _, ok := android.OtherModuleProvider(ctx, m, android.CodegenInfoProvider); ok {
 					// Probably a java_aconfig_library module.
-					// TODO: make this check better.
 					return RenameUseInclude
 				}
 				switch tag {
@@ -2805,7 +2803,7 @@
 				default:
 					return RenameUseExclude
 				}
-			} else if _, ok := m.(android.SourceFileProducer); ok {
+			} else if _, ok := android.OtherModuleProvider(ctx, m, android.SourceFilesInfoProvider); ok {
 				switch tag {
 				case sdkLibTag, libTag, staticLibTag:
 					return RenameUseInclude
diff --git a/java/dex.go b/java/dex.go
index 4a7e9dc..bc14290 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -320,7 +320,7 @@
 	// TODO(b/360905238): Remove SdkSystemServer exception after resolving missing class references.
 	if !dexParams.sdkVersion.Stable() || dexParams.sdkVersion.Kind == android.SdkSystemServer {
 		var proguardRaiseDeps classpath
-		ctx.VisitDirectDepsWithTag(proguardRaiseTag, func(m android.Module) {
+		ctx.VisitDirectDepsProxyWithTag(proguardRaiseTag, func(m android.ModuleProxy) {
 			if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
 				proguardRaiseDeps = append(proguardRaiseDeps, dep.RepackagedHeaderJars...)
 			}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 2dda72b..49674b9 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -357,7 +357,7 @@
 		deps.aidlPreprocess = sdkDep.aidl
 	}
 
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
 
@@ -381,9 +381,9 @@
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs...)
 				deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
-			} else if dep, ok := module.(android.SourceFileProducer); ok {
-				checkProducesJars(ctx, dep)
-				deps.classpath = append(deps.classpath, dep.Srcs()...)
+			} else if dep, ok := android.OtherModuleProvider(ctx, module, android.SourceFilesInfoProvider); ok {
+				checkProducesJars(ctx, dep, module)
+				deps.classpath = append(deps.classpath, dep.Srcs...)
 			} else {
 				ctx.ModuleErrorf("depends on non-java module %q", otherName)
 			}
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index b1a9deb..c9a1f2b 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -97,7 +97,7 @@
 	// Save the classes jars even if this is not active as they may be used by modular hidden API
 	// processing.
 	classesJars := android.Paths{classesJar}
-	ctx.VisitDirectDepsWithTag(hiddenApiAnnotationsTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(hiddenApiAnnotationsTag, func(dep android.ModuleProxy) {
 		if javaInfo, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 			classesJars = append(classesJars, javaInfo.ImplementationJars...)
 		}
diff --git a/java/java.go b/java/java.go
index 3a1bc33..0a9381d 100644
--- a/java/java.go
+++ b/java/java.go
@@ -640,11 +640,11 @@
 	transitiveStaticLibsResourceJars       []depset.DepSet[android.Path]
 }
 
-func checkProducesJars(ctx android.ModuleContext, dep android.SourceFileProducer) {
-	for _, f := range dep.Srcs() {
+func checkProducesJars(ctx android.ModuleContext, dep android.SourceFilesInfo, module android.ModuleProxy) {
+	for _, f := range dep.Srcs {
 		if f.Ext() != ".jar" {
 			ctx.ModuleErrorf("genrule %q must generate files ending with .jar to be used as a libs or static_libs dependency",
-				ctx.OtherModuleName(dep.(blueprint.Module)))
+				ctx.OtherModuleName(module))
 		}
 	}
 }
@@ -2761,7 +2761,7 @@
 	var staticJars android.Paths
 	var staticResourceJars android.Paths
 	var staticHeaderJars android.Paths
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(module)
 		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 			switch tag {
@@ -3395,7 +3395,7 @@
 var inList = android.InList[string]
 
 // Add class loader context (CLC) of a given dependency to the current CLC.
-func addCLCFromDep(ctx android.ModuleContext, depModule android.Module,
+func addCLCFromDep(ctx android.ModuleContext, depModule android.ModuleProxy,
 	clcMap dexpreopt.ClassLoaderContextMap) {
 
 	dep, ok := android.OtherModuleProvider(ctx, depModule, JavaInfoProvider)
@@ -3455,7 +3455,7 @@
 	}
 }
 
-func addMissingOptionalUsesLibsFromDep(ctx android.ModuleContext, depModule android.Module,
+func addMissingOptionalUsesLibsFromDep(ctx android.ModuleContext, depModule android.ModuleProxy,
 	usesLibrary *usesLibrary) {
 
 	dep, ok := android.OtherModuleProvider(ctx, depModule, JavaInfoProvider)
diff --git a/java/lint.go b/java/lint.go
index cee25a8..3838745 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -398,7 +398,7 @@
 		}
 	}
 
-	extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag)
+	extraLintCheckModules := ctx.GetDirectDepsProxyWithTag(extraLintCheckTag)
 	for _, extraLintCheckModule := range extraLintCheckModules {
 		if dep, ok := android.OtherModuleProvider(ctx, extraLintCheckModule, JavaInfoProvider); ok {
 			l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars...)
@@ -423,7 +423,7 @@
 
 	depSetsBuilder := NewLintDepSetBuilder().Direct(html, text, xml, baseline)
 
-	ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(staticLibTag, func(dep android.ModuleProxy) {
 		if info, ok := android.OtherModuleProvider(ctx, dep, LintProvider); ok {
 			depSetsBuilder.Transitive(info)
 		}
diff --git a/java/plugin.go b/java/plugin.go
index 610c9fd..3534c7b 100644
--- a/java/plugin.go
+++ b/java/plugin.go
@@ -16,8 +16,21 @@
 
 import (
 	"android/soong/android"
+	"github.com/google/blueprint"
 )
 
+type JavaPluginInfo struct {
+	ProcessorClass *string
+	GeneratesApi   bool
+}
+
+var JavaPluginInfoProvider = blueprint.NewProvider[JavaPluginInfo]()
+
+type KotlinPluginInfo struct {
+}
+
+var KotlinPluginInfoProvider = blueprint.NewProvider[KotlinPluginInfo]()
+
 func init() {
 	registerJavaPluginBuildComponents(android.InitRegistrationContext)
 }
@@ -65,7 +78,22 @@
 	Generates_api *bool
 }
 
+func (p *Plugin) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.Library.GenerateAndroidBuildActions(ctx)
+
+	android.SetProvider(ctx, JavaPluginInfoProvider, JavaPluginInfo{
+		ProcessorClass: p.pluginProperties.Processor_class,
+		GeneratesApi:   Bool(p.pluginProperties.Generates_api),
+	})
+}
+
 // Plugin describes a kotlin_plugin module, a host java/kotlin library that will be used by kotlinc as a compiler plugin.
 type KotlinPlugin struct {
 	Library
 }
+
+func (p *KotlinPlugin) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.Library.GenerateAndroidBuildActions(ctx)
+
+	android.SetProvider(ctx, KotlinPluginInfoProvider, KotlinPluginInfo{})
+}
diff --git a/rust/rust.go b/rust/rust.go
index 557bdc3..6428859 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -1120,7 +1120,7 @@
 		ctx.Phony("rust", ctx.RustModule().OutputFile().Path())
 	}
 
-	linkableInfo := cc.CreateCommonLinkableInfo(mod)
+	linkableInfo := cc.CreateCommonLinkableInfo(ctx, mod)
 	linkableInfo.Static = mod.Static()
 	linkableInfo.Shared = mod.Shared()
 	linkableInfo.CrateName = mod.CrateName()
@@ -1690,7 +1690,7 @@
 			}
 		}
 
-		if srcDep, ok := android.OtherModuleProvider(ctx, dep, android.SourceFilesInfoKey); ok {
+		if srcDep, ok := android.OtherModuleProvider(ctx, dep, android.SourceFilesInfoProvider); ok {
 			if android.IsSourceDepTagWithOutputTag(depTag, "") {
 				// These are usually genrules which don't have per-target variants.
 				directSrcDeps = append(directSrcDeps, srcDep)