Merge changes from topic "llndk-use-sdk-versioning" into main

* changes:
  Filter LLNDK ABI by SDK version
  LLNDK version maps to NDK version
diff --git a/android/apex.go b/android/apex.go
index 9277ff3..414d4e1 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -70,11 +70,6 @@
 	// prebuilt, the name here doesn't have the `prebuilt_` prefix.
 	InApexModules []string
 
-	// Pointers to the ApexContents struct each of which is for apexBundle modules that this
-	// module is part of. The ApexContents gives information about which modules the apexBundle
-	// has and whether a module became part of the apexBundle via a direct dependency or not.
-	ApexContents []*ApexContents
-
 	// True if this is for a prebuilt_apex.
 	//
 	// If true then this will customize the apex processing to make it suitable for handling
@@ -162,7 +157,6 @@
 
 // ApexBundleInfo contains information about the dependencies of an apex
 type ApexBundleInfo struct {
-	Contents *ApexContents
 }
 
 var ApexBundleInfoProvider = blueprint.NewMutatorProvider[ApexBundleInfo]("apex_info")
@@ -220,15 +214,8 @@
 	// this after apex.apexMutator is run.
 	InAnyApex() bool
 
-	// Returns true if this module is directly in any APEX. Call this AFTER apex.apexMutator is
-	// run.
-	DirectlyInAnyApex() bool
-
-	// NotInPlatform tells whether or not this module is included in an APEX and therefore
-	// shouldn't be exposed to the platform (i.e. outside of the APEX) directly. A module is
-	// considered to be included in an APEX either when there actually is an APEX that
-	// explicitly has the module as its dependency or the module is not available to the
-	// platform, which indicates that the module belongs to at least one or more other APEXes.
+	// NotInPlatform returns true if the module is not available to the platform due to
+	// apex_available being set and not containing "//apex_available:platform".
 	NotInPlatform() bool
 
 	// Tests if this module could have APEX variants. Even when a module type implements
@@ -288,17 +275,6 @@
 	// See ApexModule.InAnyApex()
 	InAnyApex bool `blueprint:"mutated"`
 
-	// See ApexModule.DirectlyInAnyApex()
-	DirectlyInAnyApex bool `blueprint:"mutated"`
-
-	// AnyVariantDirectlyInAnyApex is true in the primary variant of a module if _any_ variant
-	// of the module is directly in any apex. This includes host, arch, asan, etc. variants. It
-	// is unused in any variant that is not the primary variant. Ideally this wouldn't be used,
-	// as it incorrectly mixes arch variants if only one arch is in an apex, but a few places
-	// depend on it, for example when an ASAN variant is created before the apexMutator. Call
-	// this after apex.apexMutator is run.
-	AnyVariantDirectlyInAnyApex bool `blueprint:"mutated"`
-
 	// See ApexModule.NotAvailableForPlatform()
 	NotAvailableForPlatform bool `blueprint:"mutated"`
 
@@ -335,16 +311,6 @@
 	AlwaysRequireApexVariant() bool
 }
 
-// Marker interface that identifies dependencies that should inherit the DirectlyInAnyApex state
-// from the parent to the child. For example, stubs libraries are marked as DirectlyInAnyApex if
-// their implementation is in an apex.
-type CopyDirectlyInAnyApexTag interface {
-	blueprint.DependencyTag
-
-	// Method that differentiates this interface from others.
-	CopyDirectlyInAnyApex()
-}
-
 // Interface that identifies dependencies to skip Apex dependency check
 type SkipApexAllowedDependenciesCheck interface {
 	// Returns true to skip the Apex dependency check, which limits the allowed dependency in build.
@@ -422,13 +388,8 @@
 }
 
 // Implements ApexModule
-func (m *ApexModuleBase) DirectlyInAnyApex() bool {
-	return m.ApexProperties.DirectlyInAnyApex
-}
-
-// Implements ApexModule
 func (m *ApexModuleBase) NotInPlatform() bool {
-	return m.ApexProperties.AnyVariantDirectlyInAnyApex || !m.AvailableFor(AvailableToPlatform)
+	return !m.AvailableFor(AvailableToPlatform)
 }
 
 // Implements ApexModule
@@ -586,7 +547,6 @@
 			// Variants having the same mergedName are deduped
 			merged[index].InApexVariants = append(merged[index].InApexVariants, variantName)
 			merged[index].InApexModules = append(merged[index].InApexModules, apexInfo.InApexModules...)
-			merged[index].ApexContents = append(merged[index].ApexContents, apexInfo.ApexContents...)
 			merged[index].Updatable = merged[index].Updatable || apexInfo.Updatable
 			// Platform APIs is allowed for this module only when all APEXes containing
 			// the module are with `use_platform_apis: true`.
@@ -597,7 +557,6 @@
 			apexInfo.ApexVariationName = mergedName
 			apexInfo.InApexVariants = CopyOf(apexInfo.InApexVariants)
 			apexInfo.InApexModules = CopyOf(apexInfo.InApexModules)
-			apexInfo.ApexContents = append([]*ApexContents(nil), apexInfo.ApexContents...)
 			apexInfo.TestApexes = CopyOf(apexInfo.TestApexes)
 			merged = append(merged, apexInfo)
 		}
@@ -685,14 +644,7 @@
 		apexInfos, _ = mergeApexVariations(apexInfos)
 	}
 
-	var inApex ApexMembership
-	for _, a := range apexInfos {
-		for _, apexContents := range a.ApexContents {
-			inApex = inApex.merge(apexContents.contents[ctx.ModuleName()])
-		}
-	}
 	base.ApexProperties.InAnyApex = true
-	base.ApexProperties.DirectlyInAnyApex = inApex == directlyInApex
 
 	if platformVariation && !ctx.Host() && !module.AvailableFor(AvailableToPlatform) && module.NotAvailableForPlatform() {
 		// Do not install the module for platform, but still allow it to output
@@ -783,93 +735,6 @@
 	})
 }
 
-// UpdateDirectlyInAnyApex uses the final module to store if any variant of this module is directly
-// in any APEX, and then copies the final value to all the modules. It also copies the
-// DirectlyInAnyApex value to any transitive dependencies with a CopyDirectlyInAnyApexTag
-// dependency tag.
-func UpdateDirectlyInAnyApex(mctx BottomUpMutatorContext, am ApexModule) {
-	base := am.apexModuleBase()
-	// Copy DirectlyInAnyApex and InAnyApex from any transitive dependencies with a
-	// CopyDirectlyInAnyApexTag dependency tag.
-	mctx.WalkDeps(func(child, parent Module) bool {
-		if _, ok := mctx.OtherModuleDependencyTag(child).(CopyDirectlyInAnyApexTag); ok {
-			depBase := child.(ApexModule).apexModuleBase()
-			depBase.apexPropertiesLock.Lock()
-			defer depBase.apexPropertiesLock.Unlock()
-			depBase.ApexProperties.DirectlyInAnyApex = base.ApexProperties.DirectlyInAnyApex
-			depBase.ApexProperties.InAnyApex = base.ApexProperties.InAnyApex
-			return true
-		}
-		return false
-	})
-
-	if base.ApexProperties.DirectlyInAnyApex {
-		// Variants of a module are always visited sequentially in order, so it is safe to
-		// write to another variant of this module. For a BottomUpMutator the
-		// PrimaryModule() is visited first and FinalModule() is visited last.
-		mctx.FinalModule().(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex = true
-	}
-
-	// If this is the FinalModule (last visited module) copy
-	// AnyVariantDirectlyInAnyApex to all the other variants
-	if mctx.IsFinalModule(am) {
-		mctx.VisitAllModuleVariants(func(variant Module) {
-			variant.(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex =
-				base.ApexProperties.AnyVariantDirectlyInAnyApex
-		})
-	}
-}
-
-// ApexMembership tells how a module became part of an APEX.
-type ApexMembership int
-
-const (
-	notInApex        ApexMembership = 0
-	indirectlyInApex                = iota
-	directlyInApex
-)
-
-// ApexContents gives an information about member modules of an apexBundle.  Each apexBundle has an
-// apexContents, and modules in that apex have a provider containing the apexContents of each
-// apexBundle they are part of.
-type ApexContents struct {
-	// map from a module name to its membership in this apexBundle
-	contents map[string]ApexMembership
-}
-
-// NewApexContents creates and initializes an ApexContents that is suitable
-// for use with an apex module.
-//   - contents is a map from a module name to information about its membership within
-//     the apex.
-func NewApexContents(contents map[string]ApexMembership) *ApexContents {
-	return &ApexContents{
-		contents: contents,
-	}
-}
-
-// Updates an existing membership by adding a new direct (or indirect) membership
-func (i ApexMembership) Add(direct bool) ApexMembership {
-	if direct || i == directlyInApex {
-		return directlyInApex
-	}
-	return indirectlyInApex
-}
-
-// Merges two membership into one. Merging is needed because a module can be a part of an apexBundle
-// in many different paths. For example, it could be dependend on by the apexBundle directly, but at
-// the same time, there might be an indirect dependency to the module. In that case, the more
-// specific dependency (the direct one) is chosen.
-func (i ApexMembership) merge(other ApexMembership) ApexMembership {
-	if other == directlyInApex || i == directlyInApex {
-		return directlyInApex
-	}
-
-	if other == indirectlyInApex || i == indirectlyInApex {
-		return indirectlyInApex
-	}
-	return notInApex
-}
-
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 //Below are routines for extra safety checks.
 //
diff --git a/android/config.go b/android/config.go
index 27d3b87..dff3ea5 100644
--- a/android/config.go
+++ b/android/config.go
@@ -285,6 +285,10 @@
 	return c.config.productVariables.GetBuildFlagBool("RELEASE_CREATE_ACONFIG_STORAGE_FILE")
 }
 
+func (c Config) ReleaseUseSystemFeatureBuildFlags() bool {
+	return c.config.productVariables.GetBuildFlagBool("RELEASE_USE_SYSTEM_FEATURE_BUILD_FLAGS")
+}
+
 // A DeviceConfig object represents the configuration for a particular device
 // being built. For now there will only be one of these, but in the future there
 // may be multiple devices being built.
@@ -1834,6 +1838,10 @@
 	return Bool(c.productVariables.CompressedApex) && !c.UnbundledBuildApps()
 }
 
+func (c *config) DefaultApexPayloadType() string {
+	return StringDefault(c.productVariables.DefaultApexPayloadType, "ext4")
+}
+
 func (c *config) UseSoongSystemImage() bool {
 	return Bool(c.productVariables.UseSoongSystemImage)
 }
