cc: fix version macro for stubs

When a cc module is built against a stub, compiler passes version macro
of the stub lib. Version macro should be numeric, so codenames or
"current" should be mapped to numbers just like how ndkstubgen maps to.

* "current" -> future (10000)
* codenames -> look up api_level.json
* otherwise -> cast to int

Bug: 179329813
Test: m / soong test / manually check the output build.ninja
Change-Id: Ic0e1dd904984e161694a0b77fad5559c06a4462f
diff --git a/android/api_levels.go b/android/api_levels.go
index 1b53f3f..2f6a9d2 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -24,6 +24,8 @@
 	RegisterSingletonType("api_levels", ApiLevelsSingleton)
 }
 
+const previewAPILevelBase = 9000
+
 // An API level, which may be a finalized (numbered) API, a preview (codenamed)
 // API, or the future API level (10000). Can be parsed from a string with
 // ApiLevelFromUser or ApiLevelOrPanic.
@@ -57,6 +59,21 @@
 	}
 }
 
+// FinalOrPreviewInt distinguishes preview versions from "current" (future).
+// This is for "native" stubs and should be in sync with ndkstubgen/getApiLevelsMap().
+// - "current" -> future (10000)
+// - preview codenames -> preview base (9000) + index
+// - otherwise -> cast to int
+func (this ApiLevel) FinalOrPreviewInt() int {
+	if this.IsCurrent() {
+		return this.number
+	}
+	if this.IsPreview() {
+		return previewAPILevelBase + this.number
+	}
+	return this.number
+}
+
 // Returns the canonical name for this API level. For a finalized API level
 // this will be the API number as a string. For a preview API level this
 // will be the codename, or "current".
@@ -282,7 +299,6 @@
 
 func getApiLevelsMap(config Config) map[string]int {
 	return config.Once(apiLevelsMapKey, func() interface{} {
-		baseApiLevel := 9000
 		apiLevelsMap := map[string]int{
 			"G":     9,
 			"I":     14,
@@ -302,7 +318,7 @@
 			"R":     30,
 		}
 		for i, codename := range config.PlatformVersionActiveCodenames() {
-			apiLevelsMap[codename] = baseApiLevel + i
+			apiLevelsMap[codename] = previewAPILevelBase + i
 		}
 
 		return apiLevelsMap
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 7f5be7e..85d6259 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -994,6 +994,80 @@
 	})
 }
 
+func TestApex_PlatformUsesLatestStubFromApex(t *testing.T) {
+	t.Parallel()
+	//   myapex (Z)
+	//      mylib -----------------.
+	//                             |
+	//   otherapex (29)            |
+	//      libstub's versions: 29 Z current
+	//                                  |
+	//   <platform>                     |
+	//      libplatform ----------------'
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+			min_sdk_version: "Z", // non-final
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libstub"],
+			apex_available: ["myapex"],
+			min_sdk_version: "Z",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		apex {
+			name: "otherapex",
+			key: "myapex.key",
+			native_shared_libs: ["libstub"],
+			min_sdk_version: "29",
+		}
+
+		cc_library {
+			name: "libstub",
+			srcs: ["mylib.cpp"],
+			stubs: {
+				versions: ["29", "Z", "current"],
+			},
+			apex_available: ["otherapex"],
+			min_sdk_version: "29",
+		}
+
+		// platform module depending on libstub from otherapex should use the latest stub("current")
+		cc_library {
+			name: "libplatform",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libstub"],
+		}
+	`, func(fs map[string][]byte, config android.Config) {
+		config.TestProductVariables.Platform_sdk_codename = proptools.StringPtr("Z")
+		config.TestProductVariables.Platform_sdk_final = proptools.BoolPtr(false)
+		config.TestProductVariables.Platform_version_active_codenames = []string{"Z"}
+	})
+
+	// Ensure that mylib from myapex is built against "min_sdk_version" stub ("Z"), which is non-final
+	mylibCflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
+	ensureContains(t, mylibCflags, "-D__LIBSTUB_API__=9000 ")
+	mylibLdflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	ensureContains(t, mylibLdflags, "libstub/android_arm64_armv8-a_shared_Z/libstub.so ")
+
+	// Ensure that libplatform is built against latest stub ("current") of mylib3 from the apex
+	libplatformCflags := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	ensureContains(t, libplatformCflags, "-D__LIBSTUB_API__=10000 ") // "current" maps to 10000
+	libplatformLdflags := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
+	ensureContains(t, libplatformLdflags, "libstub/android_arm64_armv8-a_shared_current/libstub.so ")
+}
+
 func TestApexWithExplicitStubsDependency(t *testing.T) {
 	ctx, _ := testApex(t, `
 		apex {
diff --git a/cc/library.go b/cc/library.go
index 29a3c69..f185cb7 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -19,6 +19,7 @@
 	"io"
 	"path/filepath"
 	"regexp"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -1362,8 +1363,11 @@
 func (library *libraryDecorator) exportVersioningMacroIfNeeded(ctx android.BaseModuleContext) {
 	if library.buildStubs() && library.stubsVersion() != "" && !library.skipAPIDefine {
 		name := versioningMacroName(ctx.Module().(*Module).ImplementationModuleName(ctx))
-		ver := library.stubsVersion()
-		library.reexportFlags("-D" + name + "=" + ver)
+		apiLevel, err := android.ApiLevelFromUser(ctx, library.stubsVersion())
+		if err != nil {
+			ctx.ModuleErrorf("Can't export version macro: %s", err.Error())
+		}
+		library.reexportFlags("-D" + name + "=" + strconv.Itoa(apiLevel.FinalOrPreviewInt()))
 	}
 }