Generate sdk snapshots for the R build

This change replicates the behavior of the generate_ml_bundle.sh that
creates an Android.bp file along with the necessary file structure for
an R build.

It adds the information needed to a ForRBuild object which is set on
those MainlineModules that need to provide SDKs for R. That includes
a list of SdkLibrary objects each of which has a name and a flag that
indicates whether it is a shared library.

The R BuildRelease creator function builds the sdk snapshots as normal
and then extracts information from those snapshot files to construct
snapshots suitable for an R build. It then passes the directory
containing those new snapshot files to populate_dist() which will then
copy them into the correct location in the dist directory.

For each MainlineModule that provides an SDK for R the following is
created in the out/soong/mainline-sdks/for-R-build directory:
* A sub-directory using the apex name containing:
 * An Android.bp file with definitions for each SdkLibrary.
 * A file structure containing API txt file, removed API txt file,
   a srcjar containing the stub sources and a stubs jar.
* A zip file containing the contents of the previous directory whose
  name is <sdk-name>-current.zip, to match the files that are
  generated by Soong when building sdk snapshots.

As R does not support the Soong config boilerplate code that
transformation is disabled for R.

Test: atest --host mainline_modules_sdks_test
      packages/modules/common/build/mainline_modules_sdks.sh
      pyformat -s 4 --force_quote_type double -i build/mainline_modules_sdks*.py
      /usr/bin/pylint --rcfile $ANDROID_BUILD_TOP/tools/repohooks/tools/pylintrc build/mainline_modules_sdks*.py
Change-Id: I70001782496a3e9805bf56181c0e08419e31e191
This commit is contained in:
Paul Duffin
2022-03-16 13:06:42 +00:00
parent 726f9ab1bf
commit ffa89fd0a0
5 changed files with 398 additions and 32 deletions

View File

