Merge "Convert stubLibraries, soongMetricsSingleton, sdkSingleton, complianceMetadataSingleton, freezeApiSingleton and testSuiteFiles to use ModuleProxy." into main
diff --git a/android/base_module_context.go b/android/base_module_context.go
index cdee96f..5e05f54 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -53,6 +53,9 @@
 	// dependencies on the module being visited, it returns the dependency tag used for the current dependency.
 	OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag
 
+	// OtherModuleSubDir returns the string representing the variations of a module.
+	OtherModuleSubDir(m blueprint.Module) string
+
 	// OtherModuleExists returns true if a module with the specified name exists, as determined by the NameInterface
 	// passed to Context.SetNameInterface, or SimpleNameInterface if it was not called.
 	OtherModuleExists(name string) bool
@@ -284,6 +287,9 @@
 func (b *baseModuleContext) OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag {
 	return b.bp.OtherModuleDependencyTag(getWrappedModule(m))
 }
+func (b *baseModuleContext) OtherModuleSubDir(m blueprint.Module) string {
+	return b.bp.OtherModuleSubDir(getWrappedModule(m))
+}
 func (b *baseModuleContext) OtherModuleExists(name string) bool { return b.bp.OtherModuleExists(name) }
 func (b *baseModuleContext) OtherModuleDependencyVariantExists(variations []blueprint.Variation, name string) bool {
 	return b.bp.OtherModuleDependencyVariantExists(variations, name)
diff --git a/android/module_context.go b/android/module_context.go
index f279fd9..fb62e67 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -655,6 +655,7 @@
 		owner:                 owner,
 		requiresFullInstall:   requiresFullInstall,
 		fullInstallPath:       fullInstallPath,
+		variation:             m.ModuleSubDir(),
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
@@ -806,6 +807,7 @@
 		owner:               owner,
 		requiresFullInstall: m.requiresFullInstall(),
 		fullInstallPath:     fullInstallPath,
+		variation:           m.ModuleSubDir(),
 	})
 
 	return fullInstallPath
@@ -856,6 +858,7 @@
 		owner:               owner,
 		requiresFullInstall: m.requiresFullInstall(),
 		fullInstallPath:     fullInstallPath,
+		variation:           m.ModuleSubDir(),
 	})
 
 	return fullInstallPath
diff --git a/android/packaging.go b/android/packaging.go
index 4e0c74a..6146f02 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -72,6 +72,9 @@
 	// tools that want to interact with these files outside of the build. You should not use it
 	// inside of the build. Will be nil if this module doesn't require a "full install".
 	fullInstallPath InstallPath
+
+	// String representation of the variation of the module where this packaging spec is output of
+	variation string
 }
 
 type packagingSpecGob struct {
@@ -86,6 +89,15 @@
 	ArchType              ArchType
 	Overrides             []string
 	Owner                 string
+	Variation             string
+}
+
+func (p *PackagingSpec) Owner() string {
+	return p.owner
+}
+
+func (p *PackagingSpec) Variation() string {
+	return p.variation
 }
 
 func (p *PackagingSpec) ToGob() *packagingSpecGob {
@@ -101,6 +113,7 @@
 		ArchType:              p.archType,
 		Overrides:             p.overrides.ToSlice(),
 		Owner:                 p.owner,
+		Variation:             p.variation,
 	}
 }
 
@@ -116,6 +129,7 @@
 	p.archType = data.ArchType
 	p.overrides = uniquelist.Make(data.Overrides)
 	p.owner = data.Owner
+	p.variation = data.Variation
 }
 
 func (p *PackagingSpec) GobEncode() ([]byte, error) {
diff --git a/android/plugin.go b/android/plugin.go
index d62fc94..4348f14 100644
--- a/android/plugin.go
+++ b/android/plugin.go
@@ -135,6 +135,6 @@
 		disallowedPlugins[name] = true
 	})
 	if len(disallowedPlugins) > 0 {
-		ctx.Errorf("New plugins are not supported; however %q were found. Please reach out to the build team or use BUILD_BROKEN_PLUGIN_VALIDATION (see Changes.md for more info).", SortedStringKeys(disallowedPlugins))
+		ctx.Errorf("New plugins are not supported; however %q were found. Please reach out to the build team or use BUILD_BROKEN_PLUGIN_VALIDATION (see Changes.md for more info).", SortedKeys(disallowedPlugins))
 	}
 }
diff --git a/android/util.go b/android/util.go
index 8591cc6..7b305b5 100644
--- a/android/util.go
+++ b/android/util.go
@@ -102,13 +102,6 @@
 	return buf.String()
 }
 
