This changes adds support for building target build release specific
snapshots.
It adds a BuildRelease class that defines the characteristics of the
build release, e.g. name, how to create it, etc. It also adds a
first_release field to MainlineModule which specifies the first
BuildRelease for which a snapshot of the module is required and
initializes that field for each module.
After these changes this script will generate:
1. A legacy set of snapshots that match the file structure that was
generated without this change. This is intended to allow existing
consumers of the generated artifacts to continue to work while they
are modified to make use of target build release specific snapshots.
2. The set of snapshots for the latest build release, i.e. the build
release containing the source from which the snapshots are produced.
This includes snapshots for all the modules.
3. For each build release from S onwards a set of snapshots for the
modules it supports.
It does not currently generate snapshots for Q and R releases as Soong
cannot generate a compatible snapshot for them. If it is necessary to
generate snapshots for those target build releases (similar to what the
packages/modules/common/generate_ml_bundle.sh would produce) then a
follow up change will add that capability to this script.
Bug: 204763318
Test: atest mainline_modules_sdks_test
packages/modules/common/build/mainline_modules_sdks.sh
tree out/dist/mainline-sdks out/dist/stubs
- check that it adds the for-latest-build directory but is
otherwise identical to what was produced before this change.
Change-Id: I48eb0b69cbe8664106b826ee0898557c98e039c2
225 lines
9.1 KiB
Python
225 lines
9.1 KiB
Python
#!/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."""
|
|
import dataclasses
|
|
from pathlib import Path
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
|
|
import mainline_modules_sdks as mm
|
|
|
|
MAINLINE_MODULES_BY_APEX = dict((m.apex, m) for m in mm.MAINLINE_MODULES)
|
|
|
|
|
|
class FakeSnapshotBuilder(mm.SnapshotBuilder):
|
|
"""A fake snapshot builder that does not run the build.
|
|
|
|
This skips the whole build process and just creates some fake sdk
|
|
modules.
|
|
"""
|
|
|
|
def create_snapshot_file(self, name, version):
|
|
zip_file = Path(self.get_sdk_path(name, version))
|
|
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 build_snapshots(self, build_release, sdk_versions, modules):
|
|
# Create input file structure.
|
|
sdks_out_dir = Path(self.get_mainline_sdks_path())
|
|
sdks_out_dir.mkdir(parents=True, exist_ok=True)
|
|
# Create a fake sdk zip file for each module.
|
|
for module in modules:
|
|
for sdk in module.sdks:
|
|
for sdk_version in sdk_versions:
|
|
self.create_snapshot_file(sdk, sdk_version)
|
|
|
|
|
|
class TestProduceDist(unittest.TestCase):
|
|
|
|
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 = [
|
|
MAINLINE_MODULES_BY_APEX["com.android.art"],
|
|
MAINLINE_MODULES_BY_APEX["com.android.ipsec"],
|
|
]
|
|
|
|
subprocess_runner = mm.SubprocessRunner()
|
|
|
|
snapshot_builder = FakeSnapshotBuilder(
|
|
subprocess_runner=subprocess_runner,
|
|
out_dir=tmp_out_dir,
|
|
)
|
|
|
|
build_releases = [
|
|
mm.Q,
|
|
mm.R,
|
|
mm.S,
|
|
mm.LATEST,
|
|
mm.LEGACY_BUILD_RELEASE,
|
|
]
|
|
|
|
producer = mm.SdkDistProducer(
|
|
subprocess_runner=subprocess_runner,
|
|
snapshot_builder=snapshot_builder,
|
|
dist_dir=tmp_dist_dir,
|
|
)
|
|
|
|
producer.produce_dist(modules, build_releases)
|
|
|
|
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(
|
|
[
|
|
# Legacy copy of the snapshots, for use by tools that don't support build specific snapshots.
|
|
"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",
|
|
# Build specific snapshots.
|
|
"mainline-sdks/for-S-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
|
|
"mainline-sdks/for-S-build/current/com.android.art/sdk/art-module-sdk-current.zip",
|
|
"mainline-sdks/for-S-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
|
|
"mainline-sdks/for-S-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip",
|
|
"mainline-sdks/for-latest-build/current/com.android.art/host-exports/art-module-host-exports-current.zip",
|
|
"mainline-sdks/for-latest-build/current/com.android.art/sdk/art-module-sdk-current.zip",
|
|
"mainline-sdks/for-latest-build/current/com.android.art/test-exports/art-module-test-exports-current.zip",
|
|
"mainline-sdks/for-latest-build/current/com.android.ipsec/sdk/ipsec-module-sdk-current.zip",
|
|
# Legacy stubs directory containing unpacked java_sdk_library artifacts.
|
|
"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=<x>) with an embedded launcher
|
|
# the __file__ points to .../<x>/<x>.py but the .../<x> is not a directory
|
|
# it is a binary with the launcher and the python file embedded inside. In
|
|
# that case a test data file <rel> is at .../<x>_data/<rel>, not
|
|
# .../<x>/<x>_data/<rel> so it is necessary to trim the base name (<x>.py)
|
|
# from the file.
|
|
if not os.path.isfile(this_file):
|
|
this_file = os.path.dirname(this_file)
|
|
# When the python file is at .../<x>.py (or in the case of an embedded
|
|
# launcher at .../<x>/<x>.py) then the test data is at .../<x>_data/<rel>.
|
|
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(
|
|
subprocess_runner=None,
|
|
snapshot_builder=None,
|
|
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 = 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 = MAINLINE_MODULES_BY_APEX["com.android.art"]
|
|
transformations = module.transformations()
|
|
|
|
self.apply_transformations(src, transformations, expected)
|
|
|
|
|
|
class TestFilterModules(unittest.TestCase):
|
|
|
|
def test_no_filter(self):
|
|
modules = mm.filter_modules(mm.MAINLINE_MODULES)
|
|
self.assertEqual(modules, mm.MAINLINE_MODULES)
|
|
|
|
def test_with_filter(self):
|
|
os.environ["TARGET_BUILD_APPS"] = "com.android.art"
|
|
modules = mm.filter_modules(mm.MAINLINE_MODULES)
|
|
expected = MAINLINE_MODULES_BY_APEX["com.android.art"]
|
|
self.assertEqual(modules, [expected])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|