1# Copyright (C) 2023 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14""" 15This script enumerates pre-built resources and updates Android.bp files: 16 17 - $ANDROID_BUILD_TOP/device/google/cuttlefish_vmm/Android.bp 18 - $ANDROID_BUILD_TOP/device/google/cuttlefish_vmm/$ARCH/Android.bp 19 - $ANDROID_BUILD_TOP/device/google/cuttlefish/Android.bp 20 21It is intended to be run manually: 22 23 python3 $ANDROID_BUILD_TOP/device/google/cuttlefish_vmm/gen_android_bp.py 24 25""" 26import datetime 27import os 28import re 29import sys 30import textwrap 31from dataclasses import dataclass 32from enum import auto 33from enum import Enum 34from pathlib import Path 35from typing import Dict 36from typing import List 37from typing import Iterator 38from typing import TypeAlias 39 40 41def tool_name() -> str: 42 """Returns a short name for this generation tool.""" 43 return Path(__file__).name 44 45 46class Architecture(Enum): 47 """Host instruction set architectures.""" 48 49 AARCH64 = 'aarch64' 50 X86_64 = 'x86_64' 51 52 def dir(self) -> Path: 53 "Returns the relative directory path to the specified architecture." 54 return Path(f"{self.name.lower()}-linux-gnu") 55 56 57# Android.bp variant value type. 58Value: TypeAlias = str | Path | bool | list["Value"] 59 60 61def value_to_bp(value: Value) -> str: 62 """Returns `bp` expression for the specified value""" 63 if isinstance(value, list): 64 if len(value) == 1: 65 return "[%s]" % value_to_bp(value[0]) 66 return "[\n %s,\n]" % ",\n ".join(value_to_bp(e) for e in value) 67 elif isinstance(value, bool): 68 return str(value).lower() 69 elif isinstance(value, Path): 70 return value_to_bp(str(value)) 71 else: 72 return f'"{value}"' 73 74 75@dataclass 76class Module: 77 """Android bp build rule.""" 78 79 module_type: str 80 parameters: Dict[str, Value] 81 82 @property 83 def name(self) -> str: 84 assert isinstance(self.parameters.get("name"), str) 85 return self.parameters["name"] 86 87 def __str__(self) -> str: 88 """Returns a `.bp` string for this modules with its parameters.""" 89 body = "\n ".join( 90 f"{k}: {value_to_bp(v)}," for k, v in self.parameters.items() 91 ) 92 return f"{self.module_type} {{\n {body}\n}}\n" 93 94 95def update_generated_section(file_path: Path, tag: str, content: str): 96 """Reads a text file, matches and replaces the content between 97 a start beacon and an end beacon with the specified one, and 98 modifies the file in place. 99 100 The generated content is delimited by `// Start of generated` and 101 `// End of generated`. The specified content is indented the same 102 way as the start beacon is. 103 104 Args: 105 file_path: path to the text file to be modified. 106 tag: marks the beginning aned end of the string generated content. 107 content: text to replace the content between the start and end beacons with. 108 """ 109 # Read the contents of the text file. 110 with open(file_path, "rt", encoding="utf-8") as f: 111 file_contents = f.read() 112 113 # Find the start and end beacon positions in the file contents. 114 start = f"// Start of generated {tag}\n" 115 end = f"// End of generated {tag}\n" 116 117 match = re.match( 118 f"^(?P<head>.*)^(?P<indent>[ \t]*){re.escape(start)}.*{re.escape(end)}(?P<tail>.*)$", 119 file_contents, 120 re.DOTALL | re.MULTILINE, 121 ) 122 if not match: 123 raise ValueError( 124 f"Generated content beacons {(start, end)} not matched in file {file_path}" 125 ) 126 127 with open(file_path, "wt", encoding="utf-8") as f: 128 f.write(f"{match.group('head')}{match.group('indent')}{start}") 129 f.write(f"{match.group('indent')}// Generated by {tool_name()}\n") 130 f.write(textwrap.indent(content, match.group("indent"))) 131 f.write(f"{match.group('indent')}{end}{match.group('tail')}") 132 133 134def license() -> str: 135 """Returns a license header at the current date, with generation warning.""" 136 current_year = datetime.datetime.now().year 137 return textwrap.dedent( 138 f"""\ 139 // Autogenerated via {tool_name()} 140 // 141 // Copyright (C) {current_year} The Android Open Source Project 142 // 143 // Licensed under the Apache License, Version 2.0 (the "License"); 144 // you may not use this file except in compliance with the License. 145 // You may obtain a copy of the License at 146 // 147 // http://www.apache.org/licenses/LICENSE-2.0 148 // 149 // Unless required by applicable law or agreed to in writing, software 150 // distributed under the License is distributed on an "AS IS" BASIS, 151 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 152 // See the License for the specific language governing permissions and 153 // limitations under the License. 154 155 """ 156 ) 157 158 159def comment(text: str) -> str: 160 """Prefixes each lines with '/// ' and return the commented content.""" 161 return re.sub("^(?!$)", "// ", text, flags=re.MULTILINE) 162 163 164def gen_android_bp4seccomp(path: Path, arch: Architecture): 165 """Regenerates the specified '.bp' file for the given architecture.""" 166 167 with open(path, "wt") as out: 168 subdir = Path("etc/seccomp") 169 seccomp_dir = arch.dir() / subdir 170 where_in_etc_on_user_machine = "cuttlefish" / arch.dir() / "seccomp" 171 out.write(license()) 172 173 for path in sorted(seccomp_dir.iterdir()): 174 module = Module( 175 "prebuilt_usr_share_host", 176 dict( 177 name=f"{path.name}_at_{arch.name.lower()}", 178 src=subdir / path.name, 179 filename=path.name, 180 sub_dir=where_in_etc_on_user_machine, 181 ), 182 ) 183 out.write(str(module)) 184 185 186def crosvm_binaries(arch: Architecture) -> Iterator[Path]: 187 """Lists crosvm binary paths.""" 188 dir = arch.dir() / "bin" 189 yield dir / "crosvm" 190 yield dir / "gfxstream_graphics_detector" 191 yield dir / "libminijail.so" 192 yield dir / "libgfxstream_backend.so" 193 yield from dir.glob("*.so.?") 194 195 196def qemu_binaries(arch: Architecture) -> Iterator[Path]: 197 """Lists qemu binary paths.""" 198 dir = Path(f"qemu/{arch.value}-linux-gnu/bin") 199 yield from dir.glob("*.so.?") 200 yield from dir.glob("qemu-system*") 201 202 203def gen_main_android_bp_modules() -> Iterator[Module]: 204 """Returns the Modules to write in the main 'Android.bp' file.""" 205 for arch in Architecture: 206 for path in crosvm_binaries(arch): 207 name = re.sub("/bin/|/|-|_bin_", "_", str(path)) 208 if path.stem != "crosvm": 209 name = f"{name}_for_crosvm" 210 211 yield Module( 212 "cc_prebuilt_binary", 213 dict( 214 name=name, 215 srcs=[path], 216 stem=path.name, 217 relative_install_path=arch.dir(), 218 defaults=["cuttlefish_host"], 219 check_elf_files=False, 220 ), 221 ) 222 223 for arch in list(Architecture): 224 for binary_path in qemu_binaries(arch): 225 yield Module( 226 "cc_prebuilt_binary", 227 dict( 228 name=f"{arch.value}_linux_gnu_{binary_path.name}_binary_for_qemu", 229 srcs=[binary_path], 230 stem=binary_path.name, 231 relative_install_path=arch.dir() / "qemu", 232 defaults=["cuttlefish_host"], 233 check_elf_files=False, 234 ), 235 ) 236 237 resource_paths = [ 238 Path(f"efi-virtio.rom"), 239 Path(f"keymaps/en-us"), 240 Path(f"opensbi-riscv64-generic-fw_dynamic.bin"), 241 ] 242 243 for arch in list(Architecture): 244 for resource_path in resource_paths: 245 base_name = resource_path.name 246 subdir = f"qemu/{arch.value}-linux-gnu" / resource_path.parents[0] 247 yield Module( 248 "prebuilt_usr_share_host", 249 dict( 250 name=f"{arch.value}_{base_name}_resource_for_qemu", 251 src=f"qemu/{arch.value}-linux-gnu/usr/share/qemu/{resource_path}", 252 filename=base_name, 253 sub_dir=subdir, 254 ), 255 ) 256 257 258def gen_main_android_bp_file(android_bp_path: Path, modules: List[Module]): 259 """Writes the main 'Android.bp' file with the specified modules.""" 260 261 disclamer = f"""\ 262 // NOTE: Using cc_prebuilt_binary because cc_prebuilt_library will add 263 // unwanted .so file extensions when installing shared libraries 264 265 """ 266 crosvm_comment = """\ 267 // Note: This is commented out to avoid a conflict with the binary built 268 // from external/crosvm. This should be uncommented out when backporting to 269 // older branches with just use the prebuilt and which do not build from 270 // source. 271 """ 272 273 with open(android_bp_path, "wt") as out: 274 out.write(license()) 275 out.write(textwrap.dedent(disclamer)) 276 277 sort_key = lambda m: ( 278 m.name, 279 m.parameters["name"].rsplit("_")[-1], 280 m.parameters["name"], 281 ) 282 for module in sorted(modules, key=sort_key): 283 module_text = str(module) 284 if module.parameters["name"] == "x86_64_linux_gnu_crosvm": 285 out.write(textwrap.dedent(crosvm_comment)) 286 module_text = comment(module_text) 287 out.write(module_text) 288 289 290def generate_all_cuttlefish_host_package_android_bp(path: Path, modules: list[Module]): 291 """Updates the list of module in 'device/google/cuttlefish/Android.bp'.""" 292 293 def update_list(list_name: str, modules:list[Module]): 294 names = sorted(m.parameters["name"] for m in modules) 295 text = f"{list_name} = {value_to_bp(names)}\n" 296 update_generated_section(path, list_name, text) 297 298 for arch in list(Architecture): 299 qemu_binaries = [m for m in modules if m.name.endswith("_binary_for_qemu") and m.name.startswith(arch.value)] 300 qemu_resources = [m for m in modules if m.name.endswith("_resource_for_qemu") and m.name.startswith(arch.value)] 301 302 update_list(f"qemu_{arch.value}_linux_gnu_binary", qemu_binaries) 303 update_list(f"qemu_{arch.value}_linux_gnu_resource", qemu_resources) 304 305 306def main(argv): 307 if len(argv) != 1: 308 print(f"Usage error: {argv[0]} does not accept any argument.") 309 return 1 310 311 # Set the current directory to the script location. 312 os.chdir(Path(__file__).parent) 313 314 modules = list(gen_main_android_bp_modules()) 315 gen_main_android_bp_file(Path("Android.bp"), modules) 316 317 generate_all_cuttlefish_host_package_android_bp( 318 Path("../cuttlefish/build/Android.bp"), modules 319 ) 320 321 for arch in Architecture: 322 gen_android_bp4seccomp(arch.dir() / "Android.bp", arch) 323 324 325if __name__ == "__main__": 326 sys.exit(main(sys.argv)) 327