diff --git a/android/api_levels.go b/android/api_levels.go index 44c86403d..3f538c038 100644 --- a/android/api_levels.go +++ b/android/api_levels.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "strconv" + "strings" ) func init() { @@ -237,6 +238,14 @@ func uncheckedFinalApiLevel(num int) ApiLevel { } } +func uncheckedFinalIncrementalApiLevel(num int, increment int) ApiLevel { + return ApiLevel{ + value: strconv.Itoa(num) + "." + strconv.Itoa(increment), + number: num, + isPreview: false, + } +} + var NoneApiLevel = ApiLevel{ value: "(no version)", // Not 0 because we don't want this to compare equal with the first preview. @@ -371,6 +380,22 @@ func ApiLevelForTest(raw string) ApiLevel { return FutureApiLevel } + if strings.Contains(raw, ".") { + // Check prebuilt incremental API format MM.m for major (API level) and minor (incremental) revisions + parts := strings.Split(raw, ".") + if len(parts) != 2 { + panic(fmt.Errorf("Found unexpected version '%s' for incremental API - expect MM.m format for incremental API with both major (MM) an minor (m) revision.", raw)) + } + sdk, sdk_err := strconv.Atoi(parts[0]) + qpr, qpr_err := strconv.Atoi(parts[1]) + if sdk_err != nil || qpr_err != nil { + panic(fmt.Errorf("Unable to read version number for incremental api '%s'", raw)) + } + + apiLevel := uncheckedFinalIncrementalApiLevel(sdk, qpr) + return apiLevel + } + asInt, err := strconv.Atoi(raw) if err != nil { panic(fmt.Errorf("%q could not be parsed as an integer and is not a recognized codename", raw)) diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go index 044802e4d..99cb99ba2 100644 --- a/java/prebuilt_apis.go +++ b/java/prebuilt_apis.go @@ -55,6 +55,11 @@ type prebuiltApisProperties struct { // If set to true, compile dex for java_import modules. Defaults to false. Imports_compile_dex *bool + + // If set to true, allow incremental platform API of the form MM.m where MM is the major release + // version corresponding to the API level/SDK_INT and m is an incremental release version + // (e.g. API changes associated with QPR). Defaults to false. + Allow_incremental_platform_api *bool } type prebuiltApis struct { @@ -69,6 +74,8 @@ func (module *prebuiltApis) GenerateAndroidBuildActions(ctx android.ModuleContex // parsePrebuiltPath parses the relevant variables out of a variety of paths, e.g. // //.jar // //api/.txt +// *Note when using incremental platform API, may be of the form MM.m where MM is the +// API level and m is an incremental release, otherwise is a single integer corresponding to the API level only. // extensions///.jar // extensions///api/.txt func parsePrebuiltPath(ctx android.LoadHookContext, p string) (module string, version string, scope string) { @@ -90,8 +97,25 @@ func parsePrebuiltPath(ctx android.LoadHookContext, p string) (module string, ve } // parseFinalizedPrebuiltPath is like parsePrebuiltPath, but verifies the version is numeric (a finalized version). -func parseFinalizedPrebuiltPath(ctx android.LoadHookContext, p string) (module string, version int, scope string) { +func parseFinalizedPrebuiltPath(ctx android.LoadHookContext, p string, allowIncremental bool) (module string, version int, release int, scope string) { module, v, scope := parsePrebuiltPath(ctx, p) + if allowIncremental { + parts := strings.Split(v, ".") + if len(parts) != 2 { + ctx.ModuleErrorf("Found unexpected version '%v' for incremental prebuilts - expect MM.m format for incremental API with both major (MM) an minor (m) revision.", v) + return + } + sdk, sdk_err := strconv.Atoi(parts[0]) + qpr, qpr_err := strconv.Atoi(parts[1]) + if sdk_err != nil || qpr_err != nil { + ctx.ModuleErrorf("Unable to read version number for incremental prebuilt api '%v'", v) + return + } + version = sdk + release = qpr + return + } + release = 0 version, err := strconv.Atoi(v) if err != nil { ctx.ModuleErrorf("Found finalized API files in non-numeric dir '%v'", v) @@ -268,29 +292,35 @@ func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) { } // Create modules for all (, ) triplets, + allowIncremental := proptools.BoolDefault(p.properties.Allow_incremental_platform_api, false) for _, f := range apiLevelFiles { - module, version, scope := parseFinalizedPrebuiltPath(mctx, f) - createApiModule(mctx, PrebuiltApiModuleName(module, scope, strconv.Itoa(version)), f) + module, version, release, scope := parseFinalizedPrebuiltPath(mctx, f, allowIncremental) + if allowIncremental { + incrementalVersion := strconv.Itoa(version) + "." + strconv.Itoa(release) + createApiModule(mctx, PrebuiltApiModuleName(module, scope, incrementalVersion), f) + } else { + createApiModule(mctx, PrebuiltApiModuleName(module, scope, strconv.Itoa(version)), f) + } } // Figure out the latest version of each module/scope type latestApiInfo struct { module, scope, path string - version int + version, release int isExtensionApiFile bool } getLatest := func(files []string, isExtensionApiFile bool) map[string]latestApiInfo { m := make(map[string]latestApiInfo) for _, f := range files { - module, version, scope := parseFinalizedPrebuiltPath(mctx, f) + module, version, release, scope := parseFinalizedPrebuiltPath(mctx, f, allowIncremental) if strings.HasSuffix(module, "incompatibilities") { continue } key := module + "." + scope info, exists := m[key] - if !exists || version > info.version { - m[key] = latestApiInfo{module, scope, f, version, isExtensionApiFile} + if !exists || version > info.version || (version == info.version && release > info.release) { + m[key] = latestApiInfo{module, scope, f, version, release, isExtensionApiFile} } } return m diff --git a/java/prebuilt_apis_test.go b/java/prebuilt_apis_test.go index 2b8435325..b6fb2c6bf 100644 --- a/java/prebuilt_apis_test.go +++ b/java/prebuilt_apis_test.go @@ -99,3 +99,26 @@ func TestPrebuiltApis_WithExtensions(t *testing.T) { android.AssertStringEquals(t, "Expected latest bar = extension level 2", "prebuilts/sdk/extensions/2/public/api/bar.txt", bar_input) android.AssertStringEquals(t, "Expected latest baz = api level 32", "prebuilts/sdk/32/public/api/baz.txt", baz_input) } + +func TestPrebuiltApis_WithIncrementalApi(t *testing.T) { + runTestWithIncrementalApi := func() (foo_input, bar_input, baz_input string) { + result := android.GroupFixturePreparers( + prepareForJavaTest, + FixtureWithPrebuiltIncrementalApis(map[string][]string{ + "33.0": {"foo"}, + "33.1": {"foo", "bar", "baz"}, + "33.2": {"foo", "bar"}, + "current": {"foo", "bar"}, + }), + ).RunTest(t) + foo_input = result.ModuleForTests("foo.api.public.latest", "").Rule("generator").Implicits[0].String() + bar_input = result.ModuleForTests("bar.api.public.latest", "").Rule("generator").Implicits[0].String() + baz_input = result.ModuleForTests("baz.api.public.latest", "").Rule("generator").Implicits[0].String() + return + } + // 33.1 is the latest for baz, 33.2 is the latest for both foo & bar + foo_input, bar_input, baz_input := runTestWithIncrementalApi() + android.AssertStringEquals(t, "Expected latest foo = api level 33.2", "prebuilts/sdk/33.2/public/api/foo.txt", foo_input) + android.AssertStringEquals(t, "Expected latest bar = api level 33.2", "prebuilts/sdk/33.2/public/api/bar.txt", bar_input) + android.AssertStringEquals(t, "Expected latest baz = api level 33.1", "prebuilts/sdk/33.1/public/api/baz.txt", baz_input) +} diff --git a/java/testing.go b/java/testing.go index 3a238d76f..9b1493f1f 100644 --- a/java/testing.go +++ b/java/testing.go @@ -225,6 +225,29 @@ func FixtureWithPrebuiltApisAndExtensions(apiLevel2Modules map[string][]string, ) } +func FixtureWithPrebuiltIncrementalApis(apiLevel2Modules map[string][]string) android.FixturePreparer { + mockFS := android.MockFS{} + path := "prebuilts/sdk/Android.bp" + + bp := fmt.Sprintf(` + prebuilt_apis { + name: "sdk", + api_dirs: ["%s"], + allow_incremental_platform_api: true, + imports_sdk_version: "none", + imports_compile_dex: true, + } + `, strings.Join(android.SortedKeys(apiLevel2Modules), `", "`)) + + for release, modules := range apiLevel2Modules { + mockFS.Merge(prebuiltApisFilesForModules([]string{release}, modules)) + } + return android.GroupFixturePreparers( + android.FixtureAddTextFile(path, bp), + android.FixtureMergeMockFs(mockFS), + ) +} + func prebuiltApisFilesForModules(apiLevels []string, modules []string) map[string][]byte { libs := append([]string{"android"}, modules...)