-// SortedStringKeys returns the keys of the given map in the ascending order.
-//
-// Deprecated: Use SortedKeys instead.
-func SortedStringKeys[V any](m map[string]V) []string {
-	return SortedKeys(m)
-}
-
 // SortedKeys returns the keys of the given map in the ascending order.
 func SortedKeys[T cmp.Ordered, V any](m map[T]V) []T {
 	if len(m) == 0 {
diff --git a/cc/afdo.go b/cc/afdo.go
index 828e494..8d8341e 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -96,8 +96,10 @@
 		flags.Local.CFlags = append([]string{"-funique-internal-linkage-names"}, flags.Local.CFlags...)
 		// Flags for Flow Sensitive AutoFDO
 		flags.Local.CFlags = append([]string{"-mllvm", "-enable-fs-discriminator=true"}, flags.Local.CFlags...)
+		flags.Local.LdFlags = append([]string{"-Wl,-mllvm,-enable-fs-discriminator=true"}, flags.Local.LdFlags...)
 		// TODO(b/266595187): Remove the following feature once it is enabled in LLVM by default.
 		flags.Local.CFlags = append([]string{"-mllvm", "-improved-fs-discriminator=true"}, flags.Local.CFlags...)
+		flags.Local.LdFlags = append([]string{"-Wl,-mllvm,-improved-fs-discriminator=true"}, flags.Local.LdFlags...)
 	}
 	if fdoProfilePath := getFdoProfilePathFromDep(ctx); fdoProfilePath != "" {
 		// The flags are prepended to allow overriding.
diff --git a/cc/cc.go b/cc/cc.go
index 4bac0d1..0e70d48 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -4046,15 +4046,6 @@
 		return ret
 	}
 
-	// Special case for modules that are configured to be installed to /data, which includes
-	// test modules. For these modules, both APEX and non-APEX variants are considered as
-	// installable. This is because even the APEX variants won't be included in the APEX, but
-	// will anyway be installed to /data/*.
-	// See b/146995717
-	if c.InstallInData() {
-		return ret
-	}
-
 	return false
 }
 
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 7240ea5..2c06924 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2689,7 +2689,7 @@
 	cppOnly := []string{"-fPIC", "${config.CommonGlobalCppflags}", "${config.DeviceGlobalCppflags}", "${config.ArmCppflags}"}
 
 	cflags := []string{"-Werror", "-std=candcpp"}
-	cstd := []string{"-std=gnu23", "-std=conly"}
+	cstd := []string{"-std=gnu17", "-std=conly"}
 	cppstd := []string{"-std=gnu++20", "-std=cpp", "-fno-rtti"}
 
 	lastNDKFlags := []string{
diff --git a/cc/config/global.go b/cc/config/global.go
index 5011acd..7bea124 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -375,7 +375,7 @@
 		"-w",
 	}
 
-	CStdVersion               = "gnu23"
+	CStdVersion               = "gnu17"
 	CppStdVersion             = "gnu++20"
 	ExperimentalCStdVersion   = "gnu2x"
 	ExperimentalCppStdVersion = "gnu++2b"
