Merge "Don't panic when a map is included twice" into main
diff --git a/android/aconfig_providers.go b/android/aconfig_providers.go
index 7317587..ee9891d 100644
--- a/android/aconfig_providers.go
+++ b/android/aconfig_providers.go
@@ -130,6 +130,7 @@
 			AconfigFiles: mergedAconfigFiles,
 			ModeInfos:    mergedModeInfos,
 		})
+		ctx.Module().base().aconfigFilePaths = getAconfigFilePaths(ctx.Module().base(), mergedAconfigFiles)
 	}
 }
 
diff --git a/android/makevars.go b/android/makevars.go
index b6bc14e..f57ac45 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -507,7 +507,7 @@
 		if extraFiles := install.extraFiles; extraFiles != nil {
 			fmt.Fprintf(buf, "\t( unzip -qDD -d '%s' '%s' 2>&1 | grep -v \"zipfile is empty\"; exit $${PIPESTATUS[0]} ) || \\\n", extraFiles.dir.String(), extraFiles.zip.String())
 			fmt.Fprintf(buf, "\t  ( code=$$?; if [ $$code -ne 0 -a $$code -ne 1 ]; then exit $$code; fi )\n")
-			fmt.Fprintf(buf, "EXTRA_INSTALL_ZIPS += %s:%s\n", extraFiles.dir.String(), extraFiles.zip.String())
+			fmt.Fprintf(buf, "EXTRA_INSTALL_ZIPS += %s:%s:%s\n", install.to.String(), extraFiles.dir.String(), extraFiles.zip.String())
 		}
 
 		fmt.Fprintln(buf)
diff --git a/android/module.go b/android/module.go
index 738f543..d4e4c2d 100644
--- a/android/module.go
+++ b/android/module.go
@@ -902,6 +902,9 @@
 	installedInitRcPaths         InstallPaths
 	installedVintfFragmentsPaths InstallPaths
 
+	// Merged Aconfig files for all transitive deps.
+	aconfigFilePaths Paths
+
 	// set of dependency module:location mappings used to populate the license metadata for
 	// apex containers.
 	licenseInstallMap []string
@@ -1066,7 +1069,8 @@
 		// TODO(jiyong): the Make-side does this only when the required module is a shared
 		// library or a native test.
 		bothInAndroid := ctx.Device() && target.Os.Class == Device
-		nativeArch := InList(ctx.Arch().ArchType.Multilib, []string{"lib32", "lib64"})
+		nativeArch := InList(ctx.Arch().ArchType.Multilib, []string{"lib32", "lib64"}) &&
+			InList(target.Arch.ArchType.Multilib, []string{"lib32", "lib64"})
 		sameBitness := ctx.Arch().ArchType.Multilib == target.Arch.ArchType.Multilib
 		if bothInAndroid && nativeArch && !sameBitness {
 			return
diff --git a/android/module_context.go b/android/module_context.go
index 605d3ba..3c1e30a 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -482,6 +482,10 @@
 	return m.packageFile(fullInstallPath, srcPath, false)
 }
 
+func (m *moduleContext) getAconfigPaths() *Paths {
+	return &m.module.base().aconfigFilePaths
+}
+
 func (m *moduleContext) packageFile(fullInstallPath InstallPath, srcPath Path, executable bool) PackagingSpec {
 	licenseFiles := m.Module().EffectiveLicenseFiles()
 	spec := PackagingSpec{
@@ -492,6 +496,7 @@
 		effectiveLicenseFiles: &licenseFiles,
 		partition:             fullInstallPath.partition,
 		skipInstall:           m.skipInstall(),
+		aconfigPaths:          m.getAconfigPaths(),
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
@@ -616,6 +621,7 @@
 		executable:       false,
 		partition:        fullInstallPath.partition,
 		skipInstall:      m.skipInstall(),
+		aconfigPaths:     m.getAconfigPaths(),
 	})
 
 	return fullInstallPath
@@ -658,6 +664,7 @@
 		executable:       false,
 		partition:        fullInstallPath.partition,
 		skipInstall:      m.skipInstall(),
