#!/usr/bin/env -S python3 -u # Copyright (C) 2024 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. import argparse import os from pathlib import Path import shutil import subprocess import sys # Formatted using: pyformat -s 4 --force_quote_type double -i scripts/gather-android-metalava-artifacts.py def parse_command_line_args(args): """Define the command line options and the parse the command line arguments with them. :param args: the command line arguments :return: Return the result of parsing the command line arguments. """ args_parser = argparse.ArgumentParser( description=( "Gather Android artifacts created by Metalava. This will build and" " then copy a set of targets to the output directory. If no custom" " targets are provided then a set of default ones will be provided" " that covers stub generation, signature to JDiff conversion and" " api-versions.xml file generation. The intent is that this would" " be run this before and after making the change to build and copy" " the artifacts into two separate directories that can then be" " compared to see what, if any, changes have happened. This does" " not check signature file generation as that can be easily checked" " by running `m checkapi`." ), ) args_parser.add_argument( "directory", help="Output directory into which artifacts will be copied.", ) args_parser.add_argument( "--stub-src-jar", action="append", help="Additional stub jar to gather", ) return args_parser.parse_args(args) def default_stub_files(): """:return: A representative sample list of stub source jars generated by the Android build using Metalava""" return [ f"out/target/common/docs/{x}-stubs.srcjar" for x in [ "api-stubs-docs-non-updatable", "system-api-stubs-docs-non-updatable", "test-api-stubs-docs-non-updatable", "module-lib-api-stubs-docs-non-updatable", ] ] def default_doc_stub_files(): """:return: A representative sample list of doc stub source jars generated by the Android build using Metalava""" return [ "out/target/common/docs/framework-doc-stubs-stubs.srcjar", "out/target/common/docs/framework-doc-system-stubs-stubs.srcjar", ] def default_api_version_files(): """:return: A representative sample list of `api-versions.xml` files generated by the Android build using Metalava. """ return [ "out/soong/lint/api_versions_public.xml", "out/soong/lint/api_versions_system.xml", "out/soong/lint/api_versions_module_lib.xml", "out/soong/lint/api_versions_system_server.xml", "out/target/common/obj/PACKAGING/api_versions_module_lib_complete_generated-api-versions.xml", "out/target/common/obj/PACKAGING/api_versions_system_server_complete_generated-api-versions.xml", ] def default_jdiff_files(): """:return: A representative sample list of JDiff files created by the Android build using Metalava.""" return [ # JDiff files generated from jar files. "out/target/common/obj/api.xml", "out/target/common/obj/system-api.xml", "out/target/common/obj/module-lib-api.xml", "out/target/common/obj/system-server-api.xml", "out/target/common/obj/test-api.xml", # JDiff files generated from txt files. "out/soong/.intermediates/packages/services/Car/car-lib/android.car-test-stubs-jdiff/gen/car-test-api.xml", "out/soong/.intermediates/packages/services/Car/car-lib/android.car-stubs-jdiff/gen/car-api.xml", "out/soong/.intermediates/packages/services/Car/car-lib/android.car-system-stubs-jdiff/gen/car-system-api.xml", ""# JDiff files generated from txt files and then compressed using gzip.. "out/soong/.intermediates/cts/tests/signature/api/cts-android-test-base-current-api-gz/gen/android-test-base-current.api.gz", "out/soong/.intermediates/cts/tests/signature/api/cts-android-test-mock-current-api-gz/gen/android-test-mock-current.api.gz", "out/soong/.intermediates/cts/tests/signature/api/cts-android-test-runner-current-api-gz/gen/android-test-runner-current.api.gz", "out/soong/.intermediates/cts/tests/signature/api/cts-apache-http-legacy-current-api-gz/gen/apache-http-legacy-current.api.gz", "out/soong/.intermediates/cts/tests/signature/api/cts-car-system-current-api-gz/gen/car-system-current.api.gz", "out/soong/.intermediates/cts/tests/signature/api/cts-current-api-gz/android_common/gen/current.api.gz", "out/soong/.intermediates/cts/tests/signature/api/cts-system-current-api-gz/android_common/gen/system-current.api.gz", "out/soong/.intermediates/cts/tests/signature/api/cts-system-removed-api-gz/android_common/gen/system-removed.api.gz", ] def default_dex_writer_files(): """:return: A representative sample list of dex writer related files created by the Android build using Metalava.""" return [ # This is not actually a dex writer file but it contains information derived from lots of dex writer files so # any differences in the dex writer files will affect this file. "out/soong/hiddenapi/hiddenapi-flags.csv", ] def default_custom_files(top): """Returns a representative sample list of custom files created by the Android build using a custom tool based, at least in part, on Metalava. :return: A list of custom files. """ product_out = Path(os.environ.get("ANDROID_PRODUCT_OUT")).relative_to(top) return [ f"{product_out}/obj/ETC/flag-api-mapping-{surface}_intermediates/flag-api-mapping-{surface}" for surface in [ "PublicApi", "SystemApi", "ModuleLibApi", "SystemServerApi", ] ] def construct_target_list(args, top): """Generate a list of targets from the supplied arguments :param args: the command line arguments. :return: a non-empty list of targets to build. """ targets = [] # If any custom options have been provided then build them. if args.stub_src_jar: targets += args.stub_src_jar # If no custom targets have been provided then use the default targets. if not targets: targets += default_stub_files() targets += default_doc_stub_files() targets += default_jdiff_files() targets += default_api_version_files() targets += default_dex_writer_files() targets += default_custom_files(top) return targets def main(args): top = os.environ.get("ANDROID_BUILD_TOP") if not top: raise Exception("ANDROID_BUILD_TOP not specified") os.chdir(top) # Parse command line arguments. args = parse_command_line_args(args) # Make sure that the output directory does not already exist. output_dir = Path(args.directory) if output_dir.exists(): raise Exception(f"{output_dir} exists, please delete or change") # Construct the list of targets to build, using defaults where required. targets = construct_target_list(args, top) # Build the targets. build_targets(targets) # Create the output directory and copy the targets into it. copy_targets(output_dir, targets) def copy_targets(output_dir, targets): print(f"Making output directory: '{output_dir}'") os.mkdir(output_dir) print() print(f"Copying the following targets into '{output_dir}':") for t in targets: print(f" {t}") shutil.copy(t, output_dir) print() def build_targets(targets): print() print("Building the following targets:") for t in targets: print(f" {t}") print() subprocess.run( ["build/soong/soong_ui.bash", "--make-mode"] + targets, check=True ) print() if __name__ == "__main__": main(sys.argv[1:])