diff --git a/android/module_context.go b/android/module_context.go
index 2014907..ae7b54f 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -831,6 +831,11 @@
 }
 
 func (m *moduleContext) SetOutputFiles(outputFiles Paths, tag string) {
+	for _, outputFile := range outputFiles {
+		if outputFile == nil {
+			panic("outputfiles cannot be nil")
+		}
+	}
 	if tag == "" {
 		if len(m.outputFiles.DefaultOutputFiles) > 0 {
 			m.ModuleErrorf("Module %s default OutputFiles cannot be overwritten", m.ModuleName())
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 5e4af0b..b90ef3b 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -508,11 +508,10 @@
 }
 
 func (p *prebuiltModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	var src Path
 	if len(p.properties.Srcs) >= 1 {
-		src = p.prebuilt.SingleSourcePath(ctx)
+		src := p.prebuilt.SingleSourcePath(ctx)
+		ctx.SetOutputFiles(Paths{src}, "")
 	}
-	ctx.SetOutputFiles(Paths{src}, "")
 }
 
 func (p *prebuiltModule) Prebuilt() *Prebuilt {
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 403c184..a157386 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -611,6 +611,7 @@
 		nsjailCmd.WriteString(" -m none:/tmp:tmpfs:size=1073741824") // 1GB, should be enough
 		nsjailCmd.WriteString(" -D nsjail_build_sandbox")
 		nsjailCmd.WriteString(" --disable_rlimits")
+		nsjailCmd.WriteString(" --skip_setsid") // ABFS relies on process-groups to track file operations
 		nsjailCmd.WriteString(" -q")
 		nsjailCmd.WriteString(" -- ")
 		nsjailCmd.WriteString("/bin/bash -c ")
diff --git a/android/sbom.go b/android/sbom.go
index 2a5499e..f2b9c0f 100644
--- a/android/sbom.go
+++ b/android/sbom.go
@@ -15,9 +15,7 @@
 package android
 
 import (
-	"io"
 	"path/filepath"
-	"strings"
 
 	"github.com/google/blueprint"
 )
@@ -55,21 +53,7 @@
 	if !ctx.Config().HasDeviceProduct() {
 		return
 	}
-	// Get all METADATA files and add them as implicit input
-	metadataFileListFile := PathForArbitraryOutput(ctx, ".module_paths", "METADATA.list")
-	f, err := ctx.Config().fs.Open(metadataFileListFile.String())
-	if err != nil {
-		panic(err)
-	}
-	b, err := io.ReadAll(f)
-	if err != nil {
-		panic(err)
-	}
-	allMetadataFiles := strings.Split(string(b), "\n")
-	implicits := []Path{metadataFileListFile}
-	for _, path := range allMetadataFiles {
-		implicits = append(implicits, PathForSource(ctx, path))
-	}
+	implicits := []Path{}
 	prodVars := ctx.Config().productVariables
 	buildFingerprintFile := PathForArbitraryOutput(ctx, "target", "product", String(prodVars.DeviceName), "build_fingerprint.txt")
 	implicits = append(implicits, buildFingerprintFile)
diff --git a/android/variable.go b/android/variable.go
index 2d43c6d..e666530 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -408,9 +408,10 @@
 
 	Ndk_abis *bool `json:",omitempty"`
 
-	ForceApexSymlinkOptimization *bool `json:",omitempty"`
-	CompressedApex               *bool `json:",omitempty"`
-	Aml_abis                     *bool `json:",omitempty"`
+	ForceApexSymlinkOptimization *bool   `json:",omitempty"`
+	CompressedApex               *bool   `json:",omitempty"`
+	DefaultApexPayloadType       *string `json:",omitempty"`
+	Aml_abis                     *bool   `json:",omitempty"`
 
 	DexpreoptGlobalConfig *string `json:",omitempty"`
 
@@ -609,8 +610,12 @@
 	ProductUseDynamicPartitionSize bool   `json:",omitempty"`
 	CopyImagesForTargetFilesZip    bool   `json:",omitempty"`
 
+	VendorSecurityPatch string `json:",omitempty"`
+
 	// Boot image stuff
+	BuildingRamdiskImage            bool   `json:",omitempty"`
 	ProductBuildBootImage           bool   `json:",omitempty"`
+	ProductBuildVendorBootImage     string `json:",omitempty"`
 	ProductBuildInitBootImage       bool   `json:",omitempty"`
 	BoardUsesRecoveryAsBoot         bool   `json:",omitempty"`
 	BoardPrebuiltBootimage          string `json:",omitempty"`
@@ -618,6 +623,10 @@
 	BoardBootimagePartitionSize     string `json:",omitempty"`
 	BoardInitBootimagePartitionSize string `json:",omitempty"`
 	BoardBootHeaderVersion          string `json:",omitempty"`
+	TargetKernelPath                string `json:",omitempty"`
+	BoardUsesGenericKernelImage     bool   `json:",omitempty"`
+	BootSecurityPatch               string `json:",omitempty"`
+	InitBootSecurityPatch           string `json:",omitempty"`
 
 	// Avb (android verified boot) stuff
 	BoardAvbEnable          bool                                `json:",omitempty"`
diff --git a/apex/aconfig_test.go b/apex/aconfig_test.go
index 76227a9..0eb8ef4 100644
--- a/apex/aconfig_test.go
+++ b/apex/aconfig_test.go
@@ -60,6 +60,7 @@
 					apex_available: [
 						"myapex",
 					],
+					compile_dex: true,
 				}
 				aconfig_declarations {
 					name: "my_aconfig_declarations_foo",
@@ -339,6 +340,7 @@
 					apex_available: [
 						"myapex",
 					],
+					compile_dex: true,
 				}
 				aconfig_declarations {
 					name: "my_aconfig_declarations_foo",
@@ -761,6 +763,7 @@
 					apex_available: [
 						"myapex",
 					],
+					compile_dex: true,
 				}
 				java_library {
 					name: "my_java_library_foo",
diff --git a/apex/apex.go b/apex/apex.go
index dc24df3..ed9e58a 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -67,7 +67,6 @@
 	// it should create a platform variant.
 	ctx.BottomUp("mark_platform_availability", markPlatformAvailability)
 	ctx.Transition("apex", &apexTransitionMutator{})
-	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).MutatesDependencies()
 }
 
 type apexBundleProperties struct {
@@ -993,25 +992,7 @@
 		return true
 	}
 
-	// Records whether a certain module is included in this apexBundle via direct dependency or
-	// inndirect dependency.
-	contents := make(map[string]android.ApexMembership)
-	mctx.WalkDeps(func(child, parent android.Module) bool {
-		if !continueApexDepsWalk(child, parent) {
-			return false
-		}
-		// If the parent is apexBundle, this child is directly depended.
-		_, directDep := parent.(*apexBundle)
-		depName := mctx.OtherModuleName(child)
-		contents[depName] = contents[depName].Add(directDep)
-		return true
-	})
-
-	// The membership information is saved for later access
-	apexContents := android.NewApexContents(contents)
-	android.SetProvider(mctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{
-		Contents: apexContents,
-	})
+	android.SetProvider(mctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{})
 
 	minSdkVersion := a.minSdkVersion(mctx)
 	// When min_sdk_version is not set, the apex is built against FutureApiLevel.
@@ -1040,7 +1021,6 @@
 		UsePlatformApis:   a.UsePlatformApis(),
 		InApexVariants:    []string{apexVariationName},
 		InApexModules:     []string{a.Name()}, // could be com.mycompany.android.foo
-		ApexContents:      []*android.ApexContents{apexContents},
 		TestApexes:        testApexes,
 		BaseApexName:      mctx.ModuleName(),
 		ApexAvailableName: proptools.String(a.properties.Apex_available_name),
@@ -1242,14 +1222,6 @@
 	return true
 }
 
-// See android.UpdateDirectlyInAnyApex
-// TODO(jiyong): move this to android/apex.go?
-func apexDirectlyInAnyMutator(mctx android.BottomUpMutatorContext) {
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		android.UpdateDirectlyInAnyApex(mctx, am)
-	}
-}
-
 const (
 	// File extensions of an APEX for different packaging methods
 	imageApexSuffix  = ".apex"
@@ -1752,7 +1724,8 @@
 }
 
 func (a *apexBundle) setPayloadFsType(ctx android.ModuleContext) {
-	switch proptools.StringDefault(a.properties.Payload_fs_type, ext4FsType) {
+	defaultFsType := ctx.Config().DefaultApexPayloadType()
+	switch proptools.StringDefault(a.properties.Payload_fs_type, defaultFsType) {
 	case ext4FsType:
 		a.payloadFsType = ext4
 	case f2fsFsType:
@@ -2082,7 +2055,7 @@
 				//
 				// Skip the dependency in unbundled builds where the device image is not
 				// being built.
-				if ch.IsStubsImplementationRequired() && !am.DirectlyInAnyApex() && !ctx.Config().UnbundledBuild() {
+				if ch.IsStubsImplementationRequired() && !am.NotInPlatform() && !ctx.Config().UnbundledBuild() {
 					// we need a module name for Make
 					name := ch.ImplementationModuleNameForMake(ctx) + ch.Properties.SubName
 					if !android.InList(name, a.makeModulesToInstall) {
@@ -2179,8 +2152,6 @@
 			ctx.PropertyErrorf("systemserverclasspath_fragments",
 				"systemserverclasspath_fragment content %q of type %q is not supported", depName, ctx.OtherModuleType(child))
 		}
-	} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
-		// nothing
 	} else if depTag == android.DarwinUniversalVariantTag {
 		// nothing
 	} else if depTag == android.RequiredDepTag {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index d0494d6..5b5fe5f 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -495,6 +495,7 @@
 				"//apex_available:platform",
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		dex_import {
@@ -664,6 +665,7 @@
 			sdk_version: "none",
 			system_modules: "none",
 			apex_available: [ "myapex" ],
+			compile_dex: true,
 		}
 
 		android_app {
@@ -2035,6 +2037,7 @@
 			apex_available: [ "myapex" ],
 			sdk_version: "current",
 			min_sdk_version: "S", // should be okay
+			compile_dex: true,
 		}
 	`,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -2584,6 +2587,7 @@
 				"myapex",
 			],
 			min_sdk_version: "30",
+			compile_dex: true,
 		}
 	`)
 
@@ -2611,6 +2615,7 @@
 			// Compile against core API surface
 			sdk_version: "core_current",
 			min_sdk_version: "30",
+			compile_dex: true,
 		}
 	`)
 
@@ -2658,6 +2663,7 @@
 			],
 			apex_available: ["myapex"],
 			min_sdk_version: "29",
+			compile_dex: true,
 		}
 
 		java_library {
@@ -2737,6 +2743,7 @@
 					srcs: ["foo/bar/MyClass.java"],
 					sdk_version: "test_current",
 					apex_available: ["myapex"],
+					compile_dex: true,
 				}
 			`,
 		},
@@ -2761,6 +2768,7 @@
 					sdk_version: "current",
 					apex_available: ["myapex"],
 					min_sdk_version: "29",
+					compile_dex: true,
 				}
 			`,
 		},
@@ -2784,6 +2792,7 @@
 					srcs: ["foo/bar/MyClass.java"],
 					sdk_version: "test_current",
 					apex_available: ["myapex"],
+					compile_dex: true,
 				}
 			`,
 		},
@@ -2807,6 +2816,7 @@
 					srcs: ["foo/bar/MyClass.java"],
 					sdk_version: "core_platform",
 					apex_available: ["myapex"],
+					compile_dex: true,
 				}
 			`,
 			preparer: java.FixtureUseLegacyCorePlatformApi("myjar-uses-legacy"),
@@ -2835,6 +2845,7 @@
 					sdk_version: "current",
 					apex_available: ["myapex"],
 					static_libs: ["transitive-jar"],
+					compile_dex: true,
 				}
 				java_library {
 					name: "transitive-jar",
@@ -5913,6 +5924,7 @@
 			system_modules: "none",
 			enabled: false,
 			apex_available: ["myapex"],
+			compile_dex: true,
 		}
 	`)
 }
@@ -7525,6 +7537,7 @@
 			apex_available: ["myapex"],
 			sdk_version: "none",
 			system_modules: "none",
+			compile_dex: true,
 		}
 
 		java_library {
@@ -7534,6 +7547,7 @@
 			apex_available: ["myapex"],
 			sdk_version: "none",
 			system_modules: "none",
+			compile_dex: true,
 		}
 
 		prebuilt_apis {
@@ -7643,6 +7657,7 @@
 			apex_available: ["myapex"],
 			sdk_version: "none",
 			system_modules: "none",
+			compile_dex: true,
 		}
 `),
 			"source/a.java":          nil,
@@ -7664,6 +7679,7 @@
 			public: {
 				enabled: true,
 			},
+			compile_dex: true,
 		}
 `),
 			"prebuilt/a.jar": nil,
@@ -7680,6 +7696,7 @@
 			public: {
 				jars: ["a.jar"],
 			},