diff --git a/ci_tests/Android.bp b/ci_tests/Android.bp
new file mode 100644
index 0000000..181ded4
--- /dev/null
+++ b/ci_tests/Android.bp
@@ -0,0 +1,21 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-ci-tests",
+    pkgPath: "android/soong/ci_tests",
+    deps: [
+        "blueprint",
+        "blueprint-proptools",
+        "soong",
+        "soong-android",
+    ],
+    srcs: [
+        "ci_test_package_zip.go",
+    ],
+    testSrcs: [
+    ],
+    pluginFor: ["soong_build"],
+    visibility: ["//visibility:public"],
+}
diff --git a/ci_tests/ci_test_package_zip.go b/ci_tests/ci_test_package_zip.go
new file mode 100644
index 0000000..d9573a3
--- /dev/null
+++ b/ci_tests/ci_test_package_zip.go
@@ -0,0 +1,237 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ci_tests
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"android/soong/android"
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	pctx.Import("android/soong/android")
+	registerTestPackageZipBuildComponents(android.InitRegistrationContext)
+}
+
+func registerTestPackageZipBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("test_package", TestPackageZipFactory)
+}
+
+type testPackageZip struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+
+	properties CITestPackageProperties
+
+	output android.Path
+}
+
+type CITestPackageProperties struct {
+	// test modules will be added as dependencies using the device os and the common architecture's variant.
+	Tests proptools.Configurable[[]string] `android:"arch_variant"`
+	// test modules that will be added as dependencies based on the first supported arch variant and the device os variant
+	Device_first_tests proptools.Configurable[[]string] `android:"arch_variant"`
+	// test modules that will be added as dependencies based on both 32bit and 64bit arch variant and the device os variant
+	Device_both_tests proptools.Configurable[[]string] `android:"arch_variant"`
+	// test modules that will be added as dependencies based on host
+	Host_tests proptools.Configurable[[]string] `android:"arch_variant"`
+	// git-main only test modules. Will only be added as dependencies using the device os and the common architecture's variant if exists.
+	Tests_if_exist_common proptools.Configurable[[]string] `android:"arch_variant"`
+	// git-main only test modules. Will only be added as dependencies based on both 32bit and 64bit arch variant and the device os variant if exists.
+	Tests_if_exist_device_both proptools.Configurable[[]string] `android:"arch_variant"`
+}
+
+type testPackageZipDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+var testPackageZipDepTag testPackageZipDepTagType
+
+var (
+	pctx = android.NewPackageContext("android/soong/ci_tests")
+	// test_package module type should only be used for the following modules.
+	// TODO: remove "_soong" from the module names inside when eliminating the corresponding make modules
+	moduleNamesAllowed = []string{"continuous_native_tests_soong", "continuous_instrumentation_tests_soong", "platform_tests"}
+)
+
+func (p *testPackageZip) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// adding tests property deps
+	for _, t := range p.properties.Tests.GetOrDefault(ctx, nil) {
+		ctx.AddVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), testPackageZipDepTag, t)
+	}
+
+	// adding device_first_tests property deps
+	for _, t := range p.properties.Device_first_tests.GetOrDefault(ctx, nil) {
+		ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), testPackageZipDepTag, t)
+	}
+
+	// adding device_both_tests property deps
+	p.addDeviceBothDeps(ctx, false)
+
+	// adding host_tests property deps
+	for _, t := range p.properties.Host_tests.GetOrDefault(ctx, nil) {
+		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), testPackageZipDepTag, t)
+	}
+
+	// adding Tests_if_exist_* property deps
+	for _, t := range p.properties.Tests_if_exist_common.GetOrDefault(ctx, nil) {
+		if ctx.OtherModuleExists(t) {
+			ctx.AddVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), testPackageZipDepTag, t)
+		}
+	}
+	p.addDeviceBothDeps(ctx, true)
+}
+
+func (p *testPackageZip) addDeviceBothDeps(ctx android.BottomUpMutatorContext, checkIfExist bool) {
+	android32TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib32")
+	android64TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib64")
+	if len(android32TargetList) > 0 {
+		maybeAndroid32Target := &android32TargetList[0]
+		if checkIfExist {
+			for _, t := range p.properties.Tests_if_exist_device_both.GetOrDefault(ctx, nil) {
+				if ctx.OtherModuleExists(t) {
+					ctx.AddFarVariationDependencies(maybeAndroid32Target.Variations(), testPackageZipDepTag, t)
+				}
+			}
+		} else {
+			ctx.AddFarVariationDependencies(maybeAndroid32Target.Variations(), testPackageZipDepTag, p.properties.Device_both_tests.GetOrDefault(ctx, nil)...)
+		}
+	}
+	if len(android64TargetList) > 0 {
+		maybeAndroid64Target := &android64TargetList[0]
+		if checkIfExist {
+			for _, t := range p.properties.Tests_if_exist_device_both.GetOrDefault(ctx, nil) {
+				if ctx.OtherModuleExists(t) {
+					ctx.AddFarVariationDependencies(maybeAndroid64Target.Variations(), testPackageZipDepTag, t)
+				}
+			}
+		} else {
+			ctx.AddFarVariationDependencies(maybeAndroid64Target.Variations(), testPackageZipDepTag, p.properties.Device_both_tests.GetOrDefault(ctx, nil)...)
+		}
+	}
+}
+
+func TestPackageZipFactory() android.Module {
+	module := &testPackageZip{}
+
+	module.AddProperties(&module.properties)
+
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+
+	return module
+}
+
+func (p *testPackageZip) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if !android.InList(ctx.ModuleName(), moduleNamesAllowed) {
+		ctx.ModuleErrorf("%s is not allowed to use module type test_package")
+	}
+
+	p.output = createOutput(ctx, pctx)
+
+	ctx.SetOutputFiles(android.Paths{p.output}, "")
+
+	// dist the test output
+	if ctx.ModuleName() == "platform_tests_soong" {
+		distedName := ctx.Config().Getenv("TARGET_PRODUCT") + "-tests-" + ctx.Config().BuildId() + ".zip"
+		ctx.DistForGoalsWithFilename([]string{"droid", "platform_tests"}, p.output, distedName)
+	}
+}
+
+func createOutput(ctx android.ModuleContext, pctx android.PackageContext) android.ModuleOutPath {
+	productOut := filepath.Join(ctx.Config().OutDir(), "target", "product", ctx.Config().DeviceName())
+	stagingDir := android.PathForModuleOut(ctx, "STAGING")
+	productVariables := ctx.Config().ProductVariables()
+	arch := proptools.String(productVariables.DeviceArch)
+	secondArch := proptools.String(productVariables.DeviceSecondaryArch)
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Text("rm").Flag("-rf").Text(stagingDir.String())
+	builder.Command().Text("mkdir").Flag("-p").Output(stagingDir)
+	builder.Temporary(stagingDir)
+	ctx.VisitDirectDepsWithTag(testPackageZipDepTag, func(m android.Module) {
+		info, ok := android.OtherModuleProvider(ctx, m, android.ModuleInfoJSONProvider)
+		if !ok {
+			ctx.OtherModuleErrorf(m, "doesn't set ModuleInfoJSON provider")
+		} else if len(info) != 1 {
+			ctx.OtherModuleErrorf(m, "doesn't provide exactly one ModuleInfoJSON")
+		}
+
+		classes := info[0].GetClass()
+		if len(info[0].Class) != 1 {
+			ctx.OtherModuleErrorf(m, "doesn't have exactly one class in its ModuleInfoJSON")
+		}
+		class := strings.ToLower(classes[0])
+		if class == "apps" {
+			class = "app"
+		} else if class == "java_libraries" {
+			class = "framework"
+		}
+
+		installedFilesInfo, ok := android.OtherModuleProvider(ctx, m, android.InstallFilesProvider)
+		if !ok {
+			ctx.ModuleErrorf("Module %s doesn't set InstallFilesProvider", m.Name())
+		}
+
+		for _, installedFile := range installedFilesInfo.InstallFiles {
+			name := removeFileExtension(installedFile.Base())
+			f := strings.TrimPrefix(installedFile.String(), productOut+"/")
+			if strings.HasPrefix(f, "out") {
+				continue
+			}
+			f = strings.ReplaceAll(f, "system/", "DATA/")
+			f = strings.ReplaceAll(f, filepath.Join("testcases", name, arch), filepath.Join("DATA", class, name))
+			f = strings.ReplaceAll(f, filepath.Join("testcases", name, secondArch), filepath.Join("DATA", class, name))
+			f = strings.ReplaceAll(f, "testcases", filepath.Join("DATA", class))
+			f = strings.ReplaceAll(f, "data/", "DATA/")
+			f = strings.ReplaceAll(f, "DATA_other", "system_other")
+			f = strings.ReplaceAll(f, "system_other/DATA", "system_other/system")
+			dir := filepath.Dir(f)
+			tempOut := android.PathForModuleOut(ctx, "STAGING", f)
+			builder.Command().Text("mkdir").Flag("-p").Text(filepath.Join(stagingDir.String(), dir))
+			builder.Command().Text("cp").Flag("-Rf").Input(installedFile).Output(tempOut)
+			builder.Temporary(tempOut)
+		}
+	})
+
+	output := android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
+	builder.Command().
+		BuiltTool("soong_zip").
+		Flag("-o").Output(output).
+		Flag("-C").Text(stagingDir.String()).
+		Flag("-D").Text(stagingDir.String())
+	builder.Command().Text("rm").Flag("-rf").Text(stagingDir.String())
+	builder.Build("test_package", fmt.Sprintf("build test_package for %s", ctx.ModuleName()))
+	return output
+}
+
+func removeFileExtension(filename string) string {
+	return strings.TrimSuffix(filename, filepath.Ext(filename))
+}
+
+// The only purpose of this method is to make sure we can build the module directly
+// without adding suffix "-soong"
+func (p *testPackageZip) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{
+		android.AndroidMkEntries{
+			Class:      "ETC",
+			OutputFile: android.OptionalPathForPath(p.output),
+		},
+	}
+}
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index 4f6f983..4ee5681 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -15,7 +15,9 @@
 package filesystem
 
 import (
+	"cmp"
 	"fmt"
+	"slices"
 	"strings"
 	"sync/atomic"
 
@@ -247,6 +249,38 @@
 	a.distFiles(ctx)
 }
 
