• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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