+			compile_dex: true,
 		}
 `),
 		}), withFiles(filesForSdkLibrary),
@@ -7758,6 +7775,7 @@
 			sdk_version: "none",
 			system_modules: "none",
 			apex_available: [ "myapex" ],
+			compile_dex: true,
 		}
 
 		// Make sure that a preferred prebuilt does not affect the apex contents.
@@ -7997,6 +8015,7 @@
 				"//apex_available:platform",
 			],
 			min_sdk_version: "33",
+			compile_dex: true,
 		}
 
 		java_library {
@@ -8605,6 +8624,7 @@
 					apex_available: ["myapex"],
 					sdk_version: "none",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				java_library {
 					name: "nonbcp_lib2",
@@ -8613,6 +8633,7 @@
 					permitted_packages: ["a.b"],
 					sdk_version: "none",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				apex {
 					name: "myapex",
@@ -8638,6 +8659,7 @@
 					permitted_packages: ["foo.bar"],
 					sdk_version: "none",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				java_library {
 					name: "bcp_lib2",
@@ -8646,6 +8668,7 @@
 					permitted_packages: ["foo.bar", "bar.baz"],
 					sdk_version: "none",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				apex {
 					name: "myapex",
@@ -8676,6 +8699,7 @@
 					sdk_version: "none",
 					min_sdk_version: "29",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				java_library {
 					name: "bcp_lib_unrestricted",
@@ -8685,6 +8709,7 @@
 					sdk_version: "none",
 					min_sdk_version: "29",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				apex {
 					name: "myapex",
@@ -9834,6 +9859,7 @@
 			},
 			sdk_version: "current",
 			min_sdk_version: "29",
+			compile_dex: true,
 		}
 		`
 	fs := android.MockFS{
@@ -10268,6 +10294,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		java_library {
@@ -10279,6 +10306,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		aconfig_declarations {
@@ -10365,6 +10393,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		cc_library {
@@ -10691,6 +10720,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		java_library {
@@ -10702,6 +10732,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		aconfig_declarations {
@@ -10776,6 +10807,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		java_library {
@@ -10787,6 +10819,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		aconfig_declarations {
@@ -11469,6 +11502,7 @@
 			apex_available: ["com.android.apex30"],
 			min_sdk_version: "30",
 			sdk_version: "current",
+			compile_dex: true,
 		}
 
 		override_apex {
@@ -11760,6 +11794,7 @@
 				"com.android.foo30",
 			],
 			sdk_version: "core_current",
+			compile_dex: true,
 		}
 
 		java_library {
diff --git a/apex/container_test.go b/apex/container_test.go
index d1dfb9c..395793f 100644
--- a/apex/container_test.go
+++ b/apex/container_test.go
@@ -15,10 +15,11 @@
 package apex
 
 import (
-	"android/soong/android"
-	"android/soong/java"
 	"fmt"
 	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
 )
 
 var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
@@ -329,6 +330,7 @@
 			],
 			min_sdk_version: "30",
 			sdk_version: "current",
+			compile_dex: true,
 		}
 	`)
 
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index acf3b91..2bef0cc 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -306,10 +306,6 @@
 //     extra copying of files. Contrast that with source apex modules that has to build each variant
 //     from source.
 func (p *prebuiltCommon) apexInfoMutator(mctx android.TopDownMutatorContext) {
-
-	// Collect direct dependencies into contents.
-	contents := make(map[string]android.ApexMembership)
-
 	// Collect the list of dependencies.
 	var dependencies []android.ApexModule
 	mctx.WalkDeps(func(child, parent android.Module) bool {
@@ -347,21 +343,13 @@
 		// behavior whether there is a corresponding source module present or not.
 		depName = android.RemoveOptionalPrebuiltPrefix(depName)
 
-		// Remember if this module was added as a direct dependency.
-		direct := parent == mctx.Module()
-		contents[depName] = contents[depName].Add(direct)
-
 		// Add the module to the list of dependencies that need to have an APEX variant.
 		dependencies = append(dependencies, child.(android.ApexModule))
 
 		return true
 	})
 
-	// Create contents for the prebuilt_apex and store it away for later use.
-	apexContents := android.NewApexContents(contents)
-	android.SetProvider(mctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{
-		Contents: apexContents,
-	})
+	android.SetProvider(mctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{})
 
 	// Create an ApexInfo for the prebuilt_apex.
 	apexVariationName := p.ApexVariationName()
@@ -369,7 +357,6 @@
 		ApexVariationName: apexVariationName,
 		InApexVariants:    []string{apexVariationName},
 		InApexModules:     []string{p.BaseModuleName()}, // BaseModuleName() to avoid the prebuilt_ prefix.
-		ApexContents:      []*android.ApexContents{apexContents},
 		ForPrebuiltApex:   true,
 	}
 
diff --git a/bpf/libbpf/libbpf_prog.go b/bpf/libbpf/libbpf_prog.go
index 0ca7af1..18e03a5 100644
--- a/bpf/libbpf/libbpf_prog.go
+++ b/bpf/libbpf/libbpf_prog.go
@@ -205,7 +205,7 @@
 		if strings.ContainsRune(src.Base(), '_') {
 			ctx.ModuleErrorf("invalid character '_' in source name")
 		}
-		obj := android.ObjPathWithExt(ctx, "unstripped", src, "o")
+		obj := android.ObjPathWithExt(ctx, "unstripped", src, "bpf")
 
 		ctx.Build(pctx, android.BuildParams{
 			Rule:      libbpfProgCcRule,
@@ -218,7 +218,7 @@
 			},
 		})
 
-		objStripped := android.ObjPathWithExt(ctx, "", src, "o")
+		objStripped := android.ObjPathWithExt(ctx, "", src, "bpf")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   libbpfProgStripRule,
 			Input:  obj,
@@ -230,7 +230,7 @@
 		libbpf.objs = append(libbpf.objs, objStripped.WithoutRel())
 	}
 
-	installDir := android.PathForModuleInstall(ctx, "etc", "bpf/libbpf")
+	installDir := android.PathForModuleInstall(ctx, "etc", "bpf")
 	if len(libbpf.properties.Relative_install_path) > 0 {
 		installDir = installDir.Join(ctx, libbpf.properties.Relative_install_path)
 	}
@@ -251,7 +251,7 @@
 			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 			fmt.Fprintln(w)
 			var localModulePath string
-			localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf/libbpf"
+			localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf"
 			if len(libbpf.properties.Relative_install_path) > 0 {
 				localModulePath += "/" + libbpf.properties.Relative_install_path
 			}
diff --git a/bpf/libbpf/libbpf_prog_test.go b/bpf/libbpf/libbpf_prog_test.go
index 7f3653d..2b3b378 100644
--- a/bpf/libbpf/libbpf_prog_test.go
+++ b/bpf/libbpf/libbpf_prog_test.go
@@ -41,7 +41,7 @@
 func TestLibbpfProgDataDependency(t *testing.T) {
 	bp := `
 		libbpf_prog {
-			name: "bpf.o",
+			name: "bpf.bpf",
 			srcs: ["bpf.c"],
 		}
 
@@ -49,7 +49,7 @@
 			name: "vts_test_binary_bpf_module",
 			compile_multilib: "first",
 			srcs: ["BpfTest.cpp"],
-			data: [":bpf.o"],
+			data: [":bpf.bpf"],
 			gtest: false,
 		}
 	`
@@ -60,7 +60,7 @@
 func TestLibbpfProgSourceName(t *testing.T) {
 	bp := `
 		libbpf_prog {
-			name: "bpf_invalid_name.o",
+			name: "bpf_invalid_name.bpf",
 			srcs: ["bpf_invalid_name.c"],
 		}
 	`
diff --git a/cc/binary.go b/cc/binary.go
index 2ac9a45..4b77bea 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -505,7 +505,7 @@
 	// The original path becomes a symlink to the corresponding file in the
 	// runtime APEX.
 	translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled
-	if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !ctx.Host() && ctx.directlyInAnyApex() &&
+	if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !ctx.Host() && !ctx.isSdkVariant() &&
 		!translatedArch && ctx.apexVariationName() == "" && !ctx.inRamdisk() && !ctx.inRecovery() &&
 		!ctx.inVendorRamdisk() {
 
diff --git a/cc/cc.go b/cc/cc.go
index 76d01a5..bd91964 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -548,7 +548,6 @@
 	apexSdkVersion() android.ApiLevel
 	bootstrap() bool
 	nativeCoverage() bool
-	directlyInAnyApex() bool
 	isPreventInstall() bool
 	isCfiAssemblySupportEnabled() bool
 	getSharedFlags() *SharedFlags
@@ -1692,10 +1691,6 @@
 	return ctx.mod.nativeCoverage()
 }
 
-func (ctx *moduleContextImpl) directlyInAnyApex() bool {
-	return ctx.mod.DirectlyInAnyApex()
-}
-
 func (ctx *moduleContextImpl) isPreventInstall() bool {
 	return ctx.mod.Properties.PreventInstall
 }
diff --git a/cc/library.go b/cc/library.go
index 8aa2add..ea87946 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -662,7 +662,7 @@
 	// However, having this distinction helps guard accidental
 	// promotion or demotion of API and also helps the API review process b/191371676
 	var flag string
-	if ctx.Module().(android.ApexModule).NotInPlatform() {
+	if ctx.notInPlatform() {
 		flag = "--apex"
 	} else {
 		flag = "--systemapi"
@@ -746,6 +746,7 @@
 	hasLLNDKStubs() bool
 	hasLLNDKHeaders() bool
 	hasVendorPublicLibrary() bool
+	isLLNDKMovedToApex() bool
 }
 
 var _ libraryInterface = (*libraryDecorator)(nil)
@@ -1760,21 +1761,17 @@
 
 func (library *libraryDecorator) install(ctx ModuleContext, file android.Path) {
 	if library.shared() {
-		if library.hasStubsVariants() && !ctx.Host() && ctx.directlyInAnyApex() {
+		translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled
+		if library.hasStubsVariants() && !ctx.Host() && !ctx.isSdkVariant() &&
+			InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !library.buildStubs() &&
+			!translatedArch && !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() {
 			// Bionic libraries (e.g. libc.so) is installed to the bootstrap subdirectory.
 			// The original path becomes a symlink to the corresponding file in the
 			// runtime APEX.
-			translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled
-			if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !library.buildStubs() &&
-				!translatedArch && !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() {
-				if ctx.Device() {
-					library.installSymlinkToRuntimeApex(ctx, file)
-				}
-				library.baseInstaller.subDir = "bootstrap"
+			if ctx.Device() {
+				library.installSymlinkToRuntimeApex(ctx, file)
 			}
-		} else if ctx.directlyInAnyApex() && ctx.IsLlndk() && !isBionic(ctx.baseModuleName()) {
-			// Skip installing LLNDK (non-bionic) libraries moved to APEX.
-			ctx.Module().HideFromMake()
+			library.baseInstaller.subDir = "bootstrap"
 		}
 
 		library.baseInstaller.install(ctx, file)
@@ -1858,6 +1855,11 @@
 	return Bool(library.Properties.Llndk.Llndk_headers)
 }
 
+// isLLNDKMovedToApex returns true if this cc_library module sets the llndk.moved_to_apex property.
+func (library *libraryDecorator) isLLNDKMovedToApex() bool {
+	return Bool(library.Properties.Llndk.Moved_to_apex)
+}
+
 // hasVendorPublicLibrary returns true if this cc_library module has a variant that will build
 // vendor public library stubs.
 func (library *libraryDecorator) hasVendorPublicLibrary() bool {
diff --git a/cc/llndk_library.go b/cc/llndk_library.go
index c7950f9..162dd54 100644
--- a/cc/llndk_library.go
+++ b/cc/llndk_library.go
@@ -57,17 +57,18 @@
 	// if true, make this module available to provide headers to other modules that set
 	// llndk.symbol_file.
 	Llndk_headers *bool
+
+	// moved_to_apex marks this module has having been distributed through an apex module.
+	Moved_to_apex *bool
 }
 
 func makeLlndkVars(ctx android.MakeVarsContext) {
-	// Make uses LLNDK_MOVED_TO_APEX_LIBRARIES to avoid installing libraries on /system if
-	// they been moved to an apex.
+	// Make uses LLNDK_MOVED_TO_APEX_LIBRARIES to generate the linker config.
 	movedToApexLlndkLibraries := make(map[string]bool)
 	ctx.VisitAllModules(func(module android.Module) {
 		if library := moduleLibraryInterface(module); library != nil && library.hasLLNDKStubs() {
-			// Skip bionic libs, they are handled in different manner
-			name := library.implementationModuleName(module.(*Module).BaseModuleName())
-			if module.(android.ApexModule).DirectlyInAnyApex() && !isBionic(name) {
+			if library.isLLNDKMovedToApex() {
+				name := library.implementationModuleName(module.(*Module).BaseModuleName())
 				movedToApexLlndkLibraries[name] = true
 			}
 		}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index f0d7343..d8d8c7a 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -1504,9 +1504,6 @@
 
 		if Bool(sanProps.Memtag_globals) {
 			sanitizers = append(sanitizers, "memtag-globals")
-			// TODO(mitchp): For now, enable memtag-heap with memtag-globals because the linker
-			// isn't new enough (https://reviews.llvm.org/differential/changeset/?ref=4243566).
-			sanitizers = append(sanitizers, "memtag-heap")
 		}
 
 		if Bool(sanProps.Fuzzer) {
diff --git a/cmd/find_input_delta/find_input_delta_lib/internal_state.go b/cmd/find_input_delta/find_input_delta_lib/internal_state.go
index f0242b7..2b8c395 100644
--- a/cmd/find_input_delta/find_input_delta_lib/internal_state.go
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state.go
@@ -18,7 +18,7 @@
 	"errors"
 	"fmt"
 	"io/fs"
-	"path/filepath"
+	"regexp"
 	"slices"
 
 	fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto_internal"
@@ -73,15 +73,16 @@
 	return ret, nil
 }
 
+// We ignore any suffix digit caused by sharding.
+var InspectExtsZipRegexp = regexp.MustCompile("\\.(jar|apex|apk)[0-9]*$")
+
 // Inspect the file and extract the state of the elements in the archive.
 // If this is not an archive of some sort, nil is returned.
 func InspectFileContents(name string) ([]*fid_proto.PartialCompileInput, error) {
-	switch filepath.Ext(name) {
-	case ".jar", ".apex", ".apk":
+	if InspectExtsZipRegexp.Match([]byte(name)) {
 		return inspectZipFileContents(name)
-	default:
-		return nil, nil
 	}
+	return nil, nil
 }
 
 func inspectZipFileContents(name string) ([]*fid_proto.PartialCompileInput, error) {
diff --git a/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go b/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
index e69424c..c168d5a 100644
--- a/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
@@ -259,3 +259,25 @@
 		}
 	}
 }
+
+func TestCompareInspectExtsZipRegexp(t *testing.T) {
+	testCases := []struct {
+		Name     string
+		Expected bool
+	}{
+		{Name: ".jar", Expected: true},
+		{Name: ".jar5", Expected: true},
+		{Name: ".apex", Expected: true},
+		{Name: ".apex9", Expected: true},
+		{Name: ".apexx", Expected: false},
+		{Name: ".apk", Expected: true},
+		{Name: ".apk3", Expected: true},
+		{Name: ".go", Expected: false},
+	}
+	for _, tc := range testCases {
+		actual := InspectExtsZipRegexp.Match([]byte(tc.Name))
+		if tc.Expected != actual {
+			t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual)
+		}
+	}
+}
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index c9bd617..90409b3 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -26,19 +26,21 @@
 )
 
 func init() {
-	android.RegisterModuleType("bootimg", bootimgFactory)
+	android.RegisterModuleType("bootimg", BootimgFactory)
 }
 
 type bootimg struct {
 	android.ModuleBase
 
-	properties bootimgProperties
+	properties BootimgProperties
 
 	output     android.Path
 	installDir android.InstallPath
+
+	bootImageType bootImageType
 }
 
-type bootimgProperties struct {
+type BootimgProperties struct {
 	// Set the name of the output. Defaults to <module_name>.img.
 	Stem *string
 
@@ -56,9 +58,13 @@
 	// https://source.android.com/devices/bootloader/boot-image-header
 	Header_version *string
 
-	// Determines if this image is for the vendor_boot partition. Default is false. Refer to
-	// https://source.android.com/devices/bootloader/partitions/vendor-boot-partitions
-	Vendor_boot *bool
+	// Determines the specific type of boot image this module is building. Can be boot,
+	// vendor_boot or init_boot. Defaults to boot.
+	// Refer to https://source.android.com/devices/bootloader/partitions/vendor-boot-partitions
+	// for vendor_boot.
+	// Refer to https://source.android.com/docs/core/architecture/partitions/generic-boot for
+	// init_boot.
+	Boot_image_type *string
 
 	// Optional kernel commandline arguments
 	Cmdline []string `android:"arch_variant"`
@@ -67,6 +73,10 @@
 	// and `header_version` is greater than or equal to 4.
 	Bootconfig *string `android:"arch_variant,path"`
 
+	// The size of the partition on the device. It will be a build error if this built partition
+	// image exceeds this size.
+	Partition_size *int64
+
 	// When set to true, sign the image with avbtool. Default is false.
 	Use_avb *bool
 
@@ -79,10 +89,62 @@
 
 	// Hash and signing algorithm for avbtool. Default is SHA256_RSA4096.
 	Avb_algorithm *string
+
+	// The security patch passed to as the com.android.build.<type>.security_patch avb property.
+	// Replacement for the make variables BOOT_SECURITY_PATCH / INIT_BOOT_SECURITY_PATCH.
+	Security_patch *string
+}
+
+type bootImageType int
+
+const (
+	unsupported bootImageType = iota
+	boot
+	vendorBoot
+	initBoot
+)
+
+func toBootImageType(ctx android.ModuleContext, bootImageType string) bootImageType {
+	switch bootImageType {
+	case "boot":
+		return boot
+	case "vendor_boot":
+		return vendorBoot
+	case "init_boot":
+		return initBoot
+	default:
+		ctx.ModuleErrorf("Unknown boot_image_type %s. Must be one of \"boot\", \"vendor_boot\", or \"init_boot\"", bootImageType)
+	}
+	return unsupported
+}
+
+func (b bootImageType) String() string {
+	switch b {
+	case boot:
+		return "boot"
+	case vendorBoot:
+		return "vendor_boot"
+	case initBoot:
+		return "init_boot"
+	default:
+		panic("unknown boot image type")
+	}
+}
+
+func (b bootImageType) isBoot() bool {
+	return b == boot
+}
+
+func (b bootImageType) isVendorBoot() bool {
+	return b == vendorBoot
+}
+
+func (b bootImageType) isInitBoot() bool {
+	return b == initBoot
 }
 
 // bootimg is the image for the boot partition. It consists of header, kernel, ramdisk, and dtb.
-func bootimgFactory() android.Module {
+func BootimgFactory() android.Module {
 	module := &bootimg{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
@@ -112,12 +174,40 @@
 }
 
 func (b *bootimg) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	vendor := proptools.Bool(b.properties.Vendor_boot)
-	unsignedOutput := b.buildBootImage(ctx, vendor)
+	b.bootImageType = toBootImageType(ctx, proptools.StringDefault(b.properties.Boot_image_type, "boot"))
+	if b.bootImageType == unsupported {
+		return
+	}
+
+	kernelProp := proptools.String(b.properties.Kernel_prebuilt)
+	if b.bootImageType.isVendorBoot() && kernelProp != "" {
+		ctx.PropertyErrorf("kernel_prebuilt", "vendor_boot partition can't have kernel")
+		return
+	}
+	if b.bootImageType.isBoot() && kernelProp == "" {
+		ctx.PropertyErrorf("kernel_prebuilt", "boot partition must have kernel")
+		return
+	}
+	var kernel android.Path
+	if kernelProp != "" {
+		kernel = android.PathForModuleSrc(ctx, kernelProp)
+	}
+
+	unsignedOutput := b.buildBootImage(ctx, kernel)
 
 	output := unsignedOutput
 	if proptools.Bool(b.properties.Use_avb) {
-		output = b.signImage(ctx, unsignedOutput)
+		// This bootimg module supports 2 modes of avb signing, it picks between them based on
+		// if the private key is specified or not. If there is a key, it does a signing process
+		// similar to how the regular partitions (system, product, vendor, etc) are signed.
+		// If the key is not provided, it will just add an avb footer to the image. The avb
+		// footer only signing is how the make-built init_boot, boot, and vendor_boot images are
+		// built.
+		if proptools.String(b.properties.Avb_private_key) != "" {
+			output = b.signImage(ctx, unsignedOutput)
+		} else {
+			output = b.addAvbFooter(ctx, unsignedOutput, kernel)
+		}
 	}
 
 	b.installDir = android.PathForModuleInstall(ctx, "etc")
@@ -127,23 +217,20 @@
 	b.output = output
 }
 
-func (b *bootimg) buildBootImage(ctx android.ModuleContext, vendor bool) android.Path {
+func (b *bootimg) buildBootImage(ctx android.ModuleContext, kernel android.Path) android.Path {
 	output := android.PathForModuleOut(ctx, "unsigned", b.installFileName())
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	cmd := builder.Command().BuiltTool("mkbootimg")
 
-	kernel := proptools.String(b.properties.Kernel_prebuilt)
-	if vendor && kernel != "" {
-		ctx.PropertyErrorf("kernel_prebuilt", "vendor_boot partition can't have kernel")
-		return output
+	if kernel != nil {
+		cmd.FlagWithInput("--kernel ", kernel)
 	}
-	if !vendor && kernel == "" {
-		ctx.PropertyErrorf("kernel_prebuilt", "boot partition must have kernel")
-		return output
-	}
-	if kernel != "" {
-		cmd.FlagWithInput("--kernel ", android.PathForModuleSrc(ctx, kernel))
+
+	// These arguments are passed for boot.img and init_boot.img generation
+	if b.bootImageType.isBoot() || b.bootImageType.isInitBoot() {
+		cmd.FlagWithArg("--os_version ", ctx.Config().PlatformVersionLastStable())
+		cmd.FlagWithArg("--os_patch_level ", ctx.Config().PlatformSecurityPatch())
 	}
 
 	dtbName := proptools.String(b.properties.Dtb_prebuilt)
@@ -155,7 +242,7 @@
 	cmdline := strings.Join(b.properties.Cmdline, " ")
 	if cmdline != "" {
 		flag := "--cmdline "
-		if vendor {
+		if b.bootImageType.isVendorBoot() {
 			flag = "--vendor_cmdline "
 		}
 		cmd.FlagWithArg(flag, proptools.ShellEscapeIncludingSpaces(cmdline))
@@ -182,7 +269,7 @@
 		ramdisk := ctx.GetDirectDepWithTag(ramdiskName, bootimgRamdiskDep)
 		if filesystem, ok := ramdisk.(*filesystem); ok {
 			flag := "--ramdisk "
-			if vendor {
+			if b.bootImageType.isVendorBoot() {
 				flag = "--vendor_ramdisk "
 			}
 			cmd.FlagWithInput(flag, filesystem.OutputPath())
@@ -194,7 +281,7 @@
 
 	bootconfig := proptools.String(b.properties.Bootconfig)
 	if bootconfig != "" {
-		if !vendor {
+		if !b.bootImageType.isVendorBoot() {
 			ctx.PropertyErrorf("bootconfig", "requires vendor_boot: true")
 			return output
 		}
@@ -205,16 +292,60 @@
 		cmd.FlagWithInput("--vendor_bootconfig ", android.PathForModuleSrc(ctx, bootconfig))
 	}
 
+	// Output flag for boot.img and init_boot.img
 	flag := "--output "
-	if vendor {
+	if b.bootImageType.isVendorBoot() {
 		flag = "--vendor_boot "
 	}
 	cmd.FlagWithOutput(flag, output)
 
+	if b.properties.Partition_size != nil {
+		assertMaxImageSize(builder, output, *b.properties.Partition_size, proptools.Bool(b.properties.Use_avb))
+	}
+
 	builder.Build("build_bootimg", fmt.Sprintf("Creating %s", b.BaseModuleName()))
 	return output
 }
 
+func (b *bootimg) addAvbFooter(ctx android.ModuleContext, unsignedImage android.Path, kernel android.Path) android.Path {
+	output := android.PathForModuleOut(ctx, b.installFileName())
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Text("cp").Input(unsignedImage).Output(output)
+	cmd := builder.Command().BuiltTool("avbtool").
+		Text("add_hash_footer").
+		FlagWithInput("--image ", output)
+
+	if b.properties.Partition_size != nil {
+		cmd.FlagWithArg("--partition_size ", strconv.FormatInt(*b.properties.Partition_size, 10))
+	} else {
+		cmd.Flag("--dynamic_partition_size")
+	}
+
+	if kernel != nil {
+		cmd.Textf(`--salt $(sha256sum "%s" | cut -d " " -f 1)`, kernel.String())
+		cmd.Implicit(kernel)
+	}
+
+	cmd.FlagWithArg("--partition_name ", b.bootImageType.String())
+
+	if !b.bootImageType.isVendorBoot() {
+		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
+			"com.android.build.%s.os_version:%s", b.bootImageType.String(), ctx.Config().PlatformVersionLastStable())))
+	}
+
+	fingerprintFile := ctx.Config().BuildFingerprintFile(ctx)
+	cmd.FlagWithArg("--prop ", fmt.Sprintf("com.android.build.%s.fingerprint:%s", b.bootImageType.String(), fingerprintFile.String()))
+	cmd.OrderOnly(fingerprintFile)
+
+	if b.properties.Security_patch != nil {
+		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
+			"com.android.build.%s.security_patch:%s", b.bootImageType.String(), *b.properties.Security_patch)))
+	}
+
+	builder.Build("add_avb_footer", fmt.Sprintf("Adding avb footer to %s", b.BaseModuleName()))
+	return output
+}
+
 func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.Path) android.Path {
 	propFile, toolDeps := b.buildPropFile(ctx)
 
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index c346770..dadacae 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -162,6 +162,10 @@
 	// Determines if the module is auto-generated from Soong or not. If the module is
 	// auto-generated, its deps are exempted from visibility enforcement.
 	Is_auto_generated *bool
+
+	// Path to the dev nodes description file. This is only needed for building the ramdisk
+	// partition and should not be explicitly specified.
+	Dev_nodes_description_file *string `android:"path" blueprint:"mutated"`
 }
 
 // Additional properties required to generate erofs FS partitions.
@@ -210,6 +214,10 @@
 	filesystemModule.PackagingBase.AllowHighPriorityDeps = true
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
+
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		filesystemModule.setDevNodesDescriptionProp()
+	})
 }
 
 type depTag struct {
@@ -229,6 +237,16 @@
 
 var dependencyTagWithVisibilityEnforcementBypass = depTagWithVisibilityEnforcementBypass{}
 
+// ramdiskDevNodesDescription is the name of the filegroup module that provides the file that
+// contains the description of dev nodes added to the CPIO archive for the ramdisk partition.
+const ramdiskDevNodesDescription = "ramdisk_node_list"
+
+func (f *filesystem) setDevNodesDescriptionProp() {
+	if proptools.String(f.properties.Partition_name) == "ramdisk" {
+		f.properties.Dev_nodes_description_file = proptools.StringPtr(":" + ramdiskDevNodesDescription)
+	}
+}
+
 func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
 	if proptools.Bool(f.properties.Is_auto_generated) {
 		f.AddDeps(ctx, dependencyTagWithVisibilityEnforcementBypass)
@@ -659,6 +677,9 @@
 	cmd := builder.Command().
 		BuiltTool("mkbootfs").
 		Text(rootDir.String()) // input directory
+	if nodeList := f.properties.Dev_nodes_description_file; nodeList != nil {
+		cmd.FlagWithInput("-n ", android.PathForModuleSrc(ctx, proptools.String(nodeList)))
+	}
 	if compressed {
 		cmd.Text("|").
 			BuiltTool("lz4").
@@ -689,6 +710,7 @@
 	"odm_dlkm",
 	"system_dlkm",
 	"ramdisk",
+	"vendor_ramdisk",
 }
 
 func (f *filesystem) addMakeBuiltFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.Path) {
@@ -904,3 +926,26 @@
 
 	return provideModules, requireModules
 }
+
+// Checks that the given file doesn't exceed the given size, and will also print a warning
+// if it's nearing the maximum size. Equivalent to assert-max-image-size in make:
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/definitions.mk;l=3455;drc=993c4de29a02a6accd60ceaaee153307e1a18d10
+func assertMaxImageSize(builder *android.RuleBuilder, image android.Path, maxSize int64, addAvbLater bool) {
+	if addAvbLater {
+		// The value 69632 is derived from MAX_VBMETA_SIZE + MAX_FOOTER_SIZE in avbtool.
+		// Logic copied from make:
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=228;drc=a6a0007ef24e16c0b79f439beac4a118416717e6
+		maxSize -= 69632
+	}
+	cmd := builder.Command()
+	cmd.Textf(`file="%s"; maxsize="%d";`+
+		`total=$(stat -c "%%s" "$file" | tr -d '\n');`+
+		`if [ "$total" -gt "$maxsize" ]; then `+
+		`  echo "error: $file too large ($total > $maxsize)";`+
+		`  false;`+
+		`elif [ "$total" -gt $((maxsize - 32768)) ]; then `+
+		`  echo "WARNING: $file approaching size limit ($total now; limit $maxsize)";`+
+		`fi`,
+		image, maxSize)
+	cmd.Implicit(image)
+}
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index f325d96..746e4de 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -666,7 +666,9 @@
 
 	partition := result.ModuleForTests("myfilesystem", "android_common")
 	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
-	android.AssertDeepEquals(t, "cc_library listed in deps", "lib64/libc++.so\nlib64/libc.so\nlib64/libdl.so\nlib64/libfoo.so\nlib64/libm.so\n", fileList)
+	android.AssertDeepEquals(t, "cc_library listed in deps",
+		"lib64/bootstrap/libc.so\nlib64/bootstrap/libdl.so\nlib64/bootstrap/libm.so\nlib64/libc++.so\nlib64/libc.so\nlib64/libdl.so\nlib64/libfoo.so\nlib64/libm.so\n",
+		fileList)
 }
 
 // binfoo1 overrides binbar. transitive deps of binbar should not be installed.
@@ -701,7 +703,9 @@
 
 	partition := result.ModuleForTests("myfilesystem", "android_common")
 	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
-	android.AssertDeepEquals(t, "Shared library dep of overridden binary should not be installed", fileList, "bin/binfoo1\nlib64/libc++.so\nlib64/libc.so\nlib64/libdl.so\nlib64/libfoo2.so\nlib64/libm.so\n")
+	android.AssertDeepEquals(t, "Shared library dep of overridden binary should not be installed",
+		"bin/binfoo1\nlib64/bootstrap/libc.so\nlib64/bootstrap/libdl.so\nlib64/bootstrap/libm.so\nlib64/libc++.so\nlib64/libc.so\nlib64/libdl.so\nlib64/libfoo2.so\nlib64/libm.so\n",
+		fileList)
 }
 
 func TestInstallLinkerConfigFile(t *testing.T) {
@@ -754,3 +758,28 @@
 	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
 	android.AssertStringEquals(t, "filesystem with override app", "app/myoverrideapp/myoverrideapp.apk\n", fileList)
 }
+
+func TestRamdiskPartitionSetsDevNodes(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		fixture,
+		android.FixtureMergeMockFs(android.MockFS{
+			"ramdisk_node_list": nil,
+		}),
+	).RunTestWithBp(t, `
+		android_filesystem {
+			name: "ramdisk_filesystem",
+			partition_name: "ramdisk",
+		}
+		filegroup {
+			name: "ramdisk_node_list",
+			srcs: ["ramdisk_node_list"],
+		}
+	`)
+
+	android.AssertBoolEquals(
+		t,
+		"Generated ramdisk image expected to depend on \"ramdisk_node_list\" module",
+		true,
+		java.CheckModuleHasDependency(t, result.TestContext, "ramdisk_filesystem", "android_common", "ramdisk_node_list"),
+	)
+}
diff --git a/fsgen/Android.bp b/fsgen/Android.bp
index 8cd7518..a022581 100644
--- a/fsgen/Android.bp
+++ b/fsgen/Android.bp
@@ -13,6 +13,7 @@
         "soong-kernel",
     ],
     srcs: [
+        "boot_imgs.go",
         "filesystem_creator.go",
         "fsgen_mutators.go",
         "prebuilt_etc_modules_gen.go",
diff --git a/fsgen/boot_imgs.go b/fsgen/boot_imgs.go
new file mode 100644
index 0000000..b651862
--- /dev/null
+++ b/fsgen/boot_imgs.go
@@ -0,0 +1,189 @@
+package fsgen
+
+import (
+	"android/soong/android"
+	"android/soong/filesystem"
+	"fmt"
+	"path/filepath"
+	"strconv"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func createBootImage(ctx android.LoadHookContext) bool {
+	partitionVariables := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	if partitionVariables.TargetKernelPath == "" {
+		// There are potentially code paths that don't set TARGET_KERNEL_PATH
+		return false
+	}
+
+	kernelDir := filepath.Dir(partitionVariables.TargetKernelPath)
+	kernelBase := filepath.Base(partitionVariables.TargetKernelPath)
+	kernelFilegroupName := generatedModuleName(ctx.Config(), "kernel")
+
+	ctx.CreateModuleInDirectory(
+		android.FileGroupFactory,
+		kernelDir,
+		&struct {
+			Name       *string
+			Srcs       []string
+			Visibility []string
+		}{
+			Name:       proptools.StringPtr(kernelFilegroupName),
+			Srcs:       []string{kernelBase},
+			Visibility: []string{"//visibility:public"},
+		},
+	)
+
+	var partitionSize *int64
+	if partitionVariables.BoardBootimagePartitionSize != "" {
+		parsed, err := strconv.ParseInt(partitionVariables.BoardBootimagePartitionSize, 10, 64)
+		if err != nil {
+			panic(fmt.Sprintf("BOARD_BOOTIMAGE_PARTITION_SIZE must be an int, got %s", partitionVariables.BoardBootimagePartitionSize))
+		}
+		partitionSize = &parsed
+	}
+
+	var securityPatch *string
+	if partitionVariables.BootSecurityPatch != "" {
+		securityPatch = &partitionVariables.BootSecurityPatch
+	}
+
+	bootImageName := generatedModuleNameForPartition(ctx.Config(), "boot")
+
+	ctx.CreateModule(
+		filesystem.BootimgFactory,
+		&filesystem.BootimgProperties{
+			Kernel_prebuilt: proptools.StringPtr(":" + kernelFilegroupName),
+			Header_version:  proptools.StringPtr(partitionVariables.BoardBootHeaderVersion),
+			Partition_size:  partitionSize,
+			Use_avb:         &partitionVariables.BoardAvbEnable,
+			Security_patch:  securityPatch,
+		},
+		&struct {
+			Name *string
+		}{
+			Name: proptools.StringPtr(bootImageName),
+		},
+	)
+	return true
+}
+
+func createVendorBootImage(ctx android.LoadHookContext) bool {
+	partitionVariables := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	bootImageName := generatedModuleNameForPartition(ctx.Config(), "vendor_boot")
+
+	ctx.CreateModule(
+		filesystem.BootimgFactory,
+		&filesystem.BootimgProperties{
+			Boot_image_type: proptools.StringPtr("vendor_boot"),
+			Ramdisk_module:  proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "vendor_ramdisk")),
+			Header_version:  proptools.StringPtr(partitionVariables.BoardBootHeaderVersion),
+			Use_avb:         &partitionVariables.BoardAvbEnable,
+		},
+		&struct {
+			Name *string
+		}{
+			Name: proptools.StringPtr(bootImageName),
+		},
+	)
+	return true
+}
+
+func createInitBootImage(ctx android.LoadHookContext) bool {
+	partitionVariables := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	bootImageName := generatedModuleNameForPartition(ctx.Config(), "init_boot")
+
+	var securityPatch *string
+	if partitionVariables.InitBootSecurityPatch != "" {
+		securityPatch = &partitionVariables.InitBootSecurityPatch
+	} else if partitionVariables.BootSecurityPatch != "" {
+		securityPatch = &partitionVariables.BootSecurityPatch
+	}
+
+	ctx.CreateModule(
+		filesystem.BootimgFactory,
+		&filesystem.BootimgProperties{
+			Boot_image_type: proptools.StringPtr("init_boot"),
+			Ramdisk_module:  proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "ramdisk")),
+			Header_version:  proptools.StringPtr(partitionVariables.BoardBootHeaderVersion),
+			Use_avb:         &partitionVariables.BoardAvbEnable,
+			Security_patch:  securityPatch,
+		},
+		&struct {
+			Name *string
+		}{
+			Name: proptools.StringPtr(bootImageName),
+		},
+	)
+	return true
+}
+
+// Returns the equivalent of the BUILDING_BOOT_IMAGE variable in make. Derived from this logic:
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=458;drc=5b55f926830963c02ab1d2d91e46442f04ba3af0
+func buildingBootImage(partitionVars android.PartitionVariables) bool {
+	if partitionVars.BoardUsesRecoveryAsBoot {
+		return false
+	}
+
+	if partitionVars.ProductBuildBootImage {
+		return true
+	}
+
+	if len(partitionVars.BoardPrebuiltBootimage) > 0 {
+		return false
+	}
+
+	if len(partitionVars.BoardBootimagePartitionSize) > 0 {
+		return true
+	}
+
+	// TODO: return true if BOARD_KERNEL_BINARIES is set and has a *_BOOTIMAGE_PARTITION_SIZE
+	// variable. However, I don't think BOARD_KERNEL_BINARIES is ever set in practice.
+
+	return false
+}
+
+// Returns the equivalent of the BUILDING_VENDOR_BOOT_IMAGE variable in make. Derived from this logic:
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=518;drc=5b55f926830963c02ab1d2d91e46442f04ba3af0
+func buildingVendorBootImage(partitionVars android.PartitionVariables) bool {
+	if v, exists := boardBootHeaderVersion(partitionVars); exists && v >= 3 {
+		x := partitionVars.ProductBuildVendorBootImage
+		if x == "" || x == "true" {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Derived from: https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=480;drc=5b55f926830963c02ab1d2d91e46442f04ba3af0
+func buildingInitBootImage(partitionVars android.PartitionVariables) bool {
+	if !partitionVars.ProductBuildInitBootImage {
+		if partitionVars.BoardUsesRecoveryAsBoot || len(partitionVars.BoardPrebuiltInitBootimage) > 0 {
+			return false
+		} else if len(partitionVars.BoardInitBootimagePartitionSize) > 0 {
+			return true
+		}
+	} else {
+		if partitionVars.BoardUsesRecoveryAsBoot {
+			panic("PRODUCT_BUILD_INIT_BOOT_IMAGE is true, but so is BOARD_USES_RECOVERY_AS_BOOT. Use only one option.")
+		}
+		return true
+	}
+	return false
+}
+
+func boardBootHeaderVersion(partitionVars android.PartitionVariables) (int, bool) {
+	if len(partitionVars.BoardBootHeaderVersion) == 0 {
+		return 0, false
+	}
+	v, err := strconv.ParseInt(partitionVars.BoardBootHeaderVersion, 10, 32)
+	if err != nil {
+		panic(fmt.Sprintf("BOARD_BOOT_HEADER_VERSION must be an int, got: %q", partitionVars.BoardBootHeaderVersion))
+	}
+	return int(v), true
+}
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index c74579a..6bfb097 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -47,6 +47,10 @@
 
 	Vbmeta_module_names    []string `blueprint:"mutated"`
 	Vbmeta_partition_names []string `blueprint:"mutated"`
+
+	Boot_image        string `blueprint:"mutated" android:"path_device_first"`
+	Vendor_boot_image string `blueprint:"mutated" android:"path_device_first"`
+	Init_boot_image   string `blueprint:"mutated" android:"path_device_first"`
 }
 
 type filesystemCreator struct {
@@ -71,6 +75,42 @@
 	return module
 }
 
+func generatedPartitions(ctx android.LoadHookContext) []string {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	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.DeviceConfig().BuildingUserdataImage() && ctx.DeviceConfig().UserdataPath() == "data" {
+		generatedPartitions = append(generatedPartitions, "userdata")
+	}
+	if partitionVars.BuildingSystemDlkmImage {
+		generatedPartitions = append(generatedPartitions, "system_dlkm")
+	}
+	if partitionVars.BuildingVendorDlkmImage {
+		generatedPartitions = append(generatedPartitions, "vendor_dlkm")
+	}
+	if partitionVars.BuildingOdmDlkmImage {
+		generatedPartitions = append(generatedPartitions, "odm_dlkm")
+	}
+	if partitionVars.BuildingRamdiskImage {
+		generatedPartitions = append(generatedPartitions, "ramdisk")
+	}
+	if buildingVendorBootImage(partitionVars) {
+		generatedPartitions = append(generatedPartitions, "vendor_ramdisk")
+	}
+	return generatedPartitions
+}
+
 func (f *filesystemCreator) createInternalModules(ctx android.LoadHookContext) {
 	soongGeneratedPartitions := generatedPartitions(ctx)
 	finalSoongGeneratedPartitions := make([]string, 0, len(soongGeneratedPartitions))
@@ -83,6 +123,29 @@
 		}
 	}
 
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	if buildingBootImage(partitionVars) {
+		if createBootImage(ctx) {
+			f.properties.Boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "boot")
+		} else {
+			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "boot")
+		}
+	}
+	if buildingVendorBootImage(partitionVars) {
+		if createVendorBootImage(ctx) {
+			f.properties.Vendor_boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "vendor_boot")
+		} else {
+			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "vendor_boot")
+		}
+	}
+	if buildingInitBootImage(partitionVars) {
+		if createInitBootImage(ctx) {
+			f.properties.Init_boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "init_boot")
+		} else {
+			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "init_boot")
+		}
+	}
+
 	for _, x := range createVbmetaPartitions(ctx, finalSoongGeneratedPartitions) {
 		f.properties.Vbmeta_module_names = append(f.properties.Vbmeta_module_names, x.moduleName)
 		f.properties.Vbmeta_partition_names = append(f.properties.Vbmeta_partition_names, x.partitionName)
@@ -140,13 +203,13 @@
 	ctx.CreateModule(filesystem.AndroidDeviceFactory, baseProps, partitionProps)
 }
 
-func partitionSpecificFsProps(fsProps *filesystem.FilesystemProperties, partitionType string) {
+func partitionSpecificFsProps(fsProps *filesystem.FilesystemProperties, partitionVars android.PartitionVariables, partitionType string) {
 	switch partitionType {
 	case "system":
 		fsProps.Build_logtags = proptools.BoolPtr(true)
 		// https://source.corp.google.com/h/googleplex-android/platform/build//639d79f5012a6542ab1f733b0697db45761ab0f3:core/packaging/flags.mk;l=21;drc=5ba8a8b77507f93aa48cc61c5ba3f31a4d0cbf37;bpv=1;bpt=0
 		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
-		// Identical to that of the generic_system_image
+		// Identical to that of the aosp_shared_system_image
 		fsProps.Fsverity.Inputs = []string{
 			"etc/boot-image.prof",
 			"etc/dirty-image-objects",
@@ -176,7 +239,6 @@
 				Name:   proptools.StringPtr("system/lib/modules"),
 			},
 		}
-		fsProps.Base_dir = proptools.StringPtr("system")
 	case "system_ext":
 		fsProps.Fsverity.Inputs = []string{
 			"framework/*",
@@ -198,7 +260,6 @@
 				Name:   proptools.StringPtr("vendor/lib/modules"),
 			},
 		}
-		fsProps.Base_dir = proptools.StringPtr("vendor")
 	case "odm":
 		fsProps.Symlinks = []filesystem.SymlinkDefinition{
 			filesystem.SymlinkDefinition{
@@ -206,10 +267,30 @@
 				Name:   proptools.StringPtr("odm/lib/modules"),
 			},
 		}
-		fsProps.Base_dir = proptools.StringPtr("odm")
 	case "userdata":
 		fsProps.Base_dir = proptools.StringPtr("data")
-
+	case "ramdisk":
+		// Following the logic in https://cs.android.com/android/platform/superproject/main/+/c3c5063df32748a8806ce5da5dd0db158eab9ad9:build/make/core/Makefile;l=1307
+		fsProps.Dirs = android.NewSimpleConfigurable([]string{
+			"debug_ramdisk",
+			"dev",
+			"metadata",
+			"mnt",
+			"proc",
+			"second_stage_resources",
+			"sys",
+		})
+		if partitionVars.BoardUsesGenericKernelImage {
+			fsProps.Dirs.AppendSimpleValue([]string{
+				"first_stage_ramdisk/debug_ramdisk",
+				"first_stage_ramdisk/dev",
+				"first_stage_ramdisk/metadata",
+				"first_stage_ramdisk/mnt",
+				"first_stage_ramdisk/proc",
+				"first_stage_ramdisk/second_stage_resources",
+				"first_stage_ramdisk/sys",
+			})
+		}
 	}
 }
 
@@ -320,7 +401,7 @@
 	}
 	switch partitionType {
 	case "system_dlkm":
-		props.Srcs = ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelModules
+		props.Srcs = android.ExistentPathsForSources(ctx, ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelModules).Strings()
 		props.System_dlkm_specific = proptools.BoolPtr(true)
 		if len(ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelLoadModules) == 0 {
 			// Create empty modules.load file for system
@@ -331,7 +412,7 @@
 			props.Blocklist_file = proptools.StringPtr(blocklistFile)
 		}
 	case "vendor_dlkm":
-		props.Srcs = ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.VendorKernelModules
+		props.Srcs = android.ExistentPathsForSources(ctx, ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.VendorKernelModules).Strings()
 		if len(ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelModules) > 0 {
 			props.System_deps = []string{":" + generatedModuleName(ctx.Config(), "system_dlkm-kernel-modules") + "{.modules}"}
 		}
@@ -340,7 +421,7 @@
 			props.Blocklist_file = proptools.StringPtr(blocklistFile)
 		}
 	case "odm_dlkm":
-		props.Srcs = ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.OdmKernelModules
+		props.Srcs = android.ExistentPathsForSources(ctx, ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.OdmKernelModules).Strings()
 		props.Odm_dlkm_specific = proptools.BoolPtr(true)
 		if blocklistFile := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.OdmKernelBlocklistFile; blocklistFile != "" {
 			props.Blocklist_file = proptools.StringPtr(blocklistFile)
@@ -525,11 +606,13 @@
 
 	fsProps.Partition_name = proptools.StringPtr(partitionType)
 
-	fsProps.Base_dir = proptools.StringPtr(partitionType)
+	if !strings.Contains(partitionType, "ramdisk") {
+		fsProps.Base_dir = proptools.StringPtr(partitionType)
+	}
 
 	fsProps.Is_auto_generated = proptools.BoolPtr(true)
 
-	partitionSpecificFsProps(fsProps, partitionType)
+	partitionSpecificFsProps(fsProps, partitionVars, partitionType)
 
 	// system_image properties that are not set:
 	// - filesystemProperties.Avb_hash_algorithm
@@ -546,7 +629,7 @@
 	return fsProps, true
 }
 
-func (f *filesystemCreator) createDiffTest(ctx android.ModuleContext, partitionType string) android.Path {
+func (f *filesystemCreator) createFileListDiffTest(ctx android.ModuleContext, partitionType string) android.Path {
 	partitionModuleName := generatedModuleNameForPartition(ctx.Config(), partitionType)
 	systemImage := ctx.GetDirectDepWithTag(partitionModuleName, generatedFilesystemDepTag)
 	filesystemInfo, ok := android.OtherModuleProvider(ctx, systemImage, filesystem.FilesystemProvider)
@@ -591,13 +674,17 @@
 	makeVbmetaFile := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/%s.img", ctx.Config().DeviceName(), vbmetaPartitionName))
 
 	diffTestResultFile := android.PathForModuleOut(ctx, fmt.Sprintf("diff_test_%s.txt", vbmetaModuleName))
+	createDiffTest(ctx, diffTestResultFile, soongVbMetaFile, makeVbmetaFile)
+	return diffTestResultFile
+}
+
+func createDiffTest(ctx android.ModuleContext, diffTestResultFile android.WritablePath, file1 android.Path, file2 android.Path) {
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().Text("diff").
-		Input(soongVbMetaFile).
-		Input(makeVbmetaFile)
+		Input(file1).
+		Input(file2)
 	builder.Command().Text("touch").Output(diffTestResultFile)
-	builder.Build(vbmetaModuleName+" diff test", vbmetaModuleName+" diff test")
-	return diffTestResultFile
+	builder.Build("diff test "+diffTestResultFile.String(), "diff test")
 }
 
 type systemImageDepTagType struct {
@@ -634,7 +721,7 @@
 
 	var diffTestFiles []android.Path
 	for _, partitionType := range f.properties.Generated_partition_types {
-		diffTestFile := f.createDiffTest(ctx, partitionType)
+		diffTestFile := f.createFileListDiffTest(ctx, partitionType)
 		diffTestFiles = append(diffTestFiles, diffTestFile)
 		ctx.Phony(fmt.Sprintf("soong_generated_%s_filesystem_test", partitionType), diffTestFile)
 	}
@@ -648,6 +735,30 @@
 		diffTestFiles = append(diffTestFiles, diffTestFile)
 		ctx.Phony(fmt.Sprintf("soong_generated_%s_filesystem_test", f.properties.Vbmeta_partition_names[i]), diffTestFile)
 	}
+	if f.properties.Boot_image != "" {
+		diffTestFile := android.PathForModuleOut(ctx, "boot_diff_test.txt")
+		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Boot_image)
+		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/boot.img", ctx.Config().DeviceName()))
+		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony("soong_generated_boot_filesystem_test", diffTestFile)
+	}
+	if f.properties.Vendor_boot_image != "" {
+		diffTestFile := android.PathForModuleOut(ctx, "vendor_boot_diff_test.txt")
+		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Vendor_boot_image)
+		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/vendor_boot.img", ctx.Config().DeviceName()))
+		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony("soong_generated_vendor_boot_filesystem_test", diffTestFile)
+	}
+	if f.properties.Init_boot_image != "" {
+		diffTestFile := android.PathForModuleOut(ctx, "init_boot_diff_test.txt")
+		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Init_boot_image)
+		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/init_boot.img", ctx.Config().DeviceName()))
+		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony("soong_generated_init_boot_filesystem_test", diffTestFile)
+	}
 	ctx.Phony("soong_generated_filesystem_tests", diffTestFiles...)
 }
 
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
index 7b30264..12f9956 100644
--- a/fsgen/fsgen_mutators.go
+++ b/fsgen/fsgen_mutators.go
@@ -84,36 +84,6 @@
 	}
 }
 
-func generatedPartitions(ctx android.LoadHookContext) []string {
-	generatedPartitions := []string{"system", "ramdisk"}
-	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.DeviceConfig().BuildingUserdataImage() && ctx.DeviceConfig().UserdataPath() == "data" {
-		generatedPartitions = append(generatedPartitions, "userdata")
-	}
-	if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingSystemDlkmImage {
-		generatedPartitions = append(generatedPartitions, "system_dlkm")
-	}
-	if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingVendorDlkmImage {
-		generatedPartitions = append(generatedPartitions, "vendor_dlkm")
-	}
-	if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingOdmDlkmImage {
-		generatedPartitions = append(generatedPartitions, "odm_dlkm")
-	}
-
-	return generatedPartitions
-}
-
 func createFsGenState(ctx android.LoadHookContext, generatedPrebuiltEtcModuleNames []string, avbpubkeyGenerated bool) *FsGenState {
 	return ctx.Config().Once(fsGenStateOnceKey, func() interface{} {
 		partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
@@ -176,7 +146,8 @@
 					"fs_config_files_odm_dlkm": defaultDepCandidateProps(ctx.Config()),
 					"odm_dlkm-build.prop":      defaultDepCandidateProps(ctx.Config()),
 				},
-				"ramdisk": {},
+				"ramdisk":        {},
+				"vendor_ramdisk": {},
 			},
 			fsDepsMutex:                     sync.Mutex{},
 			moduleToInstallationProps:       map[string]installationProperties{},
diff --git a/fsgen/vbmeta_partitions.go b/fsgen/vbmeta_partitions.go
index b7fff68..11c5759 100644
--- a/fsgen/vbmeta_partitions.go
+++ b/fsgen/vbmeta_partitions.go
@@ -19,6 +19,7 @@
 	"android/soong/filesystem"
 	"slices"
 	"strconv"
+	"strings"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -153,8 +154,10 @@
 			// Already handled by a chained vbmeta partition
 			continue
 		}
-		if partitionType == "ramdisk" {
+		if strings.Contains(partitionType, "ramdisk") || strings.Contains(partitionType, "boot") {
 			// ramdisk is never signed with avb information
+			// boot partitions just have the avb footer, and don't have a corresponding vbmeta
+			// partition.
 			continue
 		}
 		partitionModules = append(partitionModules, generatedModuleNameForPartition(ctx.Config(), partitionType))
diff --git a/java/app.go b/java/app.go
index 8bb73cb..7f80160 100644
--- a/java/app.go
+++ b/java/app.go
@@ -164,7 +164,7 @@
 type overridableAppProperties struct {
 	// The name of a certificate in the default certificate directory, blank to use the default product certificate,
 	// or an android_app_certificate module name in the form ":module".
-	Certificate *string
+	Certificate proptools.Configurable[string] `android:"replace_instead_of_append"`
 
 	// Name of the signing certificate lineage file or filegroup module.
 	Lineage *string `android:"path"`
@@ -1252,7 +1252,7 @@
 	if overridden {
 		return ":" + certificate
 	}
-	return String(a.overridableAppProperties.Certificate)
+	return a.overridableAppProperties.Certificate.GetOrDefault(ctx, "")
 }
 
 func (a *AndroidApp) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
diff --git a/java/app_import.go b/java/app_import.go
index f044c68..8951c7d 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -97,7 +97,7 @@
 
 	// The name of a certificate in the default certificate directory or an android_app_certificate
 	// module name in the form ":module". Should be empty if presigned or default_dev_cert is set.
-	Certificate *string
+	Certificate proptools.Configurable[string] `android:"replace_instead_of_append"`
 
 	// Names of extra android_app_certificate modules to sign the apk with in the form ":module".
 	Additional_certificates []string
@@ -240,7 +240,7 @@
 }
 
 func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) {
-	cert := android.SrcIsModule(String(a.properties.Certificate))
+	cert := android.SrcIsModule(a.properties.Certificate.GetOrDefault(ctx, ""))
 	if cert != "" {
 		ctx.AddDependency(ctx.Module(), certificateTag, cert)
 	}
@@ -323,7 +323,7 @@
 	}
 
 	numCertPropsSet := 0
-	if String(a.properties.Certificate) != "" {
+	if a.properties.Certificate.GetOrDefault(ctx, "") != "" {
 		numCertPropsSet++
 	}
 	if Bool(a.properties.Presigned) {
@@ -406,7 +406,7 @@
 		// If the certificate property is empty at this point, default_dev_cert must be set to true.
 		// Which makes processMainCert's behavior for the empty cert string WAI.
 		_, _, certificates := collectAppDeps(ctx, a, false, false)
-		a.certificate, certificates = processMainCert(a.ModuleBase, String(a.properties.Certificate), certificates, ctx)
+		a.certificate, certificates = processMainCert(a.ModuleBase, a.properties.Certificate.GetOrDefault(ctx, ""), certificates, ctx)
 		signed := android.PathForModuleOut(ctx, "signed", apkFilename)
 		var lineageFile android.Path
 		if lineage := String(a.properties.Lineage); lineage != "" {
diff --git a/java/base.go b/java/base.go
index 8dad2d9..3bf2e23 100644
--- a/java/base.go
+++ b/java/base.go
@@ -714,10 +714,10 @@
 
 // helper method for java modules to set OutputFilesProvider
 func setOutputFiles(ctx android.ModuleContext, m Module) {
-	ctx.SetOutputFiles(append(android.Paths{m.outputFile}, m.extraOutputFiles...), "")
-	ctx.SetOutputFiles(android.Paths{m.outputFile}, android.DefaultDistTag)
-	ctx.SetOutputFiles(android.Paths{m.implementationAndResourcesJar}, ".jar")
-	ctx.SetOutputFiles(android.Paths{m.headerJarFile}, ".hjar")
+	ctx.SetOutputFiles(append(android.PathsIfNonNil(m.outputFile), m.extraOutputFiles...), "")
+	ctx.SetOutputFiles(android.PathsIfNonNil(m.outputFile), android.DefaultDistTag)
+	ctx.SetOutputFiles(android.PathsIfNonNil(m.implementationAndResourcesJar), ".jar")
+	ctx.SetOutputFiles(android.PathsIfNonNil(m.headerJarFile), ".hjar")
 	if m.dexer.proguardDictionary.Valid() {
 		ctx.SetOutputFiles(android.Paths{m.dexer.proguardDictionary.Path()}, ".proguard_map")
 	}
@@ -766,7 +766,8 @@
 	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
 	isJacocoAgent := ctx.ModuleName() == "jacocoagent"
 
-	if j.DirectlyInAnyApex() && !isJacocoAgent && !apexInfo.IsForPlatform() {
+	compileDex := Bool(j.dexProperties.Compile_dex) || Bool(j.properties.Installable)
+	if compileDex && !isJacocoAgent && !apexInfo.IsForPlatform() {
 		if !inList(ctx.ModuleName(), config.InstrumentFrameworkModules) {
 			return true
 		} else if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
@@ -1735,7 +1736,12 @@
 
 	completeStaticLibsImplementationJarsToCombine := completeStaticLibsImplementationJars
 
-	if j.shouldInstrument(ctx) {
+	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
+
+	// Enable dex compilation for the APEX variants, unless it is disabled explicitly
+	compileDex := Bool(j.dexProperties.Compile_dex) || Bool(j.properties.Installable)
+
+	if j.shouldInstrument(ctx) && (!ctx.Device() || compileDex) {
 		instrumentedOutputFile := j.instrument(ctx, flags, outputFile, jarName, specs)
 		completeStaticLibsImplementationJarsToCombine = depset.New(depset.PREORDER, android.Paths{instrumentedOutputFile}, nil)
 		outputFile = instrumentedOutputFile
@@ -1764,19 +1770,7 @@
 
 	j.implementationAndResourcesJar = outputFile
 
-	// Enable dex compilation for the APEX variants, unless it is disabled explicitly
-	compileDex := j.dexProperties.Compile_dex
-	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
-	if j.DirectlyInAnyApex() && !apexInfo.IsForPlatform() {
-		if compileDex == nil {
-			compileDex = proptools.BoolPtr(true)
-		}
-		if j.deviceProperties.Hostdex == nil {
-			j.deviceProperties.Hostdex = proptools.BoolPtr(true)
-		}
-	}
-
-	if ctx.Device() && (Bool(j.properties.Installable) || Bool(compileDex)) {
+	if ctx.Device() && compileDex {
 		if j.hasCode(ctx) {
 			if j.shouldInstrumentStatic(ctx) {
 				j.dexer.extraProguardFlagsFiles = append(j.dexer.extraProguardFlagsFiles,
@@ -2328,7 +2322,10 @@
 		"stable.core.platform.api.stubs",
 		"stub-annotations", "private-stub-annotations-jar",
 		"core-lambda-stubs",
-		"core-generated-annotation-stubs":
+		"core-generated-annotation-stubs",
+		// jacocoagent only uses core APIs, but has to specify a non-core sdk_version so it can use
+		// a prebuilt SDK to avoid circular dependencies when it statically included in the bootclasspath.
+		"jacocoagent":
 		return javaCore, true
 	case android.SdkPublic.DefaultJavaLibraryName():
 		return javaSdk, true
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index c778f04..375a1aa 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -83,10 +83,6 @@
 	return true
 }
 
-// Contents of bootclasspath fragments in an apex are considered to be directly in the apex, as if
-// they were listed in java_libs.
-func (b bootclasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
-
 // Contents of bootclasspath fragments require files from prebuilt apex files.
 func (b bootclasspathFragmentContentDependencyTag) RequiresFilesFromPrebuiltApex() {}
 
@@ -96,7 +92,6 @@
 var _ android.ExcludeFromVisibilityEnforcementTag = bootclasspathFragmentContentDepTag
 var _ android.ReplaceSourceWithPrebuilt = bootclasspathFragmentContentDepTag
 var _ android.SdkMemberDependencyTag = bootclasspathFragmentContentDepTag
-var _ android.CopyDirectlyInAnyApexTag = bootclasspathFragmentContentDepTag
 var _ android.RequiresFilesFromPrebuiltApexTag = bootclasspathFragmentContentDepTag
 
 func IsBootclasspathFragmentContentDepTag(tag blueprint.DependencyTag) bool {
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 8271392..2dda72b 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -578,7 +578,6 @@
 
 	rule.Build("javadoc", "javadoc")
 
-	ctx.SetOutputFiles(android.Paths{j.stubsSrcJar}, "")
 	ctx.SetOutputFiles(android.Paths{j.docZip}, ".docs.zip")
 }
 
diff --git a/java/droidstubs.go b/java/droidstubs.go
index cf3e219..bc26527 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -1011,7 +1011,7 @@
 			cmd.FlagWithOutput("--update-baseline:api-lint ", updatedBaselineOutput)
 
 			msg += fmt.Sprintf(``+
-				`3. FOR LSC ONLY: You can update the baseline by executing\n` +
+				`3. FOR LSC ONLY: You can update the baseline by executing\n`+
 				`   the following command:\n`+
 				`       (cd $ANDROID_BUILD_TOP && cp \\\n`+
 				`       "%s" \\\n`+
@@ -1374,7 +1374,7 @@
 		for _, stubType := range android.SortedKeys(stubsTypeToPrefix) {
 			tagWithPrefix := stubsTypeToPrefix[stubType] + tag
 			outputFile, err := tagToOutputFileFunc[tag](stubType)
-			if err == nil {
+			if err == nil && outputFile != nil {
 				ctx.SetOutputFiles(android.Paths{outputFile}, tagWithPrefix)
 			}
 		}
diff --git a/java/java.go b/java/java.go
index 64ef782..260d336 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2311,14 +2311,17 @@
 		case libTag:
 			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 				classPaths = append(classPaths, provider.HeaderJars...)
+				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
 			}
 		case bootClasspathTag:
 			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 				bootclassPaths = append(bootclassPaths, provider.HeaderJars...)
+				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
 			}
 		case staticLibTag:
 			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 				staticLibs = append(staticLibs, provider.HeaderJars...)
+				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
 			}
 		case systemModulesTag:
 			if sm, ok := android.OtherModuleProvider(ctx, dep, SystemModulesProvider); ok {
diff --git a/java/java_test.go b/java/java_test.go
index 54eb3e1..d415679 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -3050,6 +3050,7 @@
 		java_library {
 			name: "android.car",
 			srcs: ["android.car.java"],
+			installable: true,
 		}
 	`)
 
diff --git a/java/rro.go b/java/rro.go
index 8bb9be2..f225e1f 100644
--- a/java/rro.go
+++ b/java/rro.go
@@ -50,7 +50,7 @@
 type RuntimeResourceOverlayProperties struct {
 	// the name of a certificate in the default certificate directory or an android_app_certificate
 	// module name in the form ":module".
-	Certificate *string
+	Certificate proptools.Configurable[string] `android:"replace_instead_of_append"`
 
 	// Name of the signing certificate lineage file.
 	Lineage *string
@@ -119,7 +119,7 @@
 		r.aapt.deps(ctx, sdkDep)
 	}
 
-	cert := android.SrcIsModule(String(r.properties.Certificate))
+	cert := android.SrcIsModule(r.properties.Certificate.GetOrDefault(ctx, ""))
 	if cert != "" {
 		ctx.AddDependency(ctx.Module(), certificateTag, cert)
 	}
@@ -166,7 +166,7 @@
 
 	// Sign the built package
 	_, _, certificates := collectAppDeps(ctx, r, false, false)
-	r.certificate, certificates = processMainCert(r.ModuleBase, String(r.properties.Certificate), certificates, ctx)
+	r.certificate, certificates = processMainCert(r.ModuleBase, r.properties.Certificate.GetOrDefault(ctx, ""), certificates, ctx)
 	signed := android.PathForModuleOut(ctx, "signed", r.Name()+".apk")
 	var lineageFile android.Path
 	if lineage := String(r.properties.Lineage); lineage != "" {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index f6dfcdd..7891776 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1314,12 +1314,6 @@
 
 var _ android.InstallNeededDependencyTag = sdkLibraryComponentTag{}
 
-// To satisfy the CopyDirectlyInAnyApexTag interface. Implementation library of the sdk library
-// in an apex is considered to be directly in the apex, as if it was listed in java_libs.
-func (t sdkLibraryComponentTag) CopyDirectlyInAnyApex() {}
-
-var _ android.CopyDirectlyInAnyApexTag = implLibraryTag
-
 func (t sdkLibraryComponentTag) InstallDepNeeded() bool {
 	return t.name == "xml-permissions-file" || t.name == "impl-library"
 }
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index 608a616..3176ad9 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -218,16 +218,11 @@
 	return true
 }
 
-// Contents of system server fragments in an apex are considered to be directly in the apex, as if
-// they were listed in java_libs.
-func (systemServerClasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
-
 // Contents of system server fragments require files from prebuilt apex files.
 func (systemServerClasspathFragmentContentDependencyTag) RequiresFilesFromPrebuiltApex() {}
 
 var _ android.ReplaceSourceWithPrebuilt = systemServerClasspathFragmentContentDepTag
 var _ android.SdkMemberDependencyTag = systemServerClasspathFragmentContentDepTag
-var _ android.CopyDirectlyInAnyApexTag = systemServerClasspathFragmentContentDepTag
 var _ android.RequiresFilesFromPrebuiltApexTag = systemServerClasspathFragmentContentDepTag
 
 // The tag used for the dependency between the systemserverclasspath_fragment module and its contents.
diff --git a/java/testing.go b/java/testing.go
index 988514d..cb3245b 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -190,6 +190,7 @@
 				"//apex_available:anyapex",
 				"//apex_available:platform",
 			],
+			compile_dex: true,
 		}
 	`)),
 )
diff --git a/systemfeatures/Android.bp b/systemfeatures/Android.bp
new file mode 100644
index 0000000..a65a6b6
--- /dev/null
+++ b/systemfeatures/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-systemfeatures",
+    pkgPath: "android/soong/systemfeatures",
+    deps: [
+        "blueprint",
+        "blueprint-proptools",
+        "soong",
+        "soong-android",
+        "soong-java",
+    ],
+    srcs: ["system_features.go"],
+    testSrcs: ["system_features_test.go"],
+    pluginFor: ["soong_build"],
+}
diff --git a/systemfeatures/OWNERS b/systemfeatures/OWNERS
new file mode 100644
index 0000000..3e44806
--- /dev/null
+++ b/systemfeatures/OWNERS
@@ -0,0 +1,2 @@
+jdduke@google.com
+shayba@google.com
diff --git a/systemfeatures/system_features.go b/systemfeatures/system_features.go
new file mode 100644
index 0000000..0c1a566
--- /dev/null
+++ b/systemfeatures/system_features.go
@@ -0,0 +1,102 @@
+// 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 systemfeatures
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/genrule"
+)
+
+var (
+	pctx = android.NewPackageContext("android/soong/systemfeatures")
+)
+
+func init() {
+	registerSystemFeaturesComponents(android.InitRegistrationContext)
+}
+
+func registerSystemFeaturesComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("java_system_features_srcs", JavaSystemFeaturesSrcsFactory)
+}
+
+type javaSystemFeaturesSrcs struct {
+	android.ModuleBase
+	properties struct {
+		// The fully qualified class name for the generated code, e.g., com.android.Foo
+		Full_class_name string
+	}
+	outputFiles android.WritablePaths
+}
+
+var _ genrule.SourceFileGenerator = (*javaSystemFeaturesSrcs)(nil)
+var _ android.SourceFileProducer = (*javaSystemFeaturesSrcs)(nil)
+
+func (m *javaSystemFeaturesSrcs) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Create a file name appropriate for the given fully qualified (w/ package) class name.
+	classNameParts := strings.Split(m.properties.Full_class_name, ".")
+	outputDir := android.PathForModuleGen(ctx)
+	outputFileName := classNameParts[len(classNameParts)-1] + ".java"
+	outputFile := android.PathForModuleGen(ctx, outputFileName).OutputPath
+
+	// Collect all RELEASE_SYSTEM_FEATURE_$K:$V build flags into a list of "$K:$V" pairs.
+	var features []string
+	for k, v := range ctx.Config().ProductVariables().BuildFlags {
+		if strings.HasPrefix(k, "RELEASE_SYSTEM_FEATURE_") {
+			shortFeatureName := strings.TrimPrefix(k, "RELEASE_SYSTEM_FEATURE_")
+			features = append(features, fmt.Sprintf("%s:%s", shortFeatureName, v))
+		}
+	}
+	// Ensure sorted outputs for consistency of flag ordering in ninja outputs.
+	sort.Strings(features)
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().Text("rm -rf").Text(outputDir.String())
+	rule.Command().Text("mkdir -p").Text(outputDir.String())
+	rule.Command().
+		BuiltTool("systemfeatures-gen-tool").
+		Flag(m.properties.Full_class_name).
+		FlagForEachArg("--feature=", features).
+		FlagWithArg("--readonly=", fmt.Sprint(ctx.Config().ReleaseUseSystemFeatureBuildFlags())).
+		FlagWithOutput(" > ", outputFile)
+	rule.Build(ctx.ModuleName(), "Generating systemfeatures srcs filegroup")
+
+	m.outputFiles = append(m.outputFiles, outputFile)
+}
+
+func (m *javaSystemFeaturesSrcs) Srcs() android.Paths {
+	return m.outputFiles.Paths()
+}
+
+func (m *javaSystemFeaturesSrcs) GeneratedSourceFiles() android.Paths {
+	return m.outputFiles.Paths()
+}
+
+func (m *javaSystemFeaturesSrcs) GeneratedDeps() android.Paths {
+	return m.outputFiles.Paths()
+}
+
+func (m *javaSystemFeaturesSrcs) GeneratedHeaderDirs() android.Paths {
+	return nil
+}
+
+func JavaSystemFeaturesSrcsFactory() android.Module {
+	module := &javaSystemFeaturesSrcs{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidModule(module)
+	return module
+}
diff --git a/systemfeatures/system_features_test.go b/systemfeatures/system_features_test.go
new file mode 100644
index 0000000..558bb95
--- /dev/null
+++ b/systemfeatures/system_features_test.go
@@ -0,0 +1,51 @@
+// 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 systemfeatures
+
+import (
+	"android/soong/android"
+
+	"testing"
+)
+
+func TestJavaSystemFeaturesSrcs(t *testing.T) {
+	bp := `
+java_system_features_srcs {
+    name: "system-features-srcs",
+	full_class_name: "com.android.test.RoSystemFeatures",
+}
+`
+
+	res := android.GroupFixturePreparers(
+		android.FixtureRegisterWithContext(registerSystemFeaturesComponents),
+		android.PrepareForTestWithBuildFlag("RELEASE_USE_SYSTEM_FEATURE_BUILD_FLAGS", "true"),
+		android.PrepareForTestWithBuildFlag("RELEASE_SYSTEM_FEATURE_AUTOMOTIVE", "0"),
+		android.PrepareForTestWithBuildFlag("RELEASE_SYSTEM_FEATURE_TELEVISION", "UNAVAILABLE"),
+		android.PrepareForTestWithBuildFlag("RELEASE_SYSTEM_FEATURE_WATCH", ""),
+		android.PrepareForTestWithBuildFlag("RELEASE_NOT_SYSTEM_FEATURE_FOO", "BAR"),
+	).RunTestWithBp(t, bp)
+
+	module := res.ModuleForTests("system-features-srcs", "")
+	cmd := module.Rule("system-features-srcs").RuleParams.Command
+	android.AssertStringDoesContain(t, "Expected fully class name", cmd, " com.android.test.RoSystemFeatures ")
+	android.AssertStringDoesContain(t, "Expected readonly flag", cmd, "--readonly=true")
+	android.AssertStringDoesContain(t, "Expected AUTOMOTIVE feature flag", cmd, "--feature=AUTOMOTIVE:0 ")
+	android.AssertStringDoesContain(t, "Expected TELEVISION feature flag", cmd, "--feature=TELEVISION:UNAVAILABLE ")
+	android.AssertStringDoesContain(t, "Expected WATCH feature flag", cmd, "--feature=WATCH: ")
+	android.AssertStringDoesNotContain(t, "Unexpected FOO arg from non-system feature flag", cmd, "FOO")
+
+	systemFeaturesModule := module.Module().(*javaSystemFeaturesSrcs)
+	expectedOutputPath := "out/soong/.intermediates/system-features-srcs/gen/RoSystemFeatures.java"
+	android.AssertPathsRelativeToTopEquals(t, "Expected output file", []string{expectedOutputPath}, systemFeaturesModule.Srcs())
+}