+// Returns a list of modules that are installed, which are collected from the dependency
+// filesystem and super_image modules.
+func (a *androidDevice) allInstalledModules(ctx android.ModuleContext) []android.Module {
+	fsInfoMap := a.getFsInfos(ctx)
+	allOwners := make(map[string][]string)
+	for _, partition := range android.SortedKeys(fsInfoMap) {
+		fsInfo := fsInfoMap[partition]
+		for _, owner := range fsInfo.Owners {
+			allOwners[owner.Name] = append(allOwners[owner.Name], owner.Variation)
+		}
+	}
+
+	ret := []android.Module{}
+	ctx.WalkDepsProxy(func(mod, _ android.ModuleProxy) bool {
+		if variations, ok := allOwners[mod.Name()]; ok && android.InList(ctx.OtherModuleSubDir(mod), variations) {
+			ret = append(ret, mod)
+		}
+		return true
+	})
+
+	// Remove duplicates
+	ret = android.FirstUniqueFunc(ret, func(a, b android.Module) bool {
+		return a.String() == b.String()
+	})
+
+	// Sort the modules by their names and variants
+	slices.SortFunc(ret, func(a, b android.Module) int {
+		return cmp.Compare(a.String(), b.String())
+	})
+	return ret
+}
+
 func (a *androidDevice) distFiles(ctx android.ModuleContext) {
 	if !ctx.Config().KatiEnabled() {
 		if proptools.Bool(a.deviceProps.Main_device) {
@@ -317,7 +351,7 @@
 	if a.partitionProps.Super_partition_name != nil {
 		superPartition := ctx.GetDirectDepProxyWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
 		if info, ok := android.OtherModuleProvider(ctx, superPartition, SuperImageProvider); ok {
-			for _, partition := range android.SortedStringKeys(info.SubImageInfo) {
+			for _, partition := range android.SortedKeys(info.SubImageInfo) {
 				filesystemsToCopy = append(
 					filesystemsToCopy,
 					targetFilesystemZipCopy{info.SubImageInfo[partition], strings.ToUpper(partition)},
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index c0fb636..1ce6131 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -384,6 +384,11 @@
 	Json android.Path
 }
 
+type InstalledModuleInfo struct {
+	Name      string
+	Variation string
+}
+
 type FilesystemInfo struct {
 	// The built filesystem image
 	Output android.Path
@@ -434,6 +439,8 @@
 	SelinuxFc android.Path
 
 	FilesystemConfig android.Path
+
+	Owners []InstalledModuleInfo
 }
 
 // FullInstallPathInfo contains information about the "full install" paths of all the files
@@ -593,7 +600,8 @@
 	specs := f.gatherFilteredPackagingSpecs(ctx)
 
 	var fullInstallPaths []FullInstallPathInfo
-	for _, spec := range specs {
+	for _, specRel := range android.SortedKeys(specs) {
+		spec := specs[specRel]
 		fullInstallPaths = append(fullInstallPaths, FullInstallPathInfo{
 			FullInstallPath:     spec.FullInstallPath(),
 			RequiresFullInstall: spec.RequiresFullInstall(),
@@ -682,6 +690,7 @@
 		ErofsCompressHints: erofsCompressHints,
 		SelinuxFc:          f.selinuxFc,
 		FilesystemConfig:   f.generateFilesystemConfig(ctx, rootDir, rebasedDir),
+		Owners:             f.gatherOwners(specs),
 	}
 
 	android.SetProvider(ctx, FilesystemProvider, fsInfo)
@@ -1335,6 +1344,18 @@
 	return f.PackagingBase.GatherPackagingSpecsWithFilterAndModifier(ctx, f.filesystemBuilder.FilterPackagingSpec, f.filesystemBuilder.ModifyPackagingSpec)
 }
 
+func (f *filesystem) gatherOwners(specs map[string]android.PackagingSpec) []InstalledModuleInfo {
+	var owners []InstalledModuleInfo
+	for _, p := range android.SortedKeys(specs) {
+		spec := specs[p]
+		owners = append(owners, InstalledModuleInfo{
+			Name:      spec.Owner(),
+			Variation: spec.Variation(),
+		})
+	}
+	return owners
+}
+
 // Dexpreopt files are installed to system_other. Collect the packaingSpecs for the dexpreopt files
 // from this partition to export to the system_other partition later.
 func (f *filesystem) systemOtherFiles(ctx android.ModuleContext) map[string]android.PackagingSpec {
diff --git a/filesystem/system_other.go b/filesystem/system_other.go
index 1c00dd3..5309e90 100644
--- a/filesystem/system_other.go
+++ b/filesystem/system_other.go
@@ -23,6 +23,12 @@
 	"github.com/google/blueprint/proptools"
 )
 
+var (
+	systemOtherPropFileTweaks = pctx.AndroidStaticRule("system_other_prop_file_tweaks", blueprint.RuleParams{
+		Command: `rm -rf $out && sed -e 's@^mount_point=/$$@mount_point=system_other@g' -e 's@^partition_name=system$$@partition_name=system_other@g' $in > $out`,
+	})
+)
+
 type SystemOtherImageProperties struct {
 	// The system_other image always requires a reference to the system image. The system_other
 	// partition gets built into the system partition's "b" slot in a/b partition builds. Thus, it
@@ -123,12 +129,23 @@
 	fec := ctx.Config().HostToolPath(ctx, "fec")
 	pathToolDirs := []string{filepath.Dir(fec.String())}
 
+	// In make, the exact same prop file is used for both system and system_other. However, I
+	// believe make goes through a different build_image code path that is based on the name of
+	// the output file. So it sees the output file is named system_other.img and makes some changes.
+	// We don't use that codepath, so make the changes manually to the prop file.
+	propFile := android.PathForModuleOut(ctx, "prop")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   systemOtherPropFileTweaks,
+		Input:  systemInfo.BuildImagePropFile,
+		Output: propFile,
+	})
+
 	builder = android.NewRuleBuilder(pctx, ctx)
 	builder.Command().
 		Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")).
 		BuiltTool("build_image").
 		Text(stagingDir.String()). // input directory
-		Input(systemInfo.BuildImagePropFile).
+		Input(propFile).
 		Implicits(systemInfo.BuildImagePropFileDeps).
 		Implicit(fec).
 		Implicit(stagingDirTimestamp).
@@ -140,7 +157,7 @@
 	// Create a hermetic system_other.img with pinned timestamps
 	builder = android.NewRuleBuilder(pctx, ctx)
 	outputHermetic := android.PathForModuleOut(ctx, "for_target_files", "system_other.img")
-	outputHermeticPropFile := m.propFileForHermeticImg(ctx, builder, systemInfo.BuildImagePropFile)
+	outputHermeticPropFile := m.propFileForHermeticImg(ctx, builder, propFile)
 	builder.Command().
 		Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")).
 		BuiltTool("build_image").
diff --git a/rust/library.go b/rust/library.go
index 7f5861f..415785a 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -744,10 +744,16 @@
 		}
 		if library.rlib() {
 			ccExporter.RustRlibDeps = append(ccExporter.RustRlibDeps, deps.reexportedCcRlibDeps...)
+			ccExporter.RustRlibDeps = append(ccExporter.RustRlibDeps, deps.reexportedWholeCcRlibDeps...)
 		}
 		android.SetProvider(ctx, cc.FlagExporterInfoProvider, ccExporter)
 	}
 
+	if library.dylib() {
+		// reexport whole-static'd dependencies for dylibs.
+		library.flagExporter.wholeRustRlibDeps = append(library.flagExporter.wholeRustRlibDeps, deps.reexportedWholeCcRlibDeps...)
+	}
+
 	if library.shared() || library.stubs() {
 		// Optimize out relinking against shared libraries whose interface hasn't changed by
 		// depending on a table of contents file instead of the library itself.
diff --git a/rust/rust.go b/rust/rust.go
index 713cacc..4eebda3 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -496,8 +496,9 @@
 	depLinkFlags []string
 
 	// track cc static-libs that have Rlib dependencies
-	reexportedCcRlibDeps []cc.RustRlibDep
-	ccRlibDeps           []cc.RustRlibDep
+	reexportedCcRlibDeps      []cc.RustRlibDep
+	reexportedWholeCcRlibDeps []cc.RustRlibDep
+	ccRlibDeps                []cc.RustRlibDep
 
 	// linkDirs are link paths passed via -L to rustc. linkObjects are objects passed directly to the linker
 	// Both of these are exported and propagate to dependencies.
@@ -555,6 +556,7 @@
 	staticLibObjects      []string
 	sharedLibObjects      []string
 	wholeStaticLibObjects []string
+	wholeRustRlibDeps     []cc.RustRlibDep
 }
 
 func (flagExporter *flagExporter) exportLinkDirs(dirs ...string) {
@@ -584,6 +586,7 @@
 		StaticLibObjects:      flagExporter.staticLibObjects,
 		WholeStaticLibObjects: flagExporter.wholeStaticLibObjects,
 		SharedLibPaths:        flagExporter.sharedLibObjects,
+		WholeRustRlibDeps:     flagExporter.wholeRustRlibDeps,
 	})
 }
 
@@ -600,6 +603,7 @@
 	StaticLibObjects      []string
 	WholeStaticLibObjects []string
 	SharedLibPaths        []string
+	WholeRustRlibDeps     []cc.RustRlibDep
 }
 
 var RustFlagExporterInfoProvider = blueprint.NewProvider[RustFlagExporterInfo]()
@@ -1585,8 +1589,8 @@
 				directSrcProvidersDeps = append(directSrcProvidersDeps, &dep)
 			}
 
+			exportedRustInfo, _ := android.OtherModuleProvider(ctx, dep, RustFlagExporterInfoProvider)
 			exportedInfo, _ := android.OtherModuleProvider(ctx, dep, RustFlagExporterInfoProvider)
-
 			//Append the dependencies exported objects, except for proc-macros which target a different arch/OS
 			if depTag != procMacroDepTag {
 				depPaths.depFlags = append(depPaths.depFlags, exportedInfo.Flags...)
@@ -1595,6 +1599,11 @@
 				depPaths.staticLibObjects = append(depPaths.staticLibObjects, exportedInfo.StaticLibObjects...)
 				depPaths.wholeStaticLibObjects = append(depPaths.wholeStaticLibObjects, exportedInfo.WholeStaticLibObjects...)
 				depPaths.linkDirs = append(depPaths.linkDirs, exportedInfo.LinkDirs...)
+
+				depPaths.reexportedWholeCcRlibDeps = append(depPaths.reexportedWholeCcRlibDeps, exportedRustInfo.WholeRustRlibDeps...)
+				if !mod.Rlib() {
+					depPaths.ccRlibDeps = append(depPaths.ccRlibDeps, exportedRustInfo.WholeRustRlibDeps...)
+				}
 			}
 
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
@@ -1656,17 +1665,26 @@
 					}
 				}
 
