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 if arch == Architecture.AARCH64: 192 yield dir / "libmem_overrides.so" 193 yield dir / "libminijail.so" 194 yield dir / "libgfxstream_backend.so" 195 yield from dir.glob("*.so.?") 196 197 198def qemu_binaries(arch: Architecture) -> Iterator[Path]: 199 """Lists qemu binary paths.""" 200 dir = Path(f"qemu/{arch.value}-linux-gnu/bin") 201 yield from dir.glob("*.so.?") 202 yield from dir.glob("qemu-system*") 203 204 205def swiftshader_binaries(arch: Architecture) -> Iterator[Path]: 206 """Lists SwiftShader library and config paths.""" 207 dir = arch.dir() / "bin" 208 yield dir / "libvk_swiftshader.so" 209 yield dir / "vk_swiftshader_icd.json" 210 211def gen_main_android_bp_modules() -> Iterator[Module]: 212 """Returns the Modules to write in the main 'Android.bp' file.""" 213 for arch in Architecture: 214 for path in crosvm_binaries(arch): 215 name = re.sub("/bin/|/|-|_bin_", "_", str(path)) 216 if path.stem != "crosvm": 217 name = f"{name}_for_crosvm" 218 219 yield Module( 220 "cc_prebuilt_binary", 221 dict( 222 name=name, 223 srcs=[path], 224 stem=path.name, 225 relative_install_path=arch.dir(), 226 defaults=["cuttlefish_host"], 227 check_elf_files=False, 228 ), 229 ) 230 231 for arch in list(Architecture): 232 for binary_path in qemu_binaries(arch): 233 yield Module( 234 "cc_prebuilt_binary", 235 dict( 236 name=f"{arch.value}_linux_gnu_{binary_path.name}_binary_for_qemu", 237 srcs=[binary_path], 238 stem=binary_path.name, 239 relative_install_path=arch.dir() / "qemu", 240 defaults=["cuttlefish_host"], 241 check_elf_files=False, 242 ), 243 ) 244 245 for arch in list(Architecture): 246 for path in swiftshader_binaries(arch): 247 yield Module( 248 "cc_prebuilt_binary", 249 dict( 250 name=f"{arch.value}_linux_gnu_{path.name}", 251 srcs=[path], 252 stem=path.name, 253 relative_install_path=arch.dir(), 254 defaults=["cuttlefish_host"], 255 check_elf_files=False, 256 ), 257 ) 258 259 resource_paths = [ 260 Path(f"efi-virtio.rom"), 261 Path(f"keymaps/en-us"), 262 Path(f"opensbi-riscv64-generic-fw_dynamic.bin"), 263 ] 264 265 for arch in list(Architecture): 266 for resource_path in resource_paths: 267 base_name = resource_path.name 268 subdir = f"qemu/{arch.value}-linux-gnu" / resource_path.parents[0] 269 yield Module( 270 "prebuilt_usr_share_host", 271 dict( 272 name=f"{arch.value}_{base_name}_resource_for_qemu", 273 src=f"qemu/{arch.value}-linux-gnu/usr/share/qemu/{resource_path}", 274 filename=base_name, 275 sub_dir=subdir, 276 ), 277 ) 278 279 280def gen_main_android_bp_file(android_bp_path: Path, modules: List[Module]): 281 """Writes the main 'Android.bp' file with the specified modules.""" 282 283 disclamer = f"""\ 284 // NOTE: Using cc_prebuilt_binary because cc_prebuilt_library will add 285 // unwanted .so file extensions when installing shared libraries 286 287 """ 288 crosvm_comment = """\ 289 // Note: This is commented out to avoid a conflict with the binary built 290 // from external/crosvm. This should be uncommented out when backporting to 291 // older branches with just use the prebuilt and which do not build from 292 // source. 293 """ 294 295 with open(android_bp_path, "wt") as out: 296 out.write(license()) 297 out.write(textwrap.dedent(disclamer)) 298 299 sort_key = lambda m: ( 300 m.name, 301 m.parameters["name"].rsplit("_")[-1], 302 m.parameters["name"], 303 ) 304 for module in sorted(modules, key=sort_key): 305 module_text = str(module) 306 if module.parameters["name"] == "x86_64_linux_gnu_crosvm": 307 out.write(textwrap.dedent(crosvm_comment)) 308 module_text = comment(module_text) 309 out.write(module_text) 310 311 312def generate_all_cuttlefish_host_package_android_bp(path: Path, modules: list[Module]): 313 """Updates the list of module in 'device/google/cuttlefish/Android.bp'.""" 314 315 def update_list(list_name: str, modules:list[Module]): 316 names = sorted(m.parameters["name"] for m in modules) 317 text = f"{list_name} = {value_to_bp(names)}\n" 318 update_generated_section(path, list_name, text) 319 320 for arch in list(Architecture): 321 qemu_binaries = [m for m in modules if m.name.endswith("_binary_for_qemu") and m.name.startswith(arch.value)] 322 qemu_resources = [m for m in modules if m.name.endswith("_resource_for_qemu") and m.name.startswith(arch.value)] 323 324 update_list(f"qemu_{arch.value}_linux_gnu_binary", qemu_binaries) 325 update_list(f"qemu_{arch.value}_linux_gnu_resource", qemu_resources) 326 327 328def main(argv): 329 if len(argv) != 1: 330 print(f"Usage error: {argv[0]} does not accept any argument.") 331 return 1 332 333 # Set the current directory to the script location. 334 os.chdir(Path(__file__).parent) 335 336 modules = list(gen_main_android_bp_modules()) 337 gen_main_android_bp_file(Path("Android.bp"), modules) 338 339 generate_all_cuttlefish_host_package_android_bp( 340 Path("../cuttlefish/build/Android.bp"), modules 341 ) 342 343 for arch in Architecture: 344 gen_android_bp4seccomp(arch.dir() / "Android.bp", arch) 345 346 347if __name__ == "__main__": 348 sys.exit(main(sys.argv)) 349