Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2ec7e1c5 authored by Todd Lee's avatar Todd Lee
Browse files

Support for incremetal platform prebuilt APIs

This change provides support for prebuilt incremental platform API (i.e.
API changes associated with a QPR, as opposed to a major dessert
releas).

This feature is provided via the existing prebuilt_apis module with the
introduction of a new attribute:

    allow_incremental_platform_api

While typical platform prebuilt APIs are presumed to be under a
directory structure that follows the pattern:

<version>/<scope>/<module>.jar
<version>/<scope>/api/<module>.txt

Where <version> is limited to a single integer signifying the API level.

For modules where allow_incremental_platform_api is set to 'true' (false
by default) the pattern is the same, however <version> is presumed to be
of the form MM.m, where MM aligns with the existing API level and m
signifies the incremental release (e.g. QPR).

Bug: b/280790094
Test: platform build check with both incremental & non-incremental API
      cd build/soong && go test ./java
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:eee6995093485497bc29cdce01c2a86765ffb4eb)
Change-Id: I67e293006ccfa210d0dcc0a294db894632f1b6cb
parent 1725b20d
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -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))
+37 −7
Original line number Diff line number Diff line
@@ -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.
// <version>/<scope>/<module>.jar
// <version>/<scope>/api/<module>.txt
// *Note when using incremental platform API, <version> may be of the form MM.m where MM is the
// API level and m is an incremental release, otherwise <version> is a single integer corresponding to the API level only.
// extensions/<version>/<scope>/<module>.jar
// extensions/<version>/<scope>/api/<module>.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 (<module>, <scope, <version>) triplets,
	allowIncremental := proptools.BoolDefault(p.properties.Allow_incremental_platform_api, false)
	for _, f := range apiLevelFiles {
		module, version, scope := parseFinalizedPrebuiltPath(mctx, 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
+23 −0
Original line number Diff line number Diff line
@@ -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)
}
+23 −0
Original line number Diff line number Diff line
@@ -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...)