Merge "Remove diff excludes for partition compatibility symlink files in SBOM integration test."
diff --git a/android/sdk_version.go b/android/sdk_version.go
index 1f01dc6..0ae8073 100644
--- a/android/sdk_version.go
+++ b/android/sdk_version.go
@@ -86,7 +86,7 @@
 
 // JavaLibraryName returns the soong module containing the Java APIs of that API surface.
 func (k SdkKind) JavaLibraryName(c Config) string {
-	name := k.defaultJavaLibraryName()
+	name := k.DefaultJavaLibraryName()
 	return JavaApiLibraryName(c, name)
 }
 
@@ -100,7 +100,7 @@
 	return name
 }
 
-func (k SdkKind) defaultJavaLibraryName() string {
+func (k SdkKind) DefaultJavaLibraryName() string {
 	switch k {
 	case SdkPublic:
 		return "android_stubs_current"
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index aac5e7d..5c33308 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -21,10 +21,13 @@
 	"path/filepath"
 	"regexp"
 	"sort"
+	"strconv"
+	"strings"
 	"sync"
 	"sync/atomic"
 
 	"android/soong/shared"
+
 	"github.com/google/blueprint/pathtools"
 )
 
@@ -35,6 +38,13 @@
 // excluded from symlinking. Otherwise, the node is not excluded, but one of its
 // descendants is (otherwise the node in question would not exist)
 
+// This is a version int written to a file called symlink_forest_version at the root of the
+// symlink forest. If the version here does not match the version in the file, then we'll
+// clean the whole symlink forest and recreate it. This number can be bumped whenever there's
+// an incompatible change to the forest layout or a bug in incrementality that needs to be fixed
+// on machines that may still have the bug present in their forest.
+const symlinkForestVersion = 1
+
 type instructionsNode struct {
 	name     string
 	excluded bool // If false, this is just an intermediate node
@@ -123,6 +133,34 @@
 	}
 	newContents = append(newContents, srcBuildFileContent...)
 
+	// Say you run bp2build 4 times:
+	// - The first time there's only an Android.bp file. bp2build will convert it to a build file
+	//   under out/soong/bp2build, then symlink from the forest to that generated file
+	// - Then you add a handcrafted BUILD file in the same directory. bp2build will merge this with
+	//   the generated one, and write the result to the output file in the forest. But the output
+	//   file was a symlink to out/soong/bp2build from the previous step! So we erroneously update
+	//   the file in out/soong/bp2build instead. So far this doesn't cause any problems...
+	// - You run a 3rd bp2build with no relevant changes. Everything continues to work.
+	// - You then add a comment to the handcrafted BUILD file. This causes a merge with the
+	//   generated file again. But since we wrote to the generated file in step 2, the generated
+	//   file has an old copy of the handcrafted file in it! This probably causes duplicate bazel
+	//   targets.
+	// To solve this, if we see that the output file is a symlink from a previous build, remove it.
+	stat, err := os.Lstat(output)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	} else if err == nil {
+		if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
+			if verbose {
+				fmt.Fprintf(os.Stderr, "Removing symlink so that we can replace it with a merged file: %s\n", output)
+			}
+			err = os.Remove(output)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
 	return pathtools.WriteFileIfChanged(output, newContents, 0666)
 }
 
@@ -202,6 +240,46 @@
 	return false
 }
 
+// maybeCleanSymlinkForest will remove the whole symlink forest directory if the version recorded
+// in the symlink_forest_version file is not equal to symlinkForestVersion.
+func maybeCleanSymlinkForest(topdir, forest string, verbose bool) error {
+	versionFilePath := shared.JoinPath(topdir, forest, "symlink_forest_version")
+	versionFileContents, err := os.ReadFile(versionFilePath)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+	versionFileString := strings.TrimSpace(string(versionFileContents))
+	symlinkForestVersionString := strconv.Itoa(symlinkForestVersion)
+	if err != nil || versionFileString != symlinkForestVersionString {
+		if verbose {
+			fmt.Fprintf(os.Stderr, "Old symlink_forest_version was %q, current is %q. Cleaning symlink forest before recreating...\n", versionFileString, symlinkForestVersionString)
+		}
+		err = os.RemoveAll(shared.JoinPath(topdir, forest))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// maybeWriteVersionFile will write the symlink_forest_version file containing symlinkForestVersion
+// if it doesn't exist already. If it exists we know it must contain symlinkForestVersion because
+// we checked for that already in maybeCleanSymlinkForest
+func maybeWriteVersionFile(topdir, forest string) error {
+	versionFilePath := shared.JoinPath(topdir, forest, "symlink_forest_version")
+	_, err := os.Stat(versionFilePath)
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return err
+		}
+		err = os.WriteFile(versionFilePath, []byte(strconv.Itoa(symlinkForestVersion)+"\n"), 0666)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // Recursively plants a symlink forest at forestDir. The symlink tree will
 // contain every file in buildFilesDir and srcDir excluding the files in
 // instructions. Collects every directory encountered during the traversal of
@@ -395,6 +473,12 @@
 		symlinkCount: atomic.Uint64{},
 	}
 
+	err := maybeCleanSymlinkForest(topdir, forest, verbose)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+
 	instructions := instructionsFromExcludePathList(exclude)
 	go func() {
 		context.wg.Add(1)
@@ -407,5 +491,11 @@
 		deps = append(deps, dep)
 	}
 
+	err = maybeWriteVersionFile(topdir, forest)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+
 	return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
 }