+				exportedInfo, _ := android.OtherModuleProvider(ctx, dep, cc.FlagExporterInfoProvider)
 				if cc.IsWholeStaticLib(depTag) {
 					// Add whole staticlibs to wholeStaticLibObjects to propagate to Rust all dependents.
 					depPaths.wholeStaticLibObjects = append(depPaths.wholeStaticLibObjects, ccLibPath.String())
+
+					// We also propagate forward whole-static'd cc staticlibs with rust_ffi_rlib dependencies
+					// We don't need to check a hypothetical exportedRustInfo.WholeRustRlibDeps because we
+					// wouldn't expect a rust_ffi_rlib to be listed in `static_libs` (Soong explicitly disallows this)
+					depPaths.reexportedWholeCcRlibDeps = append(depPaths.reexportedWholeCcRlibDeps, exportedInfo.RustRlibDeps...)
 				} else {
-					// Otherwise add to staticLibObjects, which only propagate through rlibs to their dependents.
+					// If not whole_static, add to staticLibObjects, which only propagate through rlibs to their dependents.
 					depPaths.staticLibObjects = append(depPaths.staticLibObjects, ccLibPath.String())
+
+					if mod.Rlib() {
+						// rlibs propagate their inherited rust_ffi_rlibs forward.
+						depPaths.reexportedCcRlibDeps = append(depPaths.reexportedCcRlibDeps, exportedInfo.RustRlibDeps...)
+					}
 				}
 
 				depPaths.linkDirs = append(depPaths.linkDirs, linkPath)
