diff --git a/build/Android.bp b/build/Android.bp new file mode 100644 index 0000000..491949f --- /dev/null +++ b/build/Android.bp @@ -0,0 +1,35 @@ +// Copyright (C) 2021 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. + +python_test_host { + name: "mainline_modules_sdks_test", + main: "mainline_modules_sdks_test.py", + srcs: [ + "mainline_modules_sdks_test.py", + "mainline_modules_sdks.py", + ], + data: [ + "mainline_modules_sdks_test_data/**/*", + ], + version: { + py2: { + enabled: false, + }, + py3: { + enabled: true, + embedded_launcher: true, + }, + }, + test_suites: ["general-tests"], +} diff --git a/build/mainline_modules_sdks.py b/build/mainline_modules_sdks.py new file mode 100755 index 0000000..c6be7d4 --- /dev/null +++ b/build/mainline_modules_sdks.py @@ -0,0 +1,529 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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. + +"""Builds SDK snapshots. + +If the environment variable TARGET_BUILD_APPS is nonempty then only the SDKs for +the APEXes in it are built, otherwise all configured SDKs are built. +""" + +import dataclasses +import io +import os +import re +import shutil +import subprocess +import sys +import tempfile +import zipfile + + +@dataclasses.dataclass(frozen=True) +class ConfigVar: + """Represents a Soong configuration variable""" + # The config variable namespace, e.g. ANDROID. + namespace: str + + # The name of the variable within the namespace. + name: str + + +@dataclasses.dataclass(frozen=True) +class FileTransformation: + """Performs a transformation on a file within an SDK snapshot zip file.""" + + # The path of the file within the SDK snapshot zip file. + path: str + + def apply(self, producer, path): + """Apply the transformation to the src_path to produce the dest_path.""" + raise NotImplementedError + + +@dataclasses.dataclass(frozen=True) +class SoongConfigBoilerplateInserter(FileTransformation): + """Transforms an Android.bp file to add soong config boilerplate. + + The boilerplate allows the prefer setting of the modules to be controlled + through a Soong configuration variable. + """ + + # The configuration variable that will control the prefer setting. + configVar: ConfigVar + + # The bp file containing the definitions of the configuration module types + # to use in the sdk. + configBpDefFile: str + + # The prefix to use for the soong config module types. + configModuleTypePrefix: str + + def apply(self, producer, path): + with open(path, "r+") as file: + self._apply_transformation(producer, file) + + def _apply_transformation(self, producer, file): + # TODO(b/174997203): Remove this when we have a proper way to control + # prefer flags in Mainline modules. + + header_lines = [] + for line in file: + line = line.rstrip("\n") + if not line.startswith("//"): + break + header_lines.append(line) + + config_module_types = set() + + content_lines = [] + for line in file: + line = line.rstrip("\n") + + # Check to see whether the line is the start of a new module type, + # e.g. { + module_header = re.match("([a-z0-9_]+) +{$", line) + if not module_header: + # It is not so just add the line to the output and skip to the + # next line. + content_lines.append(line) + continue + + module_type = module_header.group(1) + module_content = [] + + # Iterate over the Soong module contents + for module_line in file: + module_line = module_line.rstrip("\n") + + # When the end of the module has been reached then exit. + if module_line == "}": + break + + # Check to see if the module is an unversioned module, i.e. + # without @. If it is then it needs to have the soong + # config boilerplate added to control the setting of the prefer + # property. Versioned modules do not need that because they are + # never preferred. + # At the moment this differentiation between versioned and + # unversioned relies on the fact that the unversioned modules + # set "prefer: false", while the versioned modules do not. That + # is a little bit fragile so may require some additional checks. + if module_line != " prefer: false,": + # The line does not indicate that the module needs the + # soong config boilerplate so add the line and skip to the + # next one. + module_content.append(module_line) + continue + + # Add the soong config boilerplate instead of the line: + # prefer: false, + namespace = self.configVar.namespace + name = self.configVar.name + module_content.append(f"""\ + // Do not prefer prebuilt if SOONG_CONFIG_{namespace}_{name} is true. + prefer: true, + soong_config_variables: {{ + {name}: {{ + prefer: false, + }}, + }},""") + + # Change the module type to the corresponding soong config + # module type by adding the prefix. + module_type = self.configModuleTypePrefix + module_type + # Add the module type to the list of module types that need to + # be imported into the bp file. + config_module_types.add(module_type) + + # Generate the module, possibly with the new module type and + # containing the + content_lines.append(module_type + " {") + content_lines.extend(module_content) + content_lines.append("}") + + # Add the soong_config_module_type_import module definition that imports + # the soong config module types into this bp file to the header lines so + # that they appear before any uses. + module_types = "\n".join( + [f' "{mt}",' for mt in sorted(config_module_types)]) + header_lines.append(f""" +// Soong config variable stanza added by {producer.script}. +soong_config_module_type_import {{ + from: "{self.configBpDefFile}", + module_types: [ +{module_types} + ], +}} +""") + + # Overwrite the file with the updated contents. + file.seek(0) + file.truncate() + file.write("\n".join(header_lines + content_lines) + "\n") + + +@dataclasses.dataclass(frozen=True) +class MainlineModule: + """Represents a mainline module""" + # The name of the apex. + apex: str + + # The names of the sdk and module_exports. + sdks: list[str] + + # The configuration variable, defaults to ANDROID:module_build_from_source + configVar: ConfigVar = ConfigVar( + namespace="ANDROID", + name="module_build_from_source", + ) + + # The bp file containing the definitions of the configuration module types + # to use in the sdk. + configBpDefFile: str = "packages/modules/common/Android.bp" + + # The prefix to use for the soong config module types. + configModuleTypePrefix: str = "module_" + + def transformations(self): + """Returns the transformations to apply to this module's snapshot(s).""" + return [ + SoongConfigBoilerplateInserter( + "Android.bp", + configVar=self.configVar, + configModuleTypePrefix=self.configModuleTypePrefix, + configBpDefFile=self.configBpDefFile), + ] + + +# List of mainline modules. +MAINLINE_MODULES = [ + MainlineModule( + apex="com.android.art", + sdks=[ + "art-module-sdk", + "art-module-test-exports", + "art-module-host-exports", + ], + # Override the config... fields. + configVar=ConfigVar( + namespace="art_module", + name="source_build", + ), + configBpDefFile="prebuilts/module_sdk/art/SoongConfig.bp", + configModuleTypePrefix="art_prebuilt_", + ), + MainlineModule( + apex="com.android.conscrypt", + sdks=[ + "conscrypt-module-sdk", + "conscrypt-module-test-exports", + "conscrypt-module-host-exports", + ], + ), + MainlineModule( + apex="com.android.ipsec", + sdks=["ipsec-module-sdk"], + ), + MainlineModule( + apex="com.android.media", + sdks=["media-module-sdk"], + ), + MainlineModule( + apex="com.android.mediaprovider", + sdks=["mediaprovider-module-sdk"], + ), + MainlineModule( + apex="com.android.permission", + sdks=["permission-module-sdk"], + ), + MainlineModule( + apex="com.android.sdkext", + sdks=["sdkextensions-sdk"], + ), + MainlineModule( + apex="com.android.os.statsd", + sdks=["statsd-module-sdk"], + ), + MainlineModule( + apex="com.android.tethering", + sdks=["tethering-module-sdk"], + ), + MainlineModule( + apex="com.android.wifi", + sdks=["wifi-module-sdk"], + ), +] + +# Only used by the test. +MAINLINE_MODULES_BY_APEX = dict((m.apex, m) for m in MAINLINE_MODULES) + +# A list of the sdk versions to build. Usually just current but can include a +# numeric version too. +SDK_VERSIONS = [ + # Suitable for overriding the source modules with prefer:true. + # Unlike "unversioned" this mode also adds "@current" suffixed modules + # with the same prebuilts (which are never preferred). + "current", +] + + +@dataclasses.dataclass +class SdkDistProducer: + """Produces the DIST_DIR/mainline-sdks and DIST_DIR/stubs directories. + + Builds SDK snapshots for mainline modules and then copies them into the + DIST_DIR/mainline-sdks directory. Also extracts the sdk_library txt, jar and + srcjar files from each SDK snapshot and copies them into the DIST_DIR/stubs + directory. + """ + + # Destination for stdout from subprocesses. + # + # This (and the following stderr) are needed to allow the tests to be run + # in Intellij. This ensures that the tests are run with stdout/stderr + # objects that work when passed to subprocess.run(stdout/stderr). Without it + # the tests are run with a FlushingStringIO object that has no fileno + # attribute - https://youtrack.jetbrains.com/issue/PY-27883. + stdout: io.TextIOBase = sys.stdout + + # Destination for stderr from subprocesses. + stderr: io.TextIOBase = sys.stderr + + # The OUT_DIR environment variable. + out_dir: str = "uninitialized-out" + + # The DIST_DIR environment variable. + dist_dir: str = "uninitialized-dist" + + # The path to this script. It may be inserted into files that are + # transformed to document where the changes came from. + script: str = sys.argv[0] + + def get_sdk_path(self, sdk_name, sdk_version): + """Get the path to the sdk snapshot zip file produced by soong""" + return os.path.join(self.out_dir, "soong/mainline-sdks", + f"{sdk_name}-{sdk_version}.zip") + + def produce_dist(self, sdk_versions, modules): + self.build_sdks(sdk_versions, modules) + self.populate_dist(sdk_versions, modules) + + def build_sdks(self, sdk_versions, modules): + # Build the SDKs once for each version. + for sdk_version in sdk_versions: + # Compute the paths to all the Soong generated sdk snapshot files + # required by this script. + paths = [ + self.get_sdk_path(sdk, sdk_version) + for module in modules + for sdk in module.sdks + ] + + # TODO(ngeoffray): remove SOONG_ALLOW_MISSING_DEPENDENCIES, but we + # currently break without it. + # + # Set SOONG_SDK_SNAPSHOT_USE_SRCJAR to generate .srcjars inside sdk + # zip files as expected by prebuilt drop. + extraEnv = { + "SOONG_ALLOW_MISSING_DEPENDENCIES": "true", + "SOONG_SDK_SNAPSHOT_USE_SRCJAR": "true", + "SOONG_SDK_SNAPSHOT_VERSION": sdk_version, + } + # Unless explicitly specified in the calling environment set + # TARGET_BUILD_VARIANT=user. + # This MUST be identical to the TARGET_BUILD_VARIANT used to build + # the corresponding APEXes otherwise it could result in different + # hidden API flags, see http://b/202398851#comment29 for more info. + targetBuildVariant = os.environ.get("TARGET_BUILD_VARIANT", "user") + cmd = [ + "build/soong/soong_ui.bash", + "--make-mode", + "--soong-only", + f"TARGET_BUILD_VARIANT={targetBuildVariant}", + "TARGET_PRODUCT=mainline_sdk", + "MODULE_BUILD_FROM_SOURCE=true", + "out/soong/apex/depsinfo/new-allowed-deps.txt.check", + ] + paths + print_command(extraEnv, cmd) + env = os.environ.copy() + env.update(extraEnv) + subprocess.run( + cmd, + env=env, + check=True, + stdout=self.stdout, + stderr=self.stderr) + + def unzip_current_stubs(self, sdk_name, apex_name): + """Unzips stubs for "current" into {producer.dist_dir}/stubs/{apex}.""" + sdk_path = self.get_sdk_path(sdk_name, "current") + dest_dir = os.path.join(self.dist_dir, "stubs", apex_name) + print( + f"Extracting java_sdk_library files from {sdk_path} to {dest_dir}") + os.makedirs(dest_dir, exist_ok=True) + extract_matching_files_from_zip( + sdk_path, dest_dir, r"sdk_library/[^/]+/[^/]+\.(txt|jar|srcjar)") + + def populate_dist(self, sdk_versions, modules): + # TODO(b/199759953): Remove stubs once it is no longer used by gantry. + # Clear and populate the stubs directory. + stubs_dir = os.path.join(self.dist_dir, "stubs") + shutil.rmtree(stubs_dir, ignore_errors=True) + + for module in modules: + apex = module.apex + for sdk in module.sdks: + # If the sdk's name ends with -sdk then extract sdk library + # related files from its zip file. + if sdk.endswith("-sdk"): + self.unzip_current_stubs(sdk, apex) + + # Clear and populate the mainline-sdks dist directory. + sdks_dist_dir = os.path.join(self.dist_dir, "mainline-sdks") + shutil.rmtree(sdks_dist_dir, ignore_errors=True) + + for module in modules: + apex = module.apex + for sdk_version in sdk_versions: + for sdk in module.sdks: + subdir = re.sub("^[^-]+-(module-)?", "", sdk) + if subdir not in ("sdk", "host-exports", "test-exports"): + raise Exception( + f"{sdk} is not a valid name, expected name in the" + f" format of" + f" ^[^-]+-(module-)?(sdk|host-exports|test-exports)" + ) + + sdk_dist_dir = os.path.join(sdks_dist_dir, sdk_version, + apex, subdir) + sdk_path = self.get_sdk_path(sdk, sdk_version) + self.dist_sdk_snapshot_zip(sdk_path, sdk_dist_dir, + module.transformations()) + + def dist_sdk_snapshot_zip(self, src_sdk_zip, sdk_dist_dir, transformations): + """Copy the sdk snapshot zip file to a dist directory. + + If no transformations are provided then this simply copies the show sdk + snapshot zip file to the dist dir. However, if transformations are + provided then the files to be transformed are extracted from the + snapshot zip file, they are transformed to files in a separate directory + and then a new zip file is created in the dist directory with the + original files replaced by the newly transformed files. + """ + os.makedirs(sdk_dist_dir) + dest_sdk_zip = os.path.join(sdk_dist_dir, os.path.basename(src_sdk_zip)) + print(f"Copying sdk snapshot {src_sdk_zip} to {dest_sdk_zip}") + + # If no transformations are provided then just copy the zip file + # directly. + if len(transformations) == 0: + shutil.copy(src_sdk_zip, sdk_dist_dir) + return + + with tempfile.TemporaryDirectory() as tmp_dir: + # Create a single pattern that will match any of the paths provided + # in the transformations. + pattern = "|".join( + [f"({re.escape(t.path)})" for t in transformations]) + + # Extract the matching files from the zip into the temporary + # directory. + extract_matching_files_from_zip(src_sdk_zip, tmp_dir, pattern) + + # Apply the transformations to the extracted files in situ. + apply_transformations(self, tmp_dir, transformations) + + # Replace the original entries in the zip with the transformed + # files. + paths = [transformation.path for transformation in transformations] + copy_zip_and_replace(self, src_sdk_zip, dest_sdk_zip, tmp_dir, + paths) + + +def print_command(env, cmd): + print(" ".join([f"{name}={value}" for name, value in env.items()] + cmd)) + + +def extract_matching_files_from_zip(zip_path, dest_dir, pattern): + """Extracts files from a zip file into a destination directory. + + The extracted files are those that match the specified regular expression + pattern. + """ + with zipfile.ZipFile(zip_path) as zip_file: + for filename in zip_file.namelist(): + if re.match(pattern, filename): + zip_file.extract(filename, dest_dir) + + +def copy_zip_and_replace(producer, src_zip_path, dest_zip_path, src_dir, paths): + """Copies a zip replacing some of its contents in the process. + + The files to replace are specified by the paths parameter and are relative + to the src_dir. + """ + # Get the absolute paths of the source and dest zip files so that they are + # not affected by a change of directory. + abs_src_zip_path = os.path.abspath(src_zip_path) + abs_dest_zip_path = os.path.abspath(dest_zip_path) + subprocess.run( + ["zip", "-q", abs_src_zip_path, "--out", abs_dest_zip_path] + paths, + # Change into the source directory before running zip. + cwd=src_dir, + stdout=producer.stdout, + stderr=producer.stderr, + check=True) + + +def apply_transformations(producer, tmp_dir, transformations): + for transformation in transformations: + path = os.path.join(tmp_dir, transformation.path) + + # Record the timestamp of the file. + modified = os.path.getmtime(path) + + # Transform the file. + transformation.apply(producer, path) + + # Reset the timestamp of the file to the original timestamp before the + # transformation was applied. + os.utime(path, (modified, modified)) + + +def main(): + """Program entry point.""" + if not os.path.exists("build/make/core/Makefile"): + sys.exit("This script must be run from the top of the tree.") + + producer = SdkDistProducer( + # 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"], + ) + + target_build_apps = os.environ.get("TARGET_BUILD_APPS") + if target_build_apps: + build_mainline_modules = [m for m in MAINLINE_MODULES + if m.apex in target_build_apps.split()] + else: + build_mainline_modules = MAINLINE_MODULES + + producer.produce_dist(SDK_VERSIONS, build_mainline_modules) + + +if __name__ == "__main__": + main() diff --git a/build/mainline_modules_sdks.sh b/build/mainline_modules_sdks.sh new file mode 100755 index 0000000..2cf17f3 --- /dev/null +++ b/build/mainline_modules_sdks.sh @@ -0,0 +1,40 @@ +#!/bin/bash -ex +# +# Copyright (C) 2021 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. +# + +function main() { + if [ ! -e "build/make/core/Makefile" ]; then + echo "$0 must be run from the top of the tree" + exit 1 + fi + + # Assign to a variable and eval that, since bash ignores any error status from + # the command substitution if it's directly on the eval line. + vars="$(TARGET_PRODUCT='' build/soong/soong_ui.bash --dumpvars-mode \ + --vars="BUILD_NUMBER DIST_DIR OUT_DIR")" + eval "${vars}" + + # Building with --soong-only and module products requires build_number.txt for + # some targets. + echo -n "${BUILD_NUMBER}" > "${OUT_DIR}"/soong/build_number.txt + + # Delegate the SDK generation to the python script. Use the python version + # provided by the build to ensure consistency across build environments. + export DIST_DIR OUT_DIR + prebuilts/build-tools/linux-x86/bin/py3-cmd packages/modules/common/build/mainline_modules_sdks.py "$@" +} + +main "${@}" diff --git a/build/mainline_modules_sdks_test.py b/build/mainline_modules_sdks_test.py new file mode 100644 index 0000000..8a54ab8 --- /dev/null +++ b/build/mainline_modules_sdks_test.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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. +"""Unit tests for mainline_modules_sdks.py.""" + +from pathlib import Path +import os +import tempfile +import unittest +import zipfile + +import mainline_modules_sdks as mm + + +class TestPopulateDist(unittest.TestCase): + + def create_snapshot_file(self, out_dir, name, version): + sdks_out_dir = Path(out_dir, "soong/mainline-sdks") + sdks_out_dir.mkdir(parents=True, exist_ok=True) + zip_file = Path(sdks_out_dir, f"{name}-{version}.zip") + with zipfile.ZipFile(zip_file, "w") as z: + z.writestr("Android.bp", "") + if name.endswith("-sdk"): + z.writestr("sdk_library/public/removed.txt", "") + z.writestr("sdk_library/public/source.srcjar", "") + z.writestr("sdk_library/public/lib.jar", "") + z.writestr("sdk_library/public/api.txt", "") + + def test(self): + """Verify the dist/mainline-sdks directory is populated correctly""" + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_out_dir = os.path.join(tmp_dir, "out") + os.mkdir(tmp_out_dir) + tmp_dist_dir = os.path.join(tmp_dir, "dist") + os.mkdir(tmp_dist_dir) + + modules = [ + mm.MAINLINE_MODULES_BY_APEX["com.android.art"], + mm.MAINLINE_MODULES_BY_APEX["com.android.ipsec"], + ] + + # Create input file structure. + for module in modules: + for sdk in module.sdks: + self.create_snapshot_file(tmp_out_dir, sdk, "current") + + producer = mm.SdkDistProducer( + out_dir=tmp_out_dir, + dist_dir=tmp_dist_dir, + ) + + sdk_versions = ["current"] + producer.populate_dist(sdk_versions, modules) + + files = [] + for abs_dir, _, filenames in os.walk(tmp_dist_dir): + rel_dir = os.path.relpath(abs_dir, tmp_dist_dir) + for f in filenames: + files.append(os.path.join(rel_dir, f)) + # pylint: disable=line-too-long + self.assertEqual([ + "mainline-sdks/current/com.android.art/host-exports/art-module-host-exports-current.zip", + "mainline-sdks/current/com.android.art/sdk/art-module-sdk-current.zip", + "mainline-sdks/current/com.android.art/test-exports/art-module-test-exports-current.zip", + "mainline-sdks/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip", + "stubs/com.android.art/sdk_library/public/api.txt", + "stubs/com.android.art/sdk_library/public/lib.jar", + "stubs/com.android.art/sdk_library/public/removed.txt", + "stubs/com.android.art/sdk_library/public/source.srcjar", + "stubs/com.android.ipsec/sdk_library/public/api.txt", + "stubs/com.android.ipsec/sdk_library/public/lib.jar", + "stubs/com.android.ipsec/sdk_library/public/removed.txt", + "stubs/com.android.ipsec/sdk_library/public/source.srcjar", + ], sorted(files)) + + +def pathToTestData(relative_path): + """Construct a path to a test data file. + + The relative_path is relative to the location of this file. + """ + this_file = __file__ + # When running as a python_test_host (name=) with an embedded launcher + # the __file__ points to ...//.py but the .../ is not a directory + # it is a binary with the launcher and the python file embedded inside. In + # that case a test data file is at .../_data/, not + # ...//_data/ so it is necessary to trim the base name (.py) + # from the file. + if not os.path.isfile(this_file): + this_file = os.path.dirname(this_file) + # When the python file is at .../.py (or in the case of an embedded + # launcher at ...//.py) then the test data is at .../_data/. + this_file_without_ext, _ = os.path.splitext(this_file) + return os.path.join(this_file_without_ext + "_data", relative_path) + + +def readTestData(relative_path): + with open(pathToTestData(relative_path), "r") as f: + return f.read() + + +class TestSoongConfigBoilerplateInserter(unittest.TestCase): + + def apply_transformations(self, src, transformations, expected): + producer = mm.SdkDistProducer(script=self._testMethodName) + + with tempfile.TemporaryDirectory() as tmp_dir: + path = os.path.join(tmp_dir, "Android.bp") + with open(path, "w") as f: + f.write(src) + + mm.apply_transformations(producer, tmp_dir, transformations) + + with open(path, "r") as f: + result = f.read() + + self.maxDiff = None + self.assertEqual(expected, result) + + def test_common_mainline_module(self): + """Tests the transformations applied to a common mainline module. + + This uses ipsec as an example of a common mainline module. This checks + that the correct Soong config module types and variables are used and + that it imports the definitions from the correct location. + """ + src = readTestData("ipsec_Android.bp.input") + + expected = readTestData("ipsec_Android.bp.expected") + + module = mm.MAINLINE_MODULES_BY_APEX["com.android.ipsec"] + transformations = module.transformations() + + self.apply_transformations(src, transformations, expected) + + def test_art(self): + """Tests the transformations applied to a the ART mainline module. + + The ART mainline module uses a different Soong config setup to the + common mainline modules. This checks that the ART specific Soong config + module types, variable and imports are used. + """ + src = readTestData("art_Android.bp.input") + + expected = readTestData("art_Android.bp.expected") + + module = mm.MAINLINE_MODULES_BY_APEX["com.android.art"] + transformations = module.transformations() + + self.apply_transformations(src, transformations, expected) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/build/mainline_modules_sdks_test_data/OWNERS b/build/mainline_modules_sdks_test_data/OWNERS new file mode 100644 index 0000000..3c17a96 --- /dev/null +++ b/build/mainline_modules_sdks_test_data/OWNERS @@ -0,0 +1,3 @@ +hansson@google.com +paulduffin@google.com +satayev@google.com diff --git a/build/mainline_modules_sdks_test_data/art_Android.bp.expected b/build/mainline_modules_sdks_test_data/art_Android.bp.expected new file mode 100644 index 0000000..1d6f558 --- /dev/null +++ b/build/mainline_modules_sdks_test_data/art_Android.bp.expected @@ -0,0 +1,107 @@ +// This is auto-generated. DO NOT EDIT. + +// Soong config variable stanza added by test_art. +soong_config_module_type_import { + from: "prebuilts/module_sdk/art/SoongConfig.bp", + module_types: [ + "art_prebuilt_java_import", + "art_prebuilt_prebuilt_bootclasspath_fragment", + "art_prebuilt_prebuilt_platform_compat_config", + ], +} + +package { + // A default list here prevents the license LSC from adding its own list which would + // be unnecessary as every module in the sdk already has its own licenses property. + default_applicable_licenses: ["Android-Apache-2.0"], +} + +art_prebuilt_prebuilt_bootclasspath_fragment { + name: "art-bootclasspath-fragment", + // Do not prefer prebuilt if SOONG_CONFIG_art_module_source_build is true. + prefer: true, + soong_config_variables: { + source_build: { + prefer: false, + }, + }, + visibility: [ + "//art/build/apex", + "//art/build/boot", + "//art/build/sdk", + "//prebuilts:__subpackages__", + ], + apex_available: [ + "com.android.art", + ], + licenses: ["art-module-sdk_art_license"], + image_name: "art", + contents: [ + "core-oj", + "core-libart", + "okhttp", + "bouncycastle", + "apache-xml", + ], + api: { + stub_libs: ["art.module.public.api"], + }, + core_platform_api: { + stub_libs: ["art.module.public.api.stubs.module_lib"], + }, + hidden_api: { + max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], + blocked: ["hiddenapi/hiddenapi-blocked.txt"], + unsupported_packages: ["hiddenapi/hiddenapi-unsupported-packages.txt"], + stub_flags: "hiddenapi/stub-flags.csv", + annotation_flags: "hiddenapi/annotation-flags.csv", + metadata: "hiddenapi/metadata.csv", + index: "hiddenapi/index.csv", + all_flags: "hiddenapi/all-flags.csv", + }, +} + +art_prebuilt_prebuilt_platform_compat_config { + name: "libcore-platform-compat-config", + // Do not prefer prebuilt if SOONG_CONFIG_art_module_source_build is true. + prefer: true, + soong_config_variables: { + source_build: { + prefer: false, + }, + }, + visibility: [ + "//art/build/apex", + "//art/build/sdk", + "//libcore", + "//prebuilts:__subpackages__", + ], + licenses: ["art-module-sdk_libcore_license"], + metadata: "compat_configs/libcore-platform-compat-config/libcore-platform-compat-config_meta.xml", +} + +art_prebuilt_java_import { + name: "core-oj", + // Do not prefer prebuilt if SOONG_CONFIG_art_module_source_build is true. + prefer: true, + soong_config_variables: { + source_build: { + prefer: false, + }, + }, + visibility: [ + "//art/build/apex", + "//art/build/sdk", + "//external/wycheproof", + "//libcore", + "//libcore/benchmarks", + "//packages/modules/ArtPrebuilt", + "//prebuilts:__subpackages__", + ], + apex_available: [ + "com.android.art", + "com.android.art.debug", + ], + licenses: ["art-module-sdk_libcore_license"], + jars: ["java/core-oj.jar"], +} diff --git a/build/mainline_modules_sdks_test_data/art_Android.bp.input b/build/mainline_modules_sdks_test_data/art_Android.bp.input new file mode 100644 index 0000000..97fa5f5 --- /dev/null +++ b/build/mainline_modules_sdks_test_data/art_Android.bp.input @@ -0,0 +1,79 @@ +// This is auto-generated. DO NOT EDIT. + +package { + // A default list here prevents the license LSC from adding its own list which would + // be unnecessary as every module in the sdk already has its own licenses property. + default_applicable_licenses: ["Android-Apache-2.0"], +} + +prebuilt_bootclasspath_fragment { + name: "art-bootclasspath-fragment", + prefer: false, + visibility: [ + "//art/build/apex", + "//art/build/boot", + "//art/build/sdk", + "//prebuilts:__subpackages__", + ], + apex_available: [ + "com.android.art", + ], + licenses: ["art-module-sdk_art_license"], + image_name: "art", + contents: [ + "core-oj", + "core-libart", + "okhttp", + "bouncycastle", + "apache-xml", + ], + api: { + stub_libs: ["art.module.public.api"], + }, + core_platform_api: { + stub_libs: ["art.module.public.api.stubs.module_lib"], + }, + hidden_api: { + max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"], + blocked: ["hiddenapi/hiddenapi-blocked.txt"], + unsupported_packages: ["hiddenapi/hiddenapi-unsupported-packages.txt"], + stub_flags: "hiddenapi/stub-flags.csv", + annotation_flags: "hiddenapi/annotation-flags.csv", + metadata: "hiddenapi/metadata.csv", + index: "hiddenapi/index.csv", + all_flags: "hiddenapi/all-flags.csv", + }, +} + +prebuilt_platform_compat_config { + name: "libcore-platform-compat-config", + prefer: false, + visibility: [ + "//art/build/apex", + "//art/build/sdk", + "//libcore", + "//prebuilts:__subpackages__", + ], + licenses: ["art-module-sdk_libcore_license"], + metadata: "compat_configs/libcore-platform-compat-config/libcore-platform-compat-config_meta.xml", +} + +java_import { + name: "core-oj", + prefer: false, + visibility: [ + "//art/build/apex", + "//art/build/sdk", + "//external/wycheproof", + "//libcore", + "//libcore/benchmarks", + "//packages/modules/ArtPrebuilt", + "//prebuilts:__subpackages__", + ], + apex_available: [ + "com.android.art", + "com.android.art.debug", + ], + licenses: ["art-module-sdk_libcore_license"], + jars: ["java/core-oj.jar"], +} diff --git a/build/mainline_modules_sdks_test_data/ipsec_Android.bp.expected b/build/mainline_modules_sdks_test_data/ipsec_Android.bp.expected new file mode 100644 index 0000000..4586eac --- /dev/null +++ b/build/mainline_modules_sdks_test_data/ipsec_Android.bp.expected @@ -0,0 +1,160 @@ +// This is auto-generated. DO NOT EDIT. + +// Soong config variable stanza added by test_common_mainline_module. +soong_config_module_type_import { + from: "packages/modules/common/Android.bp", + module_types: [ + "module_java_sdk_library_import", + "module_prebuilt_bootclasspath_fragment", + ], +} + +package { + // A default list here prevents the license LSC from adding its own list which would + // be unnecessary as every module in the sdk already has its own licenses property. + default_applicable_licenses: ["Android-Apache-2.0"], +} + +prebuilt_bootclasspath_fragment { + name: "ipsec-module-sdk_com.android.ipsec-bootclasspath-fragment@current", + sdk_member_name: "com.android.ipsec-bootclasspath-fragment", + visibility: ["//visibility:public"], + apex_available: ["com.android.ipsec"], + licenses: ["ipsec-module-sdk_Android-Apache-2.0@current"], + contents: ["ipsec-module-sdk_android.net.ipsec.ike@current"], + hidden_api: { + stub_flags: "hiddenapi/stub-flags.csv", + annotation_flags: "hiddenapi/annotation-flags.csv", + metadata: "hiddenapi/metadata.csv", + index: "hiddenapi/index.csv", + all_flags: "hiddenapi/all-flags.csv", + }, +} + +module_prebuilt_bootclasspath_fragment { + name: "com.android.ipsec-bootclasspath-fragment", + // Do not prefer prebuilt if SOONG_CONFIG_ANDROID_module_build_from_source is true. + prefer: true, + soong_config_variables: { + module_build_from_source: { + prefer: false, + }, + }, + visibility: ["//visibility:public"], + apex_available: ["com.android.ipsec"], + licenses: ["ipsec-module-sdk_Android-Apache-2.0"], + contents: ["android.net.ipsec.ike"], + hidden_api: { + stub_flags: "hiddenapi/stub-flags.csv", + annotation_flags: "hiddenapi/annotation-flags.csv", + metadata: "hiddenapi/metadata.csv", + index: "hiddenapi/index.csv", + all_flags: "hiddenapi/all-flags.csv", + }, +} + +java_sdk_library_import { + name: "ipsec-module-sdk_android.net.ipsec.ike@current", + sdk_member_name: "android.net.ipsec.ike", + visibility: ["//visibility:public"], + apex_available: [ + "com.android.ipsec", + "test_com.android.ipsec", + ], + licenses: ["ipsec-module-sdk_Android-Apache-2.0@current"], + shared_library: true, + compile_dex: true, + permitted_packages: [ + "com.android.internal.net", + "android.net.ipsec.ike", + "android.net.eap", + ], + public: { + jars: ["sdk_library/public/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/public/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/public/android.net.ipsec.ike.txt", + removed_api: "sdk_library/public/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, + system: { + jars: ["sdk_library/system/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/system/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/system/android.net.ipsec.ike.txt", + removed_api: "sdk_library/system/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, + module_lib: { + jars: ["sdk_library/module-lib/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/module-lib/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/module-lib/android.net.ipsec.ike.txt", + removed_api: "sdk_library/module-lib/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, +} + +module_java_sdk_library_import { + name: "android.net.ipsec.ike", + // Do not prefer prebuilt if SOONG_CONFIG_ANDROID_module_build_from_source is true. + prefer: true, + soong_config_variables: { + module_build_from_source: { + prefer: false, + }, + }, + visibility: ["//visibility:public"], + apex_available: [ + "com.android.ipsec", + "test_com.android.ipsec", + ], + licenses: ["ipsec-module-sdk_Android-Apache-2.0"], + shared_library: true, + compile_dex: true, + permitted_packages: [ + "com.android.internal.net", + "android.net.ipsec.ike", + "android.net.eap", + ], + public: { + jars: ["sdk_library/public/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/public/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/public/android.net.ipsec.ike.txt", + removed_api: "sdk_library/public/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, + system: { + jars: ["sdk_library/system/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/system/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/system/android.net.ipsec.ike.txt", + removed_api: "sdk_library/system/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, + module_lib: { + jars: ["sdk_library/module-lib/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/module-lib/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/module-lib/android.net.ipsec.ike.txt", + removed_api: "sdk_library/module-lib/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, +} + +license { + name: "ipsec-module-sdk_Android-Apache-2.0@current", + sdk_member_name: "Android-Apache-2.0", + visibility: ["//visibility:private"], + license_kinds: ["SPDX-license-identifier-Apache-2.0"], + license_text: ["licenses/build/soong/licenses/LICENSE"], +} + +license { + name: "ipsec-module-sdk_Android-Apache-2.0", + visibility: ["//visibility:private"], + license_kinds: ["SPDX-license-identifier-Apache-2.0"], + license_text: ["licenses/build/soong/licenses/LICENSE"], +} + +sdk_snapshot { + name: "ipsec-module-sdk@current", + visibility: ["//visibility:public"], + bootclasspath_fragments: ["ipsec-module-sdk_com.android.ipsec-bootclasspath-fragment@current"], + java_sdk_libs: ["ipsec-module-sdk_android.net.ipsec.ike@current"], +} diff --git a/build/mainline_modules_sdks_test_data/ipsec_Android.bp.input b/build/mainline_modules_sdks_test_data/ipsec_Android.bp.input new file mode 100644 index 0000000..124fb56 --- /dev/null +++ b/build/mainline_modules_sdks_test_data/ipsec_Android.bp.input @@ -0,0 +1,139 @@ +// This is auto-generated. DO NOT EDIT. + +package { + // A default list here prevents the license LSC from adding its own list which would + // be unnecessary as every module in the sdk already has its own licenses property. + default_applicable_licenses: ["Android-Apache-2.0"], +} + +prebuilt_bootclasspath_fragment { + name: "ipsec-module-sdk_com.android.ipsec-bootclasspath-fragment@current", + sdk_member_name: "com.android.ipsec-bootclasspath-fragment", + visibility: ["//visibility:public"], + apex_available: ["com.android.ipsec"], + licenses: ["ipsec-module-sdk_Android-Apache-2.0@current"], + contents: ["ipsec-module-sdk_android.net.ipsec.ike@current"], + hidden_api: { + stub_flags: "hiddenapi/stub-flags.csv", + annotation_flags: "hiddenapi/annotation-flags.csv", + metadata: "hiddenapi/metadata.csv", + index: "hiddenapi/index.csv", + all_flags: "hiddenapi/all-flags.csv", + }, +} + +prebuilt_bootclasspath_fragment { + name: "com.android.ipsec-bootclasspath-fragment", + prefer: false, + visibility: ["//visibility:public"], + apex_available: ["com.android.ipsec"], + licenses: ["ipsec-module-sdk_Android-Apache-2.0"], + contents: ["android.net.ipsec.ike"], + hidden_api: { + stub_flags: "hiddenapi/stub-flags.csv", + annotation_flags: "hiddenapi/annotation-flags.csv", + metadata: "hiddenapi/metadata.csv", + index: "hiddenapi/index.csv", + all_flags: "hiddenapi/all-flags.csv", + }, +} + +java_sdk_library_import { + name: "ipsec-module-sdk_android.net.ipsec.ike@current", + sdk_member_name: "android.net.ipsec.ike", + visibility: ["//visibility:public"], + apex_available: [ + "com.android.ipsec", + "test_com.android.ipsec", + ], + licenses: ["ipsec-module-sdk_Android-Apache-2.0@current"], + shared_library: true, + compile_dex: true, + permitted_packages: [ + "com.android.internal.net", + "android.net.ipsec.ike", + "android.net.eap", + ], + public: { + jars: ["sdk_library/public/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/public/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/public/android.net.ipsec.ike.txt", + removed_api: "sdk_library/public/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, + system: { + jars: ["sdk_library/system/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/system/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/system/android.net.ipsec.ike.txt", + removed_api: "sdk_library/system/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, + module_lib: { + jars: ["sdk_library/module-lib/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/module-lib/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/module-lib/android.net.ipsec.ike.txt", + removed_api: "sdk_library/module-lib/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, +} + +java_sdk_library_import { + name: "android.net.ipsec.ike", + prefer: false, + visibility: ["//visibility:public"], + apex_available: [ + "com.android.ipsec", + "test_com.android.ipsec", + ], + licenses: ["ipsec-module-sdk_Android-Apache-2.0"], + shared_library: true, + compile_dex: true, + permitted_packages: [ + "com.android.internal.net", + "android.net.ipsec.ike", + "android.net.eap", + ], + public: { + jars: ["sdk_library/public/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/public/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/public/android.net.ipsec.ike.txt", + removed_api: "sdk_library/public/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, + system: { + jars: ["sdk_library/system/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/system/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/system/android.net.ipsec.ike.txt", + removed_api: "sdk_library/system/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, + module_lib: { + jars: ["sdk_library/module-lib/android.net.ipsec.ike-stubs.jar"], + stub_srcs: ["sdk_library/module-lib/android.net.ipsec.ike.srcjar"], + current_api: "sdk_library/module-lib/android.net.ipsec.ike.txt", + removed_api: "sdk_library/module-lib/android.net.ipsec.ike-removed.txt", + sdk_version: "module_current", + }, +} + +license { + name: "ipsec-module-sdk_Android-Apache-2.0@current", + sdk_member_name: "Android-Apache-2.0", + visibility: ["//visibility:private"], + license_kinds: ["SPDX-license-identifier-Apache-2.0"], + license_text: ["licenses/build/soong/licenses/LICENSE"], +} + +license { + name: "ipsec-module-sdk_Android-Apache-2.0", + visibility: ["//visibility:private"], + license_kinds: ["SPDX-license-identifier-Apache-2.0"], + license_text: ["licenses/build/soong/licenses/LICENSE"], +} + +sdk_snapshot { + name: "ipsec-module-sdk@current", + visibility: ["//visibility:public"], + bootclasspath_fragments: ["ipsec-module-sdk_com.android.ipsec-bootclasspath-fragment@current"], + java_sdk_libs: ["ipsec-module-sdk_android.net.ipsec.ike@current"], +}