diff --git a/cc/library.go b/cc/library.go
index a9ada97..7504302 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -26,7 +26,6 @@
 	"android/soong/android"
 	"android/soong/bazel"
 	"android/soong/bazel/cquery"
-	"android/soong/cc/config"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/pathtools"
@@ -2261,8 +2260,7 @@
 		!ctx.useVndk() && !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() && ctx.Device() &&
 		library.baseLinker.sanitize.isUnsanitizedVariant() &&
 		ctx.isForPlatform() && !ctx.isPreventInstall() {
-		installPath := getNdkSysrootBase(ctx).Join(
-			ctx, "usr/lib", config.NDKTriple(ctx.toolchain()), file.Base())
+		installPath := getUnversionedLibraryInstallPath(ctx).Join(ctx, file.Base())
 
 		ctx.ModuleBuild(pctx, android.ModuleBuildParams{
 			Rule:        android.Cp,
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 2473ba2..a824361 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -528,17 +528,20 @@
 	return false
 }
 
-func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) {
-	arch := ctx.Target().Arch.ArchType.Name
-	// arm64 isn't actually a multilib toolchain, so unlike the other LP64
-	// architectures it's just installed to lib.
-	libDir := "lib"
-	if ctx.toolchain().Is64Bit() && arch != "arm64" {
-		libDir = "lib64"
-	}
+// Returns the install path for unversioned NDK libraries (currently only static
+// libraries).
+func getUnversionedLibraryInstallPath(ctx ModuleContext) android.InstallPath {
+	return getNdkSysrootBase(ctx).Join(ctx, "usr/lib", config.NDKTriple(ctx.toolchain()))
+}
 
-	installDir := getNdkInstallBase(ctx).Join(ctx, fmt.Sprintf(
-		"platforms/android-%s/arch-%s/usr/%s", stub.apiLevel, arch, libDir))
+// Returns the install path for versioned NDK libraries. These are most often
+// stubs, but the same paths are used for CRT objects.
+func getVersionedLibraryInstallPath(ctx ModuleContext, apiLevel android.ApiLevel) android.InstallPath {
+	return getUnversionedLibraryInstallPath(ctx).Join(ctx, apiLevel.String())
+}
+
+func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) {
+	installDir := getVersionedLibraryInstallPath(ctx, stub.apiLevel)
 	stub.installPath = ctx.InstallFile(installDir, path.Base(), path)
 }
 
diff --git a/java/java.go b/java/java.go
index 403f503..d400b0c 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1612,7 +1612,7 @@
 }
 
 type JavaApiLibraryDepsInfo struct {
-	StubsJar    android.Path
+	JavaInfo
 	StubsSrcJar android.Path
 }
 
@@ -1821,7 +1821,7 @@
 			staticLibs = append(staticLibs, provider.HeaderJars...)
 		case depApiSrcsTag:
 			provider := ctx.OtherModuleProvider(dep, JavaApiLibraryDepsProvider).(JavaApiLibraryDepsInfo)
-			classPaths = append(classPaths, provider.StubsJar)
+			classPaths = append(classPaths, provider.HeaderJars...)
 			depApiSrcsStubsSrcJar = provider.StubsSrcJar
 		}
 	})
@@ -1900,7 +1900,9 @@
 	})
 
 	ctx.SetProvider(JavaApiLibraryDepsProvider, JavaApiLibraryDepsInfo{
-		StubsJar:    al.stubsJar,
+		JavaInfo: JavaInfo{
+			HeaderJars: android.PathsIfNonNil(al.stubsJar),
+		},
 		StubsSrcJar: al.stubsSrcJar,
 	})
 }
diff --git a/java/testing.go b/java/testing.go
index 8a0db9c..0764d26 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -368,14 +368,6 @@
 		"core.current.stubs",
 		"legacy.core.platform.api.stubs",
 		"stable.core.platform.api.stubs",
-		"android_stubs_current.from-text",
-		"android_system_stubs_current.from-text",
-		"android_test_stubs_current.from-text",
-		"android_module_lib_stubs_current.from-text",
-		"android_system_server_stubs_current.from-text",
-		"core.current.stubs.from-text",
-		"legacy.core.platform.api.stubs.from-text",
-		"stable.core.platform.api.stubs.from-text",
 
 		"kotlin-stdlib",
 		"kotlin-stdlib-jdk7",
@@ -396,6 +388,27 @@
 		`, extra)
 	}
 
+	extraApiLibraryModules := map[string]string{
+		"android_stubs_current.from-text":               "api/current.txt",
+		"android_system_stubs_current.from-text":        "api/system-current.txt",
+		"android_test_stubs_current.from-text":          "api/test-current.txt",
+		"android_module_lib_stubs_current.from-text":    "api/module-lib-current.txt",
+		"android_system_server_stubs_current.from-text": "api/system-server-current.txt",
+		"core.current.stubs.from-text":                  "api/current.txt",
+		"legacy.core.platform.api.stubs.from-text":      "api/current.txt",
+		"stable.core.platform.api.stubs.from-text":      "api/current.txt",
+		"core-lambda-stubs.from-text":                   "api/current.txt",
+	}
+
+	for libName, apiFile := range extraApiLibraryModules {
+		bp += fmt.Sprintf(`
+            java_api_library {
+                name: "%s",
+                api_files: ["%s"],
+            }
+        `, libName, apiFile)
+	}
+
 	bp += `
 		java_library {
 			name: "framework",