-
-				exportedInfo, _ := android.OtherModuleProvider(ctx, dep, cc.FlagExporterInfoProvider)
 				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
 				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
 				depPaths.depClangFlags = append(depPaths.depClangFlags, exportedInfo.Flags...)
@@ -1675,8 +1693,6 @@
 				if !mod.Rlib() {
 					// rlibs don't need to build the generated static library, so they don't need to track these.
 					depPaths.ccRlibDeps = append(depPaths.ccRlibDeps, exportedInfo.RustRlibDeps...)
-				} else {
-					depPaths.reexportedCcRlibDeps = append(depPaths.reexportedCcRlibDeps, exportedInfo.RustRlibDeps...)
 				}
 
 				directStaticLibDeps = append(directStaticLibDeps, linkableInfo)
@@ -1835,6 +1851,7 @@
 	depPaths.depSystemIncludePaths = android.FirstUniquePaths(depPaths.depSystemIncludePaths)
 	depPaths.depLinkFlags = android.FirstUniqueStrings(depPaths.depLinkFlags)
 	depPaths.reexportedCcRlibDeps = android.FirstUniqueFunc(depPaths.reexportedCcRlibDeps, cc.EqRustRlibDeps)
+	depPaths.reexportedWholeCcRlibDeps = android.FirstUniqueFunc(depPaths.reexportedWholeCcRlibDeps, cc.EqRustRlibDeps)
 	depPaths.ccRlibDeps = android.FirstUniqueFunc(depPaths.ccRlibDeps, cc.EqRustRlibDeps)
 
 	return depPaths