@@ -31,6 +31,24 @@ import typing
from typing import Callable, List
import zipfile
COPYRIGHT_BOILERPLATE = """
//
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
""".lstrip()
@dataclasses.dataclass(frozen=True)
class ConfigVar:
@@ -206,6 +224,9 @@ def sdk_snapshot_zip_file(snapshots_dir, sdk_name, sdk_version):
class SnapshotBuilder:
"""Builds sdk snapshots"""
# The path to this tool.
tool_path: str
# Used to run subprocesses for building snapshots.
subprocess_runner: SubprocessRunner
@@ -271,6 +292,80 @@ class SnapshotBuilder:
self.subprocess_runner.run(cmd, env=env)
return self.mainline_sdks_dir
def build_snapshots_for_build_r(self, build_release, sdk_versions, modules):
# Build the snapshots as standard.
snapshot_dir = self.build_snapshots(build_release, sdk_versions,
modules)
# Each module will extract needed files from the original snapshot zip
# file and then use that to create a replacement zip file.
r_snapshot_dir = os.path.join(snapshot_dir, "for-R-build")
shutil.rmtree(r_snapshot_dir, ignore_errors=True)
for module in modules:
apex = module.apex
dest_dir = os.path.join(r_snapshot_dir, apex)
os.makedirs(dest_dir, exist_ok=True)
bp_file = os.path.join(dest_dir, "Android.bp")
# The first sdk in the list is the name to use.
sdk_name = module.sdks[0]
with open(bp_file, "w", encoding="utf8") as bp:
bp.write("// DO NOT EDIT. Auto-generated by the following:\n")
bp.write(f"// {self.tool_path}\n")
bp.write(COPYRIGHT_BOILERPLATE)
aosp_apex = google_to_aosp_name(apex)
for library in module.for_r_build.sdk_libraries:
module_name = library.name
shared_library = str(library.shared_library).lower()
sdk_file = sdk_snapshot_zip_file(snapshot_dir, sdk_name,
"current")
extract_matching_files_from_zip(
sdk_file, dest_dir,
sdk_library_files_pattern(
scope_pattern=r"(public|system|module-lib)",
name_pattern=fr"({module_name}(-removed|-stubs)?)"))
bp.write(f"""
java_sdk_library_import {{
name: "{module_name}",
owner: "google",
prefer: true,
shared_library: {shared_library},
apex_available: [
"{aosp_apex}",
"test_{aosp_apex}",
],
public: {{
jars: ["sdk_library/public/{module_name}-stubs.jar"],
stub_srcs: ["sdk_library/public/{module_name}.srcjar"],
current_api: "sdk_library/public/{module_name}.txt",
removed_api: "sdk_library/public/{module_name}-removed.txt",
sdk_version: "module_current",
}},
system: {{
jars: ["sdk_library/system/{module_name}-stubs.jar"],
stub_srcs: ["sdk_library/system/{module_name}.srcjar"],
current_api: "sdk_library/system/{module_name}.txt",
removed_api: "sdk_library/system/{module_name}-removed.txt",
sdk_version: "module_current",
}},
module_lib: {{
jars: ["sdk_library/module-lib/{module_name}-stubs.jar"],
stub_srcs: ["sdk_library/module-lib/{module_name}.srcjar"],
current_api: "sdk_library/module-lib/{module_name}.txt",
removed_api: "sdk_library/module-lib/{module_name}-removed.txt",
sdk_version: "module_current",
}},
}}
""")
base_file = os.path.join(r_snapshot_dir, sdk_name + "-current")
shutil.make_archive(base_file, "zip", dest_dir)
return r_snapshot_dir
# A list of the sdk versions to build. Usually just current but can include a
# numeric version too.
@@ -320,6 +415,10 @@ class BuildRelease:
# The position of this instance within the BUILD_RELEASES list.
ordinal: int = dataclasses.field(default=-1, init=False)
# Whether this build release supports the Soong config boilerplate that is
# used to control the prefer setting of modules via a Soong config variable.
supports_soong_config_boilerplate: bool = True
def __post_init__(self):
# The following use object.__setattr__ as this object is frozen and
# attempting to set the fields directly would cause an exception to be
@@ -351,6 +450,13 @@ def create_no_dist_snapshot(_: BuildRelease, __: "SdkDistProducer",
print(f"create_no_dist_snapshot for modules {[m.apex for m in modules]}")
def create_dist_snapshot_for_r(build_release: BuildRelease,
producer: "SdkDistProducer",
modules: List["MainlineModule"]):
"""Generate a snapshot suitable for use in an R build."""
producer.product_dist_for_build_r(build_release, modules)
def create_sdk_snapshots_in_soong(build_release: BuildRelease,
producer: "SdkDistProducer",
modules: List["MainlineModule"]):
@@ -391,9 +497,15 @@ Q = BuildRelease(
)
R = BuildRelease(
name="R",
# At the moment we do not generate a snapshot for R.
creator=create_no_dist_snapshot,
)
# Generate a simple snapshot for R.
creator=create_dist_snapshot_for_r,
# By default a BuildRelease creates an environment to pass to Soong that
# creates a release specific snapshot. However, Soong does not yet (and is
# unlikely to) support building an sdk snapshot for R so create an empty
# environment to pass to Soong instead.
soong_env={},
# R does not support or need Soong config boilerplate.
supports_soong_config_boilerplate=False)
S = BuildRelease(
name="S",
# Generate a snapshot for S using Soong.
@@ -434,6 +546,26 @@ LEGACY_BUILD_RELEASE = BuildRelease(
)
@dataclasses.dataclass(frozen=True)
class SdkLibrary:
"""Information about a java_sdk_library."""
# The name of java_sdk_library module.
name: str
# True if the sdk_library module is a shared library.
shared_library: bool = False
@dataclasses.dataclass(frozen=True)
class ForRBuild:
"""Data structure needed for generating a snapshot for an R build."""
# The java_sdk_library modules to export to the r snapshot.
sdk_libraries: typing.List[SdkLibrary] = dataclasses.field(
default_factory=list)
@dataclasses.dataclass(frozen=True)
class MainlineModule:
"""Represents a mainline module"""
@@ -469,15 +601,19 @@ class MainlineModule:
# The prefix to use for the soong config module types.
configModuleTypePrefix: str = "module_"
def transformations(self):
for_r_build: typing.Optional[ForRBuild] = None
def transformations(self, build_release):
"""Returns the transformations to apply to this module's snapshot(s)."""
return [
SoongConfigBoilerplateInserter(
transformations = []
if build_release.supports_soong_config_boilerplate:
inserter = SoongConfigBoilerplateInserter(
"Android.bp",
configVar=self.configVar,
configModuleTypePrefix=self.configModuleTypePrefix,
configBpDefFile=self.configBpDefFile),
]
configBpDefFile=self.configBpDefFile)
transformations.append(inserter)
return transformations
def is_required_for(self, target_build_release):
"""True if this module is required for the target build release."""
@@ -510,46 +646,80 @@ MAINLINE_MODULES = [
"conscrypt-module-host-exports",
],
first_release=Q,
# No conscrypt java_sdk_library modules are exported to the R snapshot.
# Conscrypt was updatable in R but the generate_ml_bundle.sh does not
# appear to generate a snapshot for it.
for_r_build=None,
),
MainlineModule(
apex="com.android.ipsec",
sdks=["ipsec-module-sdk"],
first_release=S,
first_release=R,
for_r_build=ForRBuild(sdk_libraries=[
SdkLibrary(
name="android.net.ipsec.ike",
shared_library=True,
),
]),
),
MainlineModule(
apex="com.android.media",
sdks=["media-module-sdk"],
first_release=R,
for_r_build=ForRBuild(sdk_libraries=[
SdkLibrary(name="framework-media"),
]),
),
MainlineModule(
apex="com.android.mediaprovider",
sdks=["mediaprovider-module-sdk"],
first_release=R,
for_r_build=ForRBuild(sdk_libraries=[
SdkLibrary(name="framework-mediaprovider"),
]),
),
MainlineModule(
apex="com.android.permission",
sdks=["permission-module-sdk"],
first_release=R,
for_r_build=ForRBuild(sdk_libraries=[
SdkLibrary(name="framework-permission"),
# framework-permission-s is not needed on R as it contains classes
# that are provided in R by non-updatable parts of the
# bootclasspath.
]),
),
MainlineModule(
apex="com.android.sdkext",
sdks=["sdkextensions-sdk"],
first_release=R,
for_r_build=ForRBuild(sdk_libraries=[
SdkLibrary(name="framework-sdkextensions"),
]),
),
MainlineModule(
apex="com.android.os.statsd",
sdks=["statsd-module-sdk"],
first_release=R,
for_r_build=ForRBuild(sdk_libraries=[
SdkLibrary(name="framework-statsd"),
]),
),
MainlineModule(
apex="com.android.tethering",
sdks=["tethering-module-sdk"],
first_release=R,
for_r_build=ForRBuild(sdk_libraries=[
SdkLibrary(name="framework-tethering"),
]),
),
MainlineModule(
apex="com.android.wifi",
sdks=["wifi-module-sdk"],
first_release=R,
for_r_build=ForRBuild(sdk_libraries=[
SdkLibrary(name="framework-wifi"),
]),
),
]
@@ -603,6 +773,19 @@ class SdkDistProducer:
f" build release")
build_release.creator(build_release, self, filtered_modules)
def product_dist_for_build_r(self, build_release, modules):
# Although we only need a subset of the files that a java_sdk_library
# adds to an sdk snapshot generating the whole snapshot is the simplest
# way to ensure that all the necessary files are produced.
sdk_versions = build_release.sdk_versions
# Filter out any modules that do not provide sdk for R.
modules = [m for m in modules if m.for_r_build]
snapshot_dir = self.snapshot_builder.build_snapshots_for_build_r(
build_release, sdk_versions, modules)
self.populate_dist(build_release, sdk_versions, modules, snapshot_dir)
def produce_dist_for_build_release(self, build_release, modules):
sdk_versions = build_release.sdk_versions
snapshots_dir = self.snapshot_builder.build_snapshots(
@@ -631,8 +814,9 @@ class SdkDistProducer:
sdk_version, apex, subdir)
sdk_path = sdk_snapshot_zip_file(snapshots_dir, sdk,
sdk_version)
transformations = module.transformations(build_release)
self.dist_sdk_snapshot_zip(sdk_path, sdk_dist_dir,
module.transformations())
transformations)
def dist_sdk_snapshot_zip(self, src_sdk_zip, sdk_dist_dir, transformations):
"""Copy the sdk snapshot zip file to a dist directory.
@@ -728,14 +912,19 @@ def apply_transformations(producer, tmp_dir, transformations):
os.utime(path, (modified, modified))
def create_producer():
def create_producer(tool_path):
# Variables initialized from environment variables that are set by the
# calling mainline_modules_sdks.sh.
out_dir = os.environ["OUT_DIR"]
dist_dir = os.environ["DIST_DIR"]
top_dir = os.environ["ANDROID_BUILD_TOP"]
tool_path = os.path.relpath(tool_path, top_dir)
tool_path = tool_path.replace(".py", ".sh")
subprocess_runner = SubprocessRunner()
snapshot_builder = SnapshotBuilder(
tool_path=tool_path,
subprocess_runner=subprocess_runner,
out_dir=out_dir,
)
@@ -758,6 +947,11 @@ def aosp_to_google_name(name):
return name.replace("com.android.", "com.google.android.")
def google_to_aosp_name(name):
"""Transform a Google module name into an AOSP module name"""
return name.replace("com.google.android.", "com.android.")
def filter_modules(modules):
target_build_apps = os.environ.get("TARGET_BUILD_APPS")
if target_build_apps:
@@ -774,6 +968,11 @@ def main(args):
args_parser = argparse.ArgumentParser(
description="Build snapshot zips for consumption by Gantry.")
args_parser.add_argument(
"--tool-path",
help="The path to this tool.",
default="unspecified",
)
args_parser.add_argument(
"--build-release",
action="append",
@@ -790,7 +989,7 @@ def main(args):
if b.name.lower() in selected_build_releases
]
producer = create_producer()
producer = create_producer(args.tool_path)
modules = filter_modules(MAINLINE_MODULES)
producer.produce_dist(modules, build_releases)