1# 2# Copyright (C) 2021 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16"""Tool for updating the prebuilt NDK ABI dumps.""" 17import argparse 18import logging 19from pathlib import Path 20import shutil 21import sys 22 23from .soong import Soong 24 25 26def logger() -> logging.Logger: 27 """Returns the module level logger.""" 28 return logging.getLogger(__name__) 29 30 31class Updater: 32 """Tool for updating prebuilt NDK ABI dumps.""" 33 34 def __init__(self, src_dir: Path, build_dir: Path) -> None: 35 self.src_dir = src_dir 36 self.build_dir = build_dir 37 38 def build_abi_dumps(self) -> None: 39 """Builds the updated NDK ABI dumps.""" 40 soong = Soong(self.src_dir, self.build_dir) 41 logger().info(f"Building ABI dumps to {self.build_dir}") 42 soong.build( 43 ["dump-ndk-abi"], 44 env={ 45 "TARGET_PRODUCT": "ndk", 46 "TARGET_RELEASE": "trunk_staging", 47 # TODO: remove ALLOW_MISSING_DEPENDENCIES=true when all the 48 # riscv64 dependencies exist (currently blocked by 49 # http://b/273792258). 50 "ALLOW_MISSING_DEPENDENCIES": "true", 51 # TODO: remove BUILD_BROKEN_DISABLE_BAZEL=1 when bazel supports 52 # riscv64 (http://b/262192655). 53 "BUILD_BROKEN_DISABLE_BAZEL": "1", 54 }, 55 ) 56 57 def copy_updated_abi_dumps(self) -> None: 58 """Copies the NDK ABI dumps from the build directory to prebuilts.""" 59 prebuilts_project = self.src_dir / "prebuilts/abi-dumps" 60 prebuilts_dir = prebuilts_project / "ndk" 61 abi_out = self.build_dir / "soong/abi-dumps/ndk" 62 for version_dir in abi_out.iterdir(): 63 try: 64 int(version_dir.name) 65 except ValueError: 66 logger().info("Skipping %s because it is a preview API level", version_dir) 67 continue 68 69 for dump in version_dir.glob("**/abi.stg"): 70 install_path = prebuilts_dir / dump.relative_to(abi_out) 71 install_dir = install_path.parent 72 if not install_dir.exists(): 73 install_dir.mkdir(parents=True) 74 logger().info(f"Copying ABI dump {dump} to {install_path}") 75 shutil.copy2(dump, install_path) 76 77 def run(self) -> None: 78 """Runs the updater. 79 80 Cleans the out directory, builds the ABI dumps, and copies the results 81 to the prebuilts directory. 82 """ 83 self.build_abi_dumps() 84 self.copy_updated_abi_dumps() 85 86 87HELP = """\ 88Builds and updates the NDK ABI prebuilts. 89 90Whenever a change is made that alters the NDK ABI (or an API level is 91finalized, or a new preview codename is introduced to the build), the prebuilts 92in prebuilts/abi-dumps/ndk need to be updated to match. For any finalized APIs, 93the breaking change typically needs to be reverted. 94 95Note that typically this tool should be executed via 96development/tools/ndk/update_ndk_abi.sh. That script will ensure that this tool 97is up-to-date and run with the correct arguments. 98""" 99 100 101class App: 102 """Command line application from updating NDK ABI prebuilts.""" 103 104 @staticmethod 105 def parse_args() -> argparse.Namespace: 106 """Parses and returns command line arguments.""" 107 108 parser = argparse.ArgumentParser( 109 formatter_class=argparse.RawDescriptionHelpFormatter, description=HELP 110 ) 111 112 def resolved_path(path: str) -> Path: 113 """Converts a string into a fully resolved Path.""" 114 return Path(path).resolve() 115 116 parser.add_argument( 117 "--src-dir", 118 type=resolved_path, 119 required=True, 120 help="Path to the top of the Android source tree.", 121 ) 122 123 parser.add_argument( 124 "out_dir", 125 type=resolved_path, 126 metavar="OUT_DIR", 127 help="Output directory to use for building ABI dumps.", 128 ) 129 130 parser.add_argument( 131 "-v", 132 "--verbose", 133 action="count", 134 default=0, 135 help="Increase logging verbosity.", 136 ) 137 138 return parser.parse_args() 139 140 def run(self) -> None: 141 """Builds the new NDK ABI dumps and copies them to prebuilts.""" 142 args = self.parse_args() 143 log_level = logging.DEBUG if args.verbose else logging.INFO 144 logging.basicConfig(level=log_level) 145 test_path = args.src_dir / "build/soong/soong_ui.bash" 146 if not test_path.exists(): 147 sys.exit( 148 f"Source directory {args.src_dir} does not appear to be an " 149 f"Android source tree: {test_path} does not exist." 150 ) 151 152 Updater(args.src_dir, args.out_dir).run() 153