diff --git a/rust/rust_test.go b/rust/rust_test.go
index fbb9947..f634bb5 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -456,6 +456,13 @@
 		}
 
 		rust_ffi_static {
+			name: "libfoo_from_rlib_whole",
+			crate_name: "foo_from_rlib_whole",
+			srcs: ["src/lib.rs"],
+			export_include_dirs: ["foo_includes"]
+		}
+
+		rust_ffi_static {
 			name: "libbuzz",
 			crate_name: "buzz",
 			srcs: ["src/lib.rs"],
@@ -469,6 +476,13 @@
 			export_include_dirs: ["buzz_includes"]
 		}
 
+		rust_ffi_static {
+			name: "libbuzz_from_rlib_whole",
+			crate_name: "buzz_from_rlib_whole",
+			srcs: ["src/lib.rs"],
+			export_include_dirs: ["buzz_includes"]
+		}
+
 		cc_library_shared {
 			name: "libcc_shared",
 			srcs:["foo.c"],
@@ -489,6 +503,13 @@
 			whole_static_libs: ["libfoo_from_rlib"],
 		}
 
+		cc_library_static {
+			name: "libcc_whole_static_from_rlib",
+			srcs:["foo.c"],
+			static_libs: ["libbuzz_from_rlib_whole"],
+			whole_static_libs: ["libfoo_from_rlib_whole"],
+		}
+
 		cc_binary {
 			name: "ccBin",
 			srcs:["foo.c"],
@@ -500,6 +521,14 @@
 			srcs:["src/foo.rs"],
 			crate_name: "rs",
 			static_libs: ["libcc_static_from_rlib"],
+			whole_static_libs: ["libcc_whole_static_from_rlib"],
+		}
+
+		rust_library {
+			name: "librs2",
+			srcs:["src/foo.rs"],
+			crate_name: "rs",
+			rustlibs: ["librs"],
 		}
 
 		rust_binary {
@@ -509,7 +538,7 @@
 			rlibs: ["librs", "libbar"],
 			static_libs: ["libcc_static"],
 		}
-		`)
+	`)
 
 	libbar := ctx.ModuleForTests(t, "libbar", "android_arm64_armv8-a_rlib_rlib-std").Rule("rustc")
 	libcc_shared_rustc := ctx.ModuleForTests(t, "libcc_shared", "android_arm64_armv8-a_shared").Rule("rustc")
@@ -521,6 +550,10 @@
 	rustbin_genlib := ctx.ModuleForTests(t, "rsBin", "android_arm64_armv8-a").Output("generated_rust_staticlib/librustlibs.a")
 	rustbin := ctx.ModuleForTests(t, "rsBin", "android_arm64_armv8-a").Output("unstripped/rsBin")
 	librs_rlib := ctx.ModuleForTests(t, "librs", "android_arm64_armv8-a_rlib_dylib-std").MaybeOutput("generated_rust_staticlib/librustlibs.a")
+	librs2_rlib := ctx.ModuleForTests(t, "librs2", "android_arm64_armv8-a_rlib_dylib-std").MaybeOutput("generated_rust_staticlib/librustlibs.a")
+	librs_genlib := ctx.ModuleForTests(t, "librs", "android_arm64_armv8-a_dylib").Output("generated_rust_staticlib/librustlibs.a")
+	librs2_genlib := ctx.ModuleForTests(t, "librs2", "android_arm64_armv8-a_dylib").Output("generated_rust_staticlib/librustlibs.a")
+	librs2_dylib := ctx.ModuleForTests(t, "librs2", "android_arm64_armv8-a_dylib").Output("unstripped/librs2.dylib.so")
 
 	if !strings.Contains(libbar.Args["rustcFlags"], "crate-type=rlib") {
 		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v", "rlib", libbar.Args["rustcFlags"])
@@ -578,6 +611,10 @@
 		t.Errorf("missing generated static library in linker step libFlags in Rust module, expecting %#v, libFlags: %#v",
 			"generated_rust_staticlib/librustlibs.a", rustbin.Args["libFlags"])
 	}
+	if !strings.Contains(librs2_dylib.Args["linkFlags"], "generated_rust_staticlib/librustlibs.a") {
+		t.Errorf("missing generated static library in linker step libFlags in Rust module, expecting %#v, libFlags: %#v",
+			"generated_rust_staticlib/librustlibs.a", librs2_dylib.Args["libFlags"])
+	}
 
 	// Make sure that direct dependencies and indirect whole static dependencies are
 	// propagating correctly for the rlib -> cc_library_static -> rust_* generated library example.
@@ -610,6 +647,31 @@
 	if librs_rlib.Rule != nil {
 		t.Error("rlibs should not be generating mto staticlibs", "rlib", libbar.Args["rustcFlags"])
 	}
