aboutsummaryrefslogtreecommitdiff
path: root/rules/common
diff options
context:
space:
mode:
authorJingwen Chen <jingwen@google.com>2023-03-07 07:43:09 +0000
committerJingwen Chen <jingwen@google.com>2023-03-09 16:09:46 +0000
commitcac20aeacb3c2990712f07c9d91994fefff6dc96 (patch)
tree236d75632482b9573470d1c81ba0c2b4e333d653 /rules/common
parent9fd4a5115086f6d60868bcd1e0f151d1987f4bb9 (diff)
downloadbazel-cac20aeacb3c2990712f07c9d91994fefff6dc96.tar.gz
Add preview level support to api.bzl.
The current implementation was also somewhat incomplete compared to the Soong version, e.g. it didn't use the finalized codenames. I'd suggest reading the unit tests first to understand the expected parse results. Fixes: 271424224 Bug: 271424349 Test: new AndroidManifest.xml diff test to show target sdk version being set correctly to 10000 (among other things) Test: new unittest for the api parser Change-Id: I467cdda71d766327e25c3d1db0df47db6eb2502b
Diffstat (limited to 'rules/common')
-rw-r--r--rules/common/BUILD.bazel3
-rw-r--r--rules/common/api.bzl114
-rw-r--r--rules/common/api_test.bzl77
3 files changed, 157 insertions, 37 deletions
diff --git a/rules/common/BUILD.bazel b/rules/common/BUILD.bazel
index e69de29b..e8ee30d3 100644
--- a/rules/common/BUILD.bazel
+++ b/rules/common/BUILD.bazel
@@ -0,0 +1,3 @@
+load(":api_test.bzl", "api_levels_test_suite")
+
+api_levels_test_suite(name = "api_test")
diff --git a/rules/common/api.bzl b/rules/common/api.bzl
index 54d3b402..24dd0a8a 100644
--- a/rules/common/api.bzl
+++ b/rules/common/api.bzl
@@ -12,75 +12,115 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# An API level, can be a finalized (numbered) API, a preview (codenamed) API, or
+# the future API level (10000). Can be parsed from a string with
+# parse_api_level_with_version.
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("@soong_injection//api_levels:api_levels.bzl", "api_levels_released_versions")
load("@soong_injection//product_config:product_variables.bzl", "product_vars")
-PREVIEW_API_LEVEL_BASE = 9000
-FUTURE_API_LEVEL_INT = 10000 # API Level associated with an arbitrary future release
+_PREVIEW_API_LEVEL_BASE = 9000 # Base constant for preview API levels.
+_FUTURE_API_LEVEL_INT = 10000 # API Level associated with an arbitrary future release
+
+# TODO(b/271280342): access these variables in a transition friendly way.
+_PLATFORM_SDK_FINAL = product_vars.get("Platform_sdk_final")
+_PLATFORM_SDK_VERSION = product_vars.get("Platform_sdk_version")
+_PLATFORM_SDK_CODENAME = product_vars.get("Platform_sdk_codename")
+_PLATFORM_VERSION_ACTIVE_CODENAMES = product_vars.get("Platform_version_active_codenames", [])
+
+# Dict of unfinalized codenames to a placeholder preview API int.
+_preview_codenames_to_ints = {
+ codename: _PREVIEW_API_LEVEL_BASE + i
+ for i, codename in enumerate(_PLATFORM_VERSION_ACTIVE_CODENAMES)
+}
+
+_final_codename = {
+ "current": _final_or_future(_PLATFORM_SDK_VERSION),
+} if _PLATFORM_SDK_FINAL and _PLATFORM_SDK_VERSION else {}
+
+_api_levels_with_previews = dicts.add(api_levels_released_versions, _preview_codenames_to_ints)
+_api_levels_with_final_codenames = dicts.add(api_levels_released_versions, _final_codename)
-def _api_levels_with_previews():
- ret = dict(api_levels_released_versions)
- active_codenames = product_vars.get("Platform_version_active_codenames", [])
- for i, codename in enumerate(active_codenames):
- ret[codename] = PREVIEW_API_LEVEL_BASE + i
- return ret
+# Returns true if a string or int version is in preview (not finalized).
+def _is_preview(version):
+ if type(version) == "string" and version.isdigit():
+ # normalize int types internally
+ version = int(version)
-def _api_levels_with_final_codenames():
- ret = dict(api_levels_released_versions)
- if product_vars.get("Platform_sdk_final"):
- platform_sdk_version = product_vars.get("Platform_sdk_version")
- if platform_sdk_version != None:
- ret["current"] = platform_sdk_version
- return ret
+ # Future / current is considered as a preview.
+ if version == "current" or version == _FUTURE_API_LEVEL_INT:
+ return True
-api_levels_with_previews = _api_levels_with_previews()
+ # api can be either the codename or the int level (9000+)
+ return version in _preview_codenames_to_ints or version in _preview_codenames_to_ints.values()
+
+# Return 10000 for unfinalized versions, otherwise return unchanged.
+def _final_or_future(version):
+ if _is_preview(version):
+ return _FUTURE_API_LEVEL_INT
+ else:
+ return version
# parse_api_level_from_version is a Starlark implementation of ApiLevelFromUser
# at https://cs.android.com/android/platform/superproject/+/master:build/soong/android/api_levels.go;l=221-250;drc=5095a6c4b484f34d5c4f55a855d6174e00fb7f5e
-def parse_api_level_from_version(version):
+def _parse_api_level_from_version(version):
"""converts the given string `version` to an api level
Args:
version: must be non-empty. Inputs that are not "current", known
- previews, or convertible to an integer will return an error.
+ previews, finalized codenames, or convertible to an integer will return
+ an error.
- Returns: The api level. This can be an int or unreleased version full name (string).
- Finalized codenames will be interpreted as their final API levels, not
- the preview of the associated releases. Future codenames return the
- version codename.
+ Returns: The api level as an int.
"""
- api_levels = api_levels_with_previews
if version == "":
fail("API level string must be non-empty")
if version == "current":
- return FUTURE_API_LEVEL_INT
+ return _FUTURE_API_LEVEL_INT
- if version in api_levels:
- return api_levels[version]
+ if _is_preview(version):
+ return _preview_codenames_to_ints.get(version) or int(version)
- elif version.isdigit():
+ # Not preview nor current.
+ #
+ # If the level is the codename of an API level has been finalized, this
+ # function returns the API level number associated with that API level. If the
+ # input is *not* a finalized codename, the input is returned unmodified.
+ canonical_level = api_levels_released_versions.get(version)
+ if not canonical_level:
+ if not version.isdigit():
+ fail("version %s could not be parsed as integer and is not a recognized codename" % version)
return int(version)
- else:
- fail("version could not be parsed as integer and is not a recognized codename")
+ return canonical_level
# Starlark implementation of DefaultAppTargetSDK from build/soong/android/config.go
# https://cs.android.com/android/platform/superproject/+/master:build/soong/android/config.go;l=875-889;drc=b0dc477ef740ec959548fe5517bd92ac4ea0325c
# check what you want returned for codename == "" case before using
-def default_app_target_sdk():
+def _default_app_target_sdk():
"""default_app_target_sdk returns the API level that platform apps are targeting.
This converts a codename to the exact ApiLevel it represents.
"""
- if product_vars.get("Platform_sdk_final"):
- return product_vars.get("Platform_sdk_version")
+ if _PLATFORM_SDK_FINAL:
+ return _PLATFORM_SDK_VERSION
- codename = product_vars.get("Platform_sdk_codename")
- if codename == "" or codename == None:
+ codename = _PLATFORM_SDK_CODENAME
+ if not codename:
# soong returns NoneApiLevel here value: "(no version)", number: -1, isPreview: true
- # APEX's targetSdkVersion sets this to FUTURE_API_LEVEL
- return FUTURE_API_LEVEL_INT
+ #
+ # fail fast instead of returning an arbitrary value.
+ fail("Platform_sdk_codename must be set.")
if codename == "REL":
fail("Platform_sdk_codename should not be REL when Platform_sdk_final is false")
- return parse_api_level_from_version(codename)
+ return _parse_api_level_from_version(codename)
+
+# TODO(jingwen): refactor all callers to use an exported struct instead of individual functions
+is_preview = _is_preview
+api_final_or_future = _final_or_future
+default_app_target_sdk = _default_app_target_sdk
+parse_api_level_from_version = _parse_api_level_from_version
+api_levels_with_previews = _api_levels_with_previews
+api_levels_with_final_codenames = _api_levels_with_final_codenames
diff --git a/rules/common/api_test.bzl b/rules/common/api_test.bzl
new file mode 100644
index 00000000..8de0477b
--- /dev/null
+++ b/rules/common/api_test.bzl
@@ -0,0 +1,77 @@
+load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
+load("//build/bazel/rules/common:api.bzl", "api_final_or_future", "is_preview", "parse_api_level_from_version")
+
+def _api_levels_test_impl(ctx):
+ env = unittest.begin(ctx)
+
+ # schema: version string to parse: (expected api int, is preview api)
+ _LEVELS_UNDER_TEST = {
+ # numbers
+ "9": (9, False), # earliest released number
+ "21": (21, False),
+ "30": (30, False),
+ "33": (33, False),
+ # unchecked non final api level (not finalized, not preview, not current)
+ "1234": (1234, False),
+ "8999": (8999, False),
+ "9999": (9999, False),
+ "10001": (10001, False),
+ # letters
+ "G": (9, False), # earliest released letter
+ "J-MR1": (17, False),
+ "R": (30, False),
+ "S": (31, False),
+ "S-V2": (32, False),
+ # codenames
+ "Tiramisu": (33, False),
+ "UpsideDownCake": (9000, True), # preview
+ "current": (10000, True), # future (considered as preview)
+ # preview numbers
+ "9000": (9000, True), # preview
+ "10000": (10000, True), # future (considered as preview)
+ }
+
+ for level, expected in _LEVELS_UNDER_TEST.items():
+ asserts.equals(env, expected[0], parse_api_level_from_version(level), "unexpected api level parsed for %s" % level)
+ asserts.equals(env, expected[1], is_preview(level), "unexpected is_preview value for %s" % level)
+
+ return unittest.end(env)
+
+api_levels_test = unittest.make(_api_levels_test_impl)
+
+def _final_or_future_test_impl(ctx):
+ env = unittest.begin(ctx)
+
+ # schema: version string to parse: expected api int
+ _LEVELS_UNDER_TEST = {
+ # finalized
+ "30": 30,
+ "33": 33,
+ "S": 31,
+ "S-V2": 32,
+ "Tiramisu": 33,
+ # not finalized
+ "UpsideDownCake": 10000,
+ "current": 10000,
+ "9000": 10000,
+ "10000": 10000,
+ }
+
+ for level, expected in _LEVELS_UNDER_TEST.items():
+ asserts.equals(
+ env,
+ expected,
+ api_final_or_future(parse_api_level_from_version(level)),
+ "unexpected final or future api for %s" % level,
+ )
+
+ return unittest.end(env)
+
+final_or_future_test = unittest.make(_final_or_future_test_impl)
+
+def api_levels_test_suite(name):
+ unittest.suite(
+ name,
+ api_levels_test,
+ final_or_future_test,
+ )