+		aconfigPaths:     m.getAconfigPaths(),
 	})
 
 	return fullInstallPath
diff --git a/android/packaging.go b/android/packaging.go
index fe61da1..a7260a6 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -48,6 +48,9 @@
 	// is created via InstallFile or InstallSymlink) or a simple packaging (i.e. created via
 	// PackageFile).
 	skipInstall bool
+
+	// Paths of aconfig files for the built artifact
+	aconfigPaths *Paths
 }
 
 func (p *PackagingSpec) Equals(other *PackagingSpec) bool {
@@ -102,6 +105,11 @@
 	return p.skipInstall
 }
 
+// Paths of aconfig files for the built artifact
+func (p *PackagingSpec) GetAconfigPaths() Paths {
+	return *p.aconfigPaths
+}
+
 type PackageModule interface {
 	Module
 	packagingBase() *PackagingBase
diff --git a/cc/cmake_snapshot.go b/cc/cmake_snapshot.go
index 3ac7db1..0635a29 100644
--- a/cc/cmake_snapshot.go
+++ b/cc/cmake_snapshot.go
@@ -494,13 +494,30 @@
 	return nil
 }
 
+func cmakeSnapshotLoadHook(ctx android.LoadHookContext) {
+	props := struct {
+		Target struct {
+			Darwin struct {
+				Enabled *bool
+			}
+			Windows struct {
+				Enabled *bool
+			}
+		}
+	}{}
+	props.Target.Darwin.Enabled = proptools.BoolPtr(false)
+	props.Target.Windows.Enabled = proptools.BoolPtr(false)
+	ctx.AppendProperties(&props)
+}
+
 // cmake_snapshot allows defining source packages for release outside of Android build tree.
 // As a result of cmake_snapshot module build, a zip file is generated with CMake build definitions
 // for selected source modules, their dependencies and optionally also the source code itself.
 func CmakeSnapshotFactory() android.Module {
 	module := &CmakeSnapshot{}
 	module.AddProperties(&module.Properties)
-	android.InitAndroidModule(module)
+	android.AddLoadHook(module, cmakeSnapshotLoadHook)
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
 	return module
 }
 
diff --git a/cc/cmake_snapshot_test.go b/cc/cmake_snapshot_test.go
index cda187f..8fca6c1 100644
--- a/cc/cmake_snapshot_test.go
+++ b/cc/cmake_snapshot_test.go
@@ -15,6 +15,7 @@
 package cc
 
 import (
+	"runtime"
 	"strings"
 	"testing"
 
@@ -23,9 +24,12 @@
 
 func wasGenerated(t *testing.T, m *android.TestingModule, fileName string, ruleType string) {
 	t.Helper()
-	ruleName := m.Output(fileName).Rule.String()
+	ruleName := "<nil>"
+	if rule := m.MaybeOutput(fileName).Rule; rule != nil {
+		ruleName = rule.String()
+	}
 	if !strings.HasSuffix(ruleName, ruleType) {
-		t.Errorf("Main Cmake file wasn't generated, expected rule %v, found %v", ruleType, ruleName)
+		t.Errorf("Main Cmake file wasn't generated properly, expected rule %v, found %v", ruleType, ruleName)
 	}
 }
 
@@ -39,7 +43,11 @@
 			include_sources: true,
 		}`)
 
-	snapshotModule := result.ModuleForTests("foo", "")
+	if runtime.GOOS != "linux" {
+		t.Skip("CMake snapshots are only supported on Linux")
+	}
+
+	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
 
 	wasGenerated(t, &snapshotModule, "CMakeLists.txt", "rawFileCopy")
 	wasGenerated(t, &snapshotModule, "foo.zip", "")
@@ -63,7 +71,45 @@
 			include_sources: true,
 		}`)
 
-	snapshotModule := result.ModuleForTests("foo", "")
+	if runtime.GOOS != "linux" {
+		t.Skip("CMake snapshots are only supported on Linux")
+	}
+
+	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
 
 	wasGenerated(t, &snapshotModule, "some/module/CMakeLists.txt", "rawFileCopy")
 }
+
+func TestCmakeSnapshotAsTestData(t *testing.T) {
+	t.Parallel()
+	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
+		cc_test {
+			name: "foo_test",
+			gtest: false,
+			srcs: [
+				"foo_test.c",
+			],
+			data: [
+				":foo",
+			],
+			target: {
+				android: {enabled: false},
+			},
+		}
+
+		cc_cmake_snapshot {
+			name: "foo",
+			modules: [],
+			prebuilts: ["libc++"],
+			include_sources: true,
+		}`)
+
+	if runtime.GOOS != "linux" {
+		t.Skip("CMake snapshots are only supported on Linux")
+	}
+
+	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
+
+	wasGenerated(t, &snapshotModule, "CMakeLists.txt", "rawFileCopy")
+	wasGenerated(t, &snapshotModule, "foo.zip", "")
+}
diff --git a/cc/library.go b/cc/library.go
index 12ecc13..895b199 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1490,6 +1490,11 @@
 					fileName, nameExt, isLlndk)
 			}
 		}
+		// Ensure that a module tagged with only platformLsdumpTag has ref_dump_dirs.
+		// Android.bp in vendor projects should be cleaned up before this is enforced for vendorLsdumpTag and productLsdumpTag.
+		if len(headerAbiChecker.Ref_dump_dirs) == 0 && len(tags) == 1 && tags[0] == platformLsdumpTag {
+			ctx.ModuleErrorf("header_abi_checker is explicitly enabled, but no ref_dump_dirs are specified.")
+		}
 		// Check against the opt-in reference dumps.
 		for i, optInDumpDir := range headerAbiChecker.Ref_dump_dirs {
 			optInDumpDirPath := android.PathForModuleSrc(ctx, optInDumpDir)
diff --git a/cc/sabi.go b/cc/sabi.go
index edd9cfe..64eab41 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -43,8 +43,6 @@
 		return "platform"
 	case llndkLsdumpTag:
 		return "vndk"
-	case platformLsdumpTag:
-		return "platform"
 	default:
 		return ""
 	}
@@ -134,10 +132,10 @@
 		if m.isImplementationForLLNDKPublic() {
 			result = append(result, llndkLsdumpTag)
 		}
-		// APEX and opt-in platform dumps are placed in the same directory.
 		if m.library.hasStubsVariants() {
 			result = append(result, apexLsdumpTag)
-		} else if headerAbiChecker.enabled() {
+		}
+		if headerAbiChecker.enabled() {
 			result = append(result, platformLsdumpTag)
 		}
 	} else if headerAbiChecker.enabled() {
diff --git a/cc/test.go b/cc/test.go
index 3a1a3af..a96af31 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -359,6 +359,12 @@
 func (test *testBinary) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = test.binaryDecorator.linkerFlags(ctx, flags)
 	flags = test.testDecorator.linkerFlags(ctx, flags)
+
+	// Add a default rpath to allow tests to dlopen libraries specified in data_libs.
+	// Host modules already get an rpath specified in linker.go.
+	if !ctx.Host() {
+		flags.Global.LdFlags = append(flags.Global.LdFlags, `-Wl,-rpath,\$$ORIGIN`)
+	}
 	return flags
 }
 
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index d64010e..4490dd2 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -108,7 +108,7 @@
 	case "always":
 		return true
 	case "depend":
-		if _, err := os.Stat(filepath.Join(ctx.Config().OutDir(), ".ninja_log")); errors.Is(err, os.ErrNotExist) {
+		if _, err := os.Stat(filepath.Join(topDir, ctx.Config().OutDir(), ".ninja_log")); errors.Is(err, os.ErrNotExist) {
 			return true
 		}
 	}
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index 854a366..a08f7cf 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -15,6 +15,7 @@
         "soong-phony", // for testing
     ],
     srcs: [
+        "aconfig_files.go",
         "avb_add_hash_footer.go",
         "avb_gen_vbmeta_image.go",
         "bootimg.go",
diff --git a/filesystem/aconfig_files.go b/filesystem/aconfig_files.go
new file mode 100644
index 0000000..44de202
--- /dev/null
+++ b/filesystem/aconfig_files.go
@@ -0,0 +1,58 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filesystem
+
+import (
+	"android/soong/android"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func (f *filesystem) buildAconfigFlagsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, dir android.Path) {
+	if !proptools.Bool(f.properties.Gen_aconfig_flags_pb) {
+		return
+	}
+
+	aconfigFlagsBuilderPath := android.PathForModuleOut(ctx, "aconfig_flags_builder.sh")
+	aconfigToolPath := ctx.Config().HostToolPath(ctx, "aconfig")
+	cmd := builder.Command().Tool(aconfigFlagsBuilderPath).Implicit(aconfigToolPath)
+
+	installAconfigFlags := filepath.Join(dir.String(), "etc", "aconfig_flags_"+f.partitionName()+".pb")
+
+	var sb strings.Builder
+	sb.WriteString("set -e\n")
+	sb.WriteString(aconfigToolPath.String())
+	sb.WriteString(" dump-cache --dedup --format protobuf --out ")
+	sb.WriteString(installAconfigFlags)
+	sb.WriteString(" \\\n")
+
+	var caches []string
+	for _, ps := range specs {
+		cmd.Implicits(ps.GetAconfigPaths())
+		caches = append(caches, ps.GetAconfigPaths().Strings()...)
+	}
+	caches = android.SortedUniqueStrings(caches)
+
+	for _, cache := range caches {
+		sb.WriteString("  --cache ")
+		sb.WriteString(cache)
+		sb.WriteString(" \\\n")
+	}
+	sb.WriteRune('\n')
+
+	android.WriteExecutableFileRuleVerbatim(ctx, aconfigFlagsBuilderPath, sb.String())
+}
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index b342ae9..d8a00e2 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -131,6 +131,9 @@
 	// Default is false
 	Build_logtags *bool
 
+	// Install aconfig_flags.pb file for the modules installed in this partition.
+	Gen_aconfig_flags_pb *bool
+
 	Fsverity fsverityProperties
 }
 
@@ -300,6 +303,7 @@
 	f.addMakeBuiltFiles(ctx, builder, rootDir)
 	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
 	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
+	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
 
 	// run host_init_verifier
 	// Ideally we should have a concept of pluggable linters that verify the generated image.
@@ -441,6 +445,7 @@
 	f.buildNonDepsFiles(ctx, builder, rootDir)
 	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
 	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
+	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
 
 	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
 	cmd := builder.Command().
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index acd4813..861918f 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -465,3 +465,35 @@
 		}
 	`)
 }
+
+func TestTrackPhonyAsRequiredDep(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "fs",
+			deps: ["foo"],
+		}
+
+		cc_binary {
+			name: "foo",
+			required: ["phony"],
+		}
+
+		phony {
+			name: "phony",
+			required: ["libbar"],
+		}
+
+		cc_library {
+			name: "libbar",
+		}
+	`)
+
+	fs := result.ModuleForTests("fs", "android_common").Module().(*filesystem)
+	expected := []string{
+		"bin/foo",
+		"lib64/libbar.so",
+	}
+	for _, e := range expected {
+		android.AssertStringListContains(t, "missing entry", fs.entries, e)
+	}
+}
diff --git a/java/app.go b/java/app.go
index 50d1a2f..f05b8a7 100644
--- a/java/app.go
+++ b/java/app.go
@@ -521,7 +521,7 @@
 	}
 
 	// Use non final ids if we are doing optimized shrinking and are using R8.
-	nonFinalIds := Bool(a.dexProperties.Optimize.Optimized_shrink_resources) && a.dexer.effectiveOptimizeEnabled()
+	nonFinalIds := a.dexProperties.optimizedResourceShrinkingEnabled(ctx) && a.dexer.effectiveOptimizeEnabled()
 	a.aapt.buildActions(ctx,
 		aaptBuildActionOptions{
 			sdkContext:                     android.SdkContext(a),
@@ -552,7 +552,7 @@
 	staticLibProguardFlagFiles = android.FirstUniquePaths(staticLibProguardFlagFiles)
 
 	a.Module.extraProguardFlagsFiles = append(a.Module.extraProguardFlagsFiles, staticLibProguardFlagFiles...)
-	if !Bool(a.dexProperties.Optimize.Optimized_shrink_resources) {
+	if !(a.dexProperties.optimizedResourceShrinkingEnabled(ctx)) {
 		// When using the optimized shrinking the R8 enqueuer will traverse the xml files that become
 		// live for code references and (transitively) mark these as live.
 		// In this case we explicitly don't wan't the aapt2 generated keep files (which would keep the now
@@ -591,7 +591,7 @@
 	var packageResources = a.exportPackage
 
 	if ctx.ModuleName() != "framework-res" {
-		if a.dexProperties.resourceShrinkingEnabled() {
+		if a.dexProperties.resourceShrinkingEnabled(ctx) {
 			protoFile := android.PathForModuleOut(ctx, packageResources.Base()+".proto.apk")
 			aapt2Convert(ctx, protoFile, packageResources, "proto")
 			a.dexer.resourcesInput = android.OptionalPathForPath(protoFile)
@@ -614,7 +614,7 @@
 		}
 
 		a.Module.compile(ctx, extraSrcJars, extraClasspathJars, extraCombinedJars)
-		if a.dexProperties.resourceShrinkingEnabled() {
+		if a.dexProperties.resourceShrinkingEnabled(ctx) {
 			binaryResources := android.PathForModuleOut(ctx, packageResources.Base()+".binary.out.apk")
 			aapt2Convert(ctx, binaryResources, a.dexer.resourcesOutput.Path(), "binary")
 			packageResources = binaryResources
diff --git a/java/dex.go b/java/dex.go
index 6caaa7f..8cfffaf 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -111,8 +111,12 @@
 	return BoolDefault(d.dexProperties.Optimize.Enabled, d.dexProperties.Optimize.EnabledByDefault)
 }
 
-func (d *DexProperties) resourceShrinkingEnabled() bool {
-	return BoolDefault(d.Optimize.Optimized_shrink_resources, Bool(d.Optimize.Shrink_resources))
+func (d *DexProperties) resourceShrinkingEnabled(ctx android.ModuleContext) bool {
+	return !ctx.Config().Eng() && BoolDefault(d.Optimize.Optimized_shrink_resources, Bool(d.Optimize.Shrink_resources))
+}
+
+func (d *DexProperties) optimizedResourceShrinkingEnabled(ctx android.ModuleContext) bool {
+	return d.resourceShrinkingEnabled(ctx) && Bool(d.Optimize.Optimized_shrink_resources)
 }
 
 var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8",
diff --git a/scripts/extra_install_zips_file_list.py b/scripts/extra_install_zips_file_list.py
index 8ea2a4b..148d6cc 100755
--- a/scripts/extra_install_zips_file_list.py
+++ b/scripts/extra_install_zips_file_list.py
@@ -18,13 +18,16 @@
     parser.add_argument('staging_dir',
         help='Path to the partition staging directory')
     parser.add_argument('extra_install_zips', nargs='*',
-        help='The value of EXTRA_INSTALL_ZIPS from make. It should be a list of extraction_dir:zip_file pairs.')
+        help='The value of EXTRA_INSTALL_ZIPS from make. '
+        'It should be a list of primary_file:extraction_dir:zip_file trios. '
+        'The primary file will be ignored by this script, you should ensure that '
+        'the list of trios given to this script is already filtered by relevant primary files.')
     args = parser.parse_args()
 
     staging_dir = args.staging_dir.removesuffix('/') + '/'
 
-    for zip_pair in args.extra_install_zips:
-        d, z = zip_pair.split(':')
+    for zip_trio in args.extra_install_zips:
+        _, d, z = zip_trio.split(':')
         d = d.removesuffix('/') + '/'
 
         if d.startswith(staging_dir):