+	if librs2_rlib.Rule != nil {
+		t.Error("rlibs should not be generating mto staticlibs", "rlib", libbar.Args["rustcFlags"])
+	}
+
+	// Make sure that direct whole static dependencies are propagating correctly downstream
+	// foo_from_rlib_whole --(ws)--> libcc_whole_static_from_rlib --(ws)--> librs
+	if !strings.Contains(librs_genlib.Args["libFlags"], "--extern foo_from_rlib_whole=") {
+		t.Errorf("Missing direct whole_static_lib dependency libfoo_from_rlib_whole from rust dylib when writing generated Rust staticlib: %#v", librs_genlib.Args["libFlags"])
+	}
+
+	// Make sure that indirect whole static dependencies are propagating correctly downstream
+	// foo_from_rlib_whole --(ws)--> libcc_whole_static_from_rlib --(ws)--> librs --> rust_*
+	if !strings.Contains(librs2_genlib.Args["libFlags"], "--extern foo_from_rlib_whole=") {
+		t.Errorf("Missing indirect whole_static_lib dependency libfoo_from_rlib_whole from rust dylib when writing generated Rust staticlib: %#v", librs2_genlib.Args["libFlags"])
+	}
+	if !strings.Contains(rustbin_genlib.Args["libFlags"], "--extern foo_from_rlib_whole=") {
+		t.Errorf("Missing indirect whole_static_lib dependency libfoo_from_rlib_whole from rust dylib in rust binary when writing generated Rust staticlib: %#v", rustbin_genlib.Args["libFlags"])
+	}
+
+	// Make sure that normal static dependencies are not propagating through dylib dependencies
+	// buzz_from_rlib_whole --(s)--> libcc_whole_static_from_rlib --(ws)--> librs --> rust_*
+	if strings.Contains(librs2_genlib.Args["libFlags"], "--extern buzz_from_rlib_whole=") {
+		t.Errorf("dependency from indirect cc staticlib from direct dylib dep found in rust dylib when writing generated Rust staticlib: %#v", librs2_genlib.Args["libFlags"])
+	}
+
 }
 
 func assertString(t *testing.T, got, expected string) {
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
index 8c3ff29..73edbf6 100644
--- a/ui/status/ninja.go
+++ b/ui/status/ninja.go
@@ -46,6 +46,7 @@
 		forceClose: make(chan bool),
 		done:       make(chan bool),
 		cancelOpen: make(chan bool),
+		running:    make(map[uint32]*Action),
 	}
 
 	go n.run()
@@ -59,6 +60,7 @@
 	forceClose chan bool
 	done       chan bool
 	cancelOpen chan bool
+	running    map[uint32]*Action
 }
 
 const NINJA_READER_CLOSE_TIMEOUT = 5 * time.Second
@@ -68,32 +70,47 @@
 	// Signal the goroutine to stop if it is blocking opening the fifo.
 	close(n.cancelOpen)
 
+	closed := false
+
 	// Ninja should already have exited or been killed, wait 5 seconds for the FIFO to be closed and any
 	// remaining messages to be processed through the NinjaReader.run goroutine.
 	timeoutCh := time.After(NINJA_READER_CLOSE_TIMEOUT)
 	select {
 	case <-n.done:
-		return
+		closed = true
 	case <-timeoutCh:
 		// Channel is not closed yet
 	}
 
-	n.status.Error(fmt.Sprintf("ninja fifo didn't finish after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
+	if !closed {
+		n.status.Error(fmt.Sprintf("ninja fifo didn't finish after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
 
-	// Force close the reader even if the FIFO didn't close.
-	close(n.forceClose)
+		// Force close the reader even if the FIFO didn't close.
+		close(n.forceClose)
 
-	// Wait again for the reader thread to acknowledge the close before giving up and assuming it isn't going
-	// to send anything else.
-	timeoutCh = time.After(NINJA_READER_CLOSE_TIMEOUT)
-	select {
-	case <-n.done:
-		return
-	case <-timeoutCh:
-		// Channel is not closed yet
+		// Wait again for the reader thread to acknowledge the close before giving up and assuming it isn't going
+		// to send anything else.
+		timeoutCh = time.After(NINJA_READER_CLOSE_TIMEOUT)
+		select {
+		case <-n.done:
+			closed = true
+		case <-timeoutCh:
+			// Channel is not closed yet
+		}
 	}
 
-	n.status.Verbose(fmt.Sprintf("ninja fifo didn't finish even after force closing after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
+	if !closed {
+		n.status.Verbose(fmt.Sprintf("ninja fifo didn't finish even after force closing after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
+	}
+
+	err := fmt.Errorf("error: action cancelled when ninja exited")
+	for _, action := range n.running {
+		n.status.FinishAction(ActionResult{
+			Action: action,
+			Output: err.Error(),
+			Error:  err,
+		})
+	}
 }
 
 func (n *NinjaReader) run() {
@@ -125,8 +142,6 @@
 
 	r := bufio.NewReader(f)
 
-	running := map[uint32]*Action{}
-
 	msgChan := make(chan *ninja_frontend.Status)
 
 	// Read from the ninja fifo and decode the protobuf in a goroutine so the main NinjaReader.run goroutine
@@ -213,11 +228,11 @@
 				ChangedInputs: msg.EdgeStarted.ChangedInputs,
 			}
 			n.status.StartAction(action)
-			running[msg.EdgeStarted.GetId()] = action
+			n.running[msg.EdgeStarted.GetId()] = action
 		}
 		if msg.EdgeFinished != nil {
-			if started, ok := running[msg.EdgeFinished.GetId()]; ok {
-				delete(running, msg.EdgeFinished.GetId())
+			if started, ok := n.running[msg.EdgeFinished.GetId()]; ok {
+				delete(n.running, msg.EdgeFinished.GetId())
 
 				var err error
 				exitCode := int(msg.EdgeFinished.GetStatus())