David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 2 | # |
| 3 | # Copyright (C) 2021 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 17 | """ |
| 18 | This scripts compiles Java files which are needed to execute run-tests. |
| 19 | It is intended to be used only from soong genrule. |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 20 | """ |
| 21 | |
| 22 | import argparse |
| 23 | import functools |
| 24 | import glob |
| 25 | import os |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 26 | import pathlib |
David Srbecky | c9453d3 | 2022-11-17 13:28:28 +0000 | [diff] [blame] | 27 | import re |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 28 | import shlex |
| 29 | import shutil |
| 30 | import subprocess |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 31 | import sys |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 32 | import zipfile |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 33 | |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 34 | from argparse import ArgumentParser |
David Srbecky | c9453d3 | 2022-11-17 13:28:28 +0000 | [diff] [blame] | 35 | from concurrent.futures import ThreadPoolExecutor |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 36 | from fcntl import lockf, LOCK_EX, LOCK_NB |
| 37 | from importlib.machinery import SourceFileLoader |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 38 | from os import environ, getcwd, chdir, cpu_count, chmod |
| 39 | from os.path import relpath |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 40 | from pathlib import Path |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 41 | from pprint import pprint |
David Srbecky | e0c3cd8 | 2022-08-24 14:56:24 +0100 | [diff] [blame] | 42 | from re import match |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 43 | from shutil import copytree, rmtree |
| 44 | from subprocess import run |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 45 | from tempfile import TemporaryDirectory, NamedTemporaryFile |
David Srbecky | 100fe67 | 2022-11-14 11:32:27 +0000 | [diff] [blame] | 46 | from typing import Dict, List, Union, Set, Optional |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 47 | |
David Srbecky | a42a2e3 | 2022-11-17 13:43:22 +0000 | [diff] [blame] | 48 | USE_RBE = 100 # Percentage of tests that can use RBE (between 0 and 100) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 49 | |
| 50 | lock_file = None # Keep alive as long as this process is alive. |
| 51 | |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 52 | RBE_D8_DISABLED_FOR = { |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 53 | "952-invoke-custom", # b/228312861: RBE uses wrong inputs. |
| 54 | "979-const-method-handle", # b/228312861: RBE uses wrong inputs. |
| 55 | } |
David Srbecky | 89010a3 | 2022-07-14 16:47:30 +0000 | [diff] [blame] | 56 | |
David Srbecky | c9453d3 | 2022-11-17 13:28:28 +0000 | [diff] [blame] | 57 | # Debug option. Report commands that are taking a lot of user CPU time. |
| 58 | REPORT_SLOW_COMMANDS = False |
| 59 | |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 60 | class BuildTestContext: |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 61 | def __init__(self, args, android_build_top, test_dir): |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 62 | self.android_build_top = android_build_top.absolute() |
| 63 | self.bootclasspath = args.bootclasspath.absolute() |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 64 | self.test_name = test_dir.name |
| 65 | self.test_dir = test_dir.absolute() |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 66 | self.mode = args.mode |
| 67 | self.jvm = (self.mode == "jvm") |
| 68 | self.host = (self.mode == "host") |
| 69 | self.target = (self.mode == "target") |
| 70 | assert self.jvm or self.host or self.target |
| 71 | |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 72 | self.java_home = Path(os.environ.get("JAVA_HOME")).absolute() |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 73 | self.java_path = self.java_home / "bin/java" |
| 74 | self.javac_path = self.java_home / "bin/javac" |
Nicolas Geoffray | bb8d6f6 | 2023-09-12 11:04:23 +0100 | [diff] [blame] | 75 | self.javac_args = "-g -Xlint:-options" |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 76 | |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 77 | # Helper functions to execute tools. |
| 78 | self.d8 = functools.partial(self.run, args.d8.absolute()) |
| 79 | self.jasmin = functools.partial(self.run, args.jasmin.absolute()) |
| 80 | self.javac = functools.partial(self.run, self.javac_path) |
| 81 | self.smali = functools.partial(self.run, args.smali.absolute()) |
| 82 | self.soong_zip = functools.partial(self.run, args.soong_zip.absolute()) |
| 83 | self.zipalign = functools.partial(self.run, args.zipalign.absolute()) |
| 84 | if args.hiddenapi: |
| 85 | self.hiddenapi = functools.partial(self.run, args.hiddenapi.absolute()) |
| 86 | |
| 87 | # RBE wrapper for some of the tools. |
Hans Boehm | 6183e75 | 2023-01-11 00:45:46 +0000 | [diff] [blame] | 88 | if "RBE_server_address" in os.environ and USE_RBE > (hash(self.test_name) % 100): |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 89 | self.rbe_exec_root = os.environ.get("RBE_exec_root") |
| 90 | self.rbe_rewrapper = self.android_build_top / "prebuilts/remoteexecution-client/live/rewrapper" |
David Srbecky | 17b5042 | 2023-10-26 15:25:41 +0100 | [diff] [blame^] | 91 | |
| 92 | # TODO(b/307932183) Regression: RBE produces wrong output for D8 in ART |
| 93 | disable_d8 = any((self.test_dir / n).exists() for n in ["classes", "src2", "src-art"]) |
| 94 | |
| 95 | if self.test_name not in RBE_D8_DISABLED_FOR and not disable_d8: |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 96 | self.d8 = functools.partial(self.rbe_d8, args.d8.absolute()) |
David Srbecky | a42a2e3 | 2022-11-17 13:43:22 +0000 | [diff] [blame] | 97 | self.javac = functools.partial(self.rbe_javac, self.javac_path) |
| 98 | self.smali = functools.partial(self.rbe_smali, args.smali.absolute()) |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 99 | |
| 100 | # Minimal environment needed for bash commands that we execute. |
| 101 | self.bash_env = { |
| 102 | "ANDROID_BUILD_TOP": self.android_build_top, |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 103 | "D8": args.d8.absolute(), |
| 104 | "JAVA": self.java_path, |
| 105 | "JAVAC": self.javac_path, |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 106 | "JAVAC_ARGS": self.javac_args, |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 107 | "JAVA_HOME": self.java_home, |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 108 | "PATH": os.environ["PATH"], |
| 109 | "PYTHONDONTWRITEBYTECODE": "1", |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 110 | "SMALI": args.smali.absolute(), |
| 111 | "SOONG_ZIP": args.soong_zip.absolute(), |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 112 | "TEST_NAME": self.test_name, |
| 113 | } |
| 114 | |
| 115 | def bash(self, cmd): |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 116 | return subprocess.run(cmd, |
| 117 | shell=True, |
| 118 | cwd=self.test_dir, |
| 119 | env=self.bash_env, |
| 120 | check=True) |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 121 | |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 122 | def run(self, executable: pathlib.Path, args: List[Union[pathlib.Path, str]]): |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 123 | assert isinstance(executable, pathlib.Path), executable |
| 124 | cmd: List[Union[pathlib.Path, str]] = [] |
David Srbecky | c9453d3 | 2022-11-17 13:28:28 +0000 | [diff] [blame] | 125 | if REPORT_SLOW_COMMANDS: |
| 126 | cmd += ["/usr/bin/time"] |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 127 | if executable.suffix == ".sh": |
| 128 | cmd += ["/bin/bash"] |
| 129 | cmd += [executable] |
| 130 | cmd += args |
| 131 | env = self.bash_env |
| 132 | env.update({k: v for k, v in os.environ.items() if k.startswith("RBE_")}) |
| 133 | # Make paths relative as otherwise we could create too long command line. |
| 134 | for i, arg in enumerate(cmd): |
| 135 | if isinstance(arg, pathlib.Path): |
| 136 | assert arg.absolute(), arg |
| 137 | cmd[i] = relpath(arg, self.test_dir) |
| 138 | elif isinstance(arg, list): |
| 139 | assert all(p.absolute() for p in arg), arg |
| 140 | cmd[i] = ":".join(relpath(p, self.test_dir) for p in arg) |
| 141 | else: |
| 142 | assert isinstance(arg, str), arg |
| 143 | p = subprocess.run(cmd, |
| 144 | encoding=sys.stdout.encoding, |
| 145 | cwd=self.test_dir, |
| 146 | env=self.bash_env, |
| 147 | stderr=subprocess.STDOUT, |
| 148 | stdout=subprocess.PIPE) |
David Srbecky | c9453d3 | 2022-11-17 13:28:28 +0000 | [diff] [blame] | 149 | if REPORT_SLOW_COMMANDS: |
| 150 | m = re.search("([0-9\.]+)user", p.stdout) |
| 151 | assert m, p.stdout |
| 152 | t = float(m.group(1)) |
| 153 | if t > 1.0: |
| 154 | cmd_text = " ".join(map(str, cmd[1:]))[:100] |
| 155 | print(f"[{self.test_name}] Command took {t:.2f}s: {cmd_text}") |
| 156 | |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 157 | if p.returncode != 0: |
| 158 | raise Exception("Command failed with exit code {}\n$ {}\n{}".format( |
| 159 | p.returncode, " ".join(map(str, cmd)), p.stdout)) |
| 160 | return p |
| 161 | |
| 162 | def rbe_wrap(self, args, inputs: Set[pathlib.Path]=None): |
| 163 | with NamedTemporaryFile(mode="w+t") as input_list: |
| 164 | inputs = inputs or set() |
| 165 | for i, arg in enumerate(args): |
| 166 | if isinstance(arg, pathlib.Path): |
| 167 | assert arg.absolute(), arg |
| 168 | inputs.add(arg) |
| 169 | elif isinstance(arg, list): |
| 170 | assert all(p.absolute() for p in arg), arg |
| 171 | inputs.update(arg) |
| 172 | input_list.writelines([relpath(i, self.rbe_exec_root)+"\n" for i in inputs]) |
| 173 | input_list.flush() |
| 174 | return self.run(self.rbe_rewrapper, [ |
| 175 | "--platform=" + os.environ["RBE_platform"], |
| 176 | "--input_list_paths=" + input_list.name, |
| 177 | ] + args) |
| 178 | |
David Srbecky | a42a2e3 | 2022-11-17 13:43:22 +0000 | [diff] [blame] | 179 | def rbe_javac(self, javac_path:Path, args): |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 180 | output = relpath(Path(args[args.index("-d") + 1]), self.rbe_exec_root) |
David Srbecky | a42a2e3 | 2022-11-17 13:43:22 +0000 | [diff] [blame] | 181 | return self.rbe_wrap(["--output_directories", output, javac_path] + args) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 182 | |
David Srbecky | a42a2e3 | 2022-11-17 13:43:22 +0000 | [diff] [blame] | 183 | def rbe_d8(self, d8_path:Path, args): |
| 184 | inputs = set([d8_path.parent.parent / "framework/d8.jar"]) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 185 | output = relpath(Path(args[args.index("--output") + 1]), self.rbe_exec_root) |
| 186 | return self.rbe_wrap([ |
| 187 | "--output_files" if output.endswith(".jar") else "--output_directories", output, |
Sorin Basca | e1f8e3c | 2022-12-21 15:58:58 +0000 | [diff] [blame] | 188 | "--toolchain_inputs=prebuilts/jdk/jdk17/linux-x86/bin/java", |
David Srbecky | a42a2e3 | 2022-11-17 13:43:22 +0000 | [diff] [blame] | 189 | d8_path] + args, inputs) |
| 190 | |
| 191 | def rbe_smali(self, smali_path:Path, args): |
| 192 | inputs = set([smali_path.parent.parent / "framework/smali.jar"]) |
| 193 | output = relpath(Path(args[args.index("--output") + 1]), self.rbe_exec_root) |
| 194 | return self.rbe_wrap([ |
| 195 | "--output_files", output, |
Sorin Basca | e1f8e3c | 2022-12-21 15:58:58 +0000 | [diff] [blame] | 196 | "--toolchain_inputs=prebuilts/jdk/jdk17/linux-x86/bin/java", |
David Srbecky | a42a2e3 | 2022-11-17 13:43:22 +0000 | [diff] [blame] | 197 | smali_path] + args, inputs) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 198 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 199 | def build(self) -> None: |
| 200 | script = self.test_dir / "build.py" |
| 201 | if script.exists(): |
| 202 | module = SourceFileLoader("build_" + self.test_name, |
| 203 | str(script)).load_module() |
| 204 | module.build(self) |
| 205 | else: |
| 206 | self.default_build() |
| 207 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 208 | def default_build( |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 209 | self, |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 210 | use_desugar=True, |
| 211 | use_hiddenapi=True, |
| 212 | need_dex=None, |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 213 | zip_compression_method="deflate", |
| 214 | zip_align_bytes=None, |
David Srbecky | e82c87d | 2022-11-16 20:31:11 +0000 | [diff] [blame] | 215 | api_level:Union[int, str]=26, # Can also be named alias (string). |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 216 | javac_args=[], |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 217 | javac_classpath: List[Path]=[], |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 218 | d8_flags=[], |
| 219 | smali_args=[], |
| 220 | use_smali=True, |
| 221 | use_jasmin=True, |
Nicolas Geoffray | bb8d6f6 | 2023-09-12 11:04:23 +0100 | [diff] [blame] | 222 | javac_source_arg="1.8", |
| 223 | javac_target_arg="1.8" |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 224 | ): |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 225 | javac_classpath = javac_classpath.copy() # Do not modify default value. |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 226 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 227 | # Wrap "pathlib.Path" with our own version that ensures all paths are absolute. |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 228 | # Plain filenames are assumed to be relative to self.test_dir and made absolute. |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 229 | class Path(pathlib.Path): |
| 230 | def __new__(cls, filename: str): |
| 231 | path = pathlib.Path(filename) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 232 | return path if path.is_absolute() else (self.test_dir / path) |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 233 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 234 | need_dex = (self.host or self.target) if need_dex is None else need_dex |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 235 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 236 | if self.jvm: |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 237 | # No desugaring on jvm because it supports the latest functionality. |
| 238 | use_desugar = False |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 239 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 240 | # Set API level for smali and d8. |
David Srbecky | e82c87d | 2022-11-16 20:31:11 +0000 | [diff] [blame] | 241 | if isinstance(api_level, str): |
| 242 | API_LEVEL = { |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 243 | "default-methods": 24, |
| 244 | "parameter-annotations": 25, |
| 245 | "agents": 26, |
| 246 | "method-handles": 26, |
| 247 | "var-handles": 28, |
| 248 | } |
David Srbecky | e82c87d | 2022-11-16 20:31:11 +0000 | [diff] [blame] | 249 | api_level = API_LEVEL[api_level] |
| 250 | assert isinstance(api_level, int), api_level |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 251 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 252 | def zip(zip_target: Path, *files: Path): |
| 253 | zip_args = ["-o", zip_target, "-C", zip_target.parent] |
| 254 | if zip_compression_method == "store": |
| 255 | zip_args.extend(["-L", "0"]) |
| 256 | for f in files: |
| 257 | zip_args.extend(["-f", f]) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 258 | self.soong_zip(zip_args) |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 259 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 260 | if zip_align_bytes: |
| 261 | # zipalign does not operate in-place, so write results to a temp file. |
| 262 | with TemporaryDirectory() as tmp_dir: |
| 263 | tmp_file = Path(tmp_dir) / "aligned.zip" |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 264 | self.zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file]) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 265 | # replace original zip target with our temp file. |
| 266 | tmp_file.rename(zip_target) |
| 267 | |
| 268 | |
| 269 | def make_jasmin(dst_dir: Path, src_dir: Path) -> Optional[Path]: |
| 270 | if not use_jasmin or not src_dir.exists(): |
| 271 | return None # No sources to compile. |
| 272 | dst_dir.mkdir() |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 273 | self.jasmin(["-d", dst_dir] + sorted(src_dir.glob("**/*.j"))) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 274 | return dst_dir |
| 275 | |
| 276 | def make_smali(dst_dex: Path, src_dir: Path) -> Optional[Path]: |
| 277 | if not use_smali or not src_dir.exists(): |
| 278 | return None # No sources to compile. |
David Srbecky | d7c770a | 2023-10-26 15:45:57 +0100 | [diff] [blame] | 279 | p = self.smali(["-JXmx512m", "assemble"] + smali_args + ["--api", str(api_level)] + |
| 280 | ["--output", dst_dex] + sorted(src_dir.glob("**/*.smali"))) |
| 281 | assert dst_dex.exists(), p.stdout # NB: smali returns 0 exit code even on failure. |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 282 | return dst_dex |
| 283 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 284 | def make_java(dst_dir: Path, *src_dirs: Path) -> Optional[Path]: |
| 285 | if not any(src_dir.exists() for src_dir in src_dirs): |
| 286 | return None # No sources to compile. |
| 287 | dst_dir.mkdir(exist_ok=True) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 288 | args = self.javac_args.split(" ") + javac_args |
| 289 | args += ["-implicit:none", "-encoding", "utf8", "-d", dst_dir] |
Nicolas Geoffray | bb8d6f6 | 2023-09-12 11:04:23 +0100 | [diff] [blame] | 290 | args += ["-source", javac_source_arg, "-target", javac_target_arg] |
| 291 | if not self.jvm and float(javac_target_arg) < 17.0: |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 292 | args += ["-bootclasspath", self.bootclasspath] |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 293 | if javac_classpath: |
| 294 | args += ["-classpath", javac_classpath] |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 295 | for src_dir in src_dirs: |
| 296 | args += sorted(src_dir.glob("**/*.java")) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 297 | self.javac(args) |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 298 | javac_post = Path("javac_post.sh") |
| 299 | if javac_post.exists(): |
| 300 | self.run(javac_post, [dst_dir]) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 301 | return dst_dir |
| 302 | |
| 303 | |
| 304 | # Make a "dex" file given a directory of classes. This will be |
| 305 | # packaged in a jar file. |
| 306 | def make_dex(src_dir: Path): |
| 307 | dst_jar = Path(src_dir.name + ".jar") |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 308 | args = d8_flags + ["--min-api", str(api_level), "--output", dst_jar] |
| 309 | args += ["--lib", self.bootclasspath] if use_desugar else ["--no-desugaring"] |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 310 | args += sorted(src_dir.glob("**/*.class")) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 311 | self.d8(args) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 312 | |
| 313 | # D8 outputs to JAR files today rather than DEX files as DX used |
| 314 | # to. To compensate, we extract the DEX from d8's output to meet the |
| 315 | # expectations of make_dex callers. |
| 316 | dst_dex = Path(src_dir.name + ".dex") |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 317 | with TemporaryDirectory() as tmp_dir: |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 318 | zipfile.ZipFile(dst_jar, "r").extractall(tmp_dir) |
| 319 | (Path(tmp_dir) / "classes.dex").rename(dst_dex) |
| 320 | |
| 321 | # Merge all the dex files. |
| 322 | # Skip non-existing files, but at least 1 file must exist. |
| 323 | def make_dexmerge(dst_dex: Path, *src_dexs: Path): |
| 324 | # Include destination. Skip any non-existing files. |
| 325 | srcs = [f for f in [dst_dex] + list(src_dexs) if f.exists()] |
| 326 | |
| 327 | # NB: We merge even if there is just single input. |
| 328 | # It is useful to normalize non-deterministic smali output. |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 329 | tmp_dir = self.test_dir / "dexmerge" |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 330 | tmp_dir.mkdir() |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 331 | self.d8(["--min-api", str(api_level), "--output", tmp_dir] + srcs) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 332 | assert not (tmp_dir / "classes2.dex").exists() |
| 333 | for src_file in srcs: |
| 334 | src_file.unlink() |
| 335 | (tmp_dir / "classes.dex").rename(dst_dex) |
| 336 | tmp_dir.rmdir() |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 337 | |
| 338 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 339 | def make_hiddenapi(*dex_files: Path): |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 340 | if not use_hiddenapi or not Path("hiddenapi-flags.csv").exists(): |
| 341 | return # Nothing to do. |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 342 | args: List[Union[str, Path]] = ["encode"] |
| 343 | for dex_file in dex_files: |
| 344 | args.extend(["--input-dex=" + str(dex_file), "--output-dex=" + str(dex_file)]) |
| 345 | args.append("--api-flags=hiddenapi-flags.csv") |
| 346 | args.append("--no-force-assign-all") |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 347 | self.hiddenapi(args) |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 348 | |
| 349 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 350 | if Path("classes.dex").exists(): |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 351 | zip(Path(self.test_name + ".jar"), Path("classes.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 352 | return |
| 353 | |
| 354 | if Path("classes.dm").exists(): |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 355 | zip(Path(self.test_name + ".jar"), Path("classes.dm")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 356 | return |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 357 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 358 | if make_jasmin(Path("jasmin_classes"), Path("jasmin")): |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 359 | javac_classpath.append(Path("jasmin_classes")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 360 | |
| 361 | if make_jasmin(Path("jasmin_classes2"), Path("jasmin-multidex")): |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 362 | javac_classpath.append(Path("jasmin_classes2")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 363 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 364 | # To allow circular references, compile src/, src-multidex/, src-aotex/, |
| 365 | # src-bcpex/, src-ex/ together and pass the output as class path argument. |
| 366 | # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols |
| 367 | # used by the other src-* sources we compile here but everything needed to |
| 368 | # compile the other src-* sources should be present in src/ (and jasmin*/). |
| 369 | extra_srcs = ["src-multidex", "src-aotex", "src-bcpex", "src-ex"] |
| 370 | replacement_srcs = ["src2", "src-ex2"] + ([] if self.jvm else ["src-art"]) |
| 371 | if (Path("src").exists() and |
| 372 | any(Path(p).exists() for p in extra_srcs + replacement_srcs)): |
| 373 | make_java(Path("classes-tmp-all"), Path("src"), *map(Path, extra_srcs)) |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 374 | javac_classpath.append(Path("classes-tmp-all")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 375 | |
| 376 | if make_java(Path("classes-aotex"), Path("src-aotex")) and need_dex: |
| 377 | make_dex(Path("classes-aotex")) |
| 378 | # rename it so it shows up as "classes.dex" in the zip file. |
| 379 | Path("classes-aotex.dex").rename(Path("classes.dex")) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 380 | zip(Path(self.test_name + "-aotex.jar"), Path("classes.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 381 | |
| 382 | if make_java(Path("classes-bcpex"), Path("src-bcpex")) and need_dex: |
| 383 | make_dex(Path("classes-bcpex")) |
| 384 | # rename it so it shows up as "classes.dex" in the zip file. |
| 385 | Path("classes-bcpex.dex").rename(Path("classes.dex")) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 386 | zip(Path(self.test_name + "-bcpex.jar"), Path("classes.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 387 | |
| 388 | make_java(Path("classes"), Path("src")) |
| 389 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 390 | if not self.jvm: |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 391 | # Do not attempt to build src-art directories on jvm, |
| 392 | # since it would fail without libcore. |
| 393 | make_java(Path("classes"), Path("src-art")) |
| 394 | |
| 395 | if make_java(Path("classes2"), Path("src-multidex")) and need_dex: |
| 396 | make_dex(Path("classes2")) |
| 397 | |
| 398 | make_java(Path("classes"), Path("src2")) |
| 399 | |
| 400 | # If the classes directory is not-empty, package classes in a DEX file. |
| 401 | # NB: some tests provide classes rather than java files. |
| 402 | if any(Path("classes").glob("*")) and need_dex: |
| 403 | make_dex(Path("classes")) |
| 404 | |
| 405 | if Path("jasmin_classes").exists(): |
| 406 | # Compile Jasmin classes as if they were part of the classes.dex file. |
| 407 | if need_dex: |
| 408 | make_dex(Path("jasmin_classes")) |
| 409 | make_dexmerge(Path("classes.dex"), Path("jasmin_classes.dex")) |
| 410 | else: |
| 411 | # Move jasmin classes into classes directory so that they are picked up |
| 412 | # with -cp classes. |
| 413 | Path("classes").mkdir(exist_ok=True) |
| 414 | copytree(Path("jasmin_classes"), Path("classes"), dirs_exist_ok=True) |
| 415 | |
| 416 | if need_dex and make_smali(Path("smali_classes.dex"), Path("smali")): |
| 417 | # Merge smali files into classes.dex, |
| 418 | # this takes priority over any jasmin files. |
| 419 | make_dexmerge(Path("classes.dex"), Path("smali_classes.dex")) |
| 420 | |
| 421 | # Compile Jasmin classes in jasmin-multidex as if they were part of |
| 422 | # the classes2.jar |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 423 | if Path("jasmin-multidex").exists(): |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 424 | if need_dex: |
| 425 | make_dex(Path("jasmin_classes2")) |
| 426 | make_dexmerge(Path("classes2.dex"), Path("jasmin_classes2.dex")) |
| 427 | else: |
| 428 | # Move jasmin classes into classes2 directory so that |
| 429 | # they are picked up with -cp classes2. |
| 430 | Path("classes2").mkdir() |
| 431 | copytree(Path("jasmin_classes2"), Path("classes2"), dirs_exist_ok=True) |
| 432 | rmtree(Path("jasmin_classes2")) |
| 433 | |
| 434 | if need_dex and make_smali(Path("smali_classes2.dex"), Path("smali-multidex")): |
| 435 | # Merge smali_classes2.dex into classes2.dex |
| 436 | make_dexmerge(Path("classes2.dex"), Path("smali_classes2.dex")) |
| 437 | |
| 438 | make_java(Path("classes-ex"), Path("src-ex")) |
| 439 | |
| 440 | make_java(Path("classes-ex"), Path("src-ex2")) |
| 441 | |
| 442 | if Path("classes-ex").exists() and need_dex: |
| 443 | make_dex(Path("classes-ex")) |
| 444 | |
| 445 | if need_dex and make_smali(Path("smali_classes-ex.dex"), Path("smali-ex")): |
| 446 | # Merge smali files into classes-ex.dex. |
| 447 | make_dexmerge(Path("classes-ex.dex"), Path("smali_classes-ex.dex")) |
| 448 | |
| 449 | if Path("classes-ex.dex").exists(): |
| 450 | # Apply hiddenapi on the dex files if the test has API list file(s). |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 451 | make_hiddenapi(Path("classes-ex.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 452 | |
| 453 | # quick shuffle so that the stored name is "classes.dex" |
| 454 | Path("classes.dex").rename(Path("classes-1.dex")) |
| 455 | Path("classes-ex.dex").rename(Path("classes.dex")) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 456 | zip(Path(self.test_name + "-ex.jar"), Path("classes.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 457 | Path("classes.dex").rename(Path("classes-ex.dex")) |
| 458 | Path("classes-1.dex").rename(Path("classes.dex")) |
| 459 | |
| 460 | # Apply hiddenapi on the dex files if the test has API list file(s). |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 461 | if need_dex: |
| 462 | if any(Path(".").glob("*-multidex")): |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 463 | make_hiddenapi(Path("classes.dex"), Path("classes2.dex")) |
| 464 | else: |
| 465 | make_hiddenapi(Path("classes.dex")) |
| 466 | |
| 467 | # Create a single dex jar with two dex files for multidex. |
| 468 | if need_dex: |
| 469 | if Path("classes2.dex").exists(): |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 470 | zip(Path(self.test_name + ".jar"), Path("classes.dex"), Path("classes2.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 471 | else: |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 472 | zip(Path(self.test_name + ".jar"), Path("classes.dex")) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 473 | |
| 474 | |
| 475 | # If we build just individual shard, we want to split the work among all the cores, |
| 476 | # but if the build system builds all shards, we don't want to overload the machine. |
| 477 | # We don't know which situation we are in, so as simple work-around, we use a lock |
| 478 | # file to allow only one shard to use multiprocessing at the same time. |
| 479 | def use_multiprocessing(mode: str) -> bool: |
David Srbecky | d26f907 | 2022-12-09 16:40:40 +0000 | [diff] [blame] | 480 | if "RBE_server_address" in os.environ: |
| 481 | return True |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 482 | global lock_file |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 483 | lock_path = Path(environ["TMPDIR"]) / ("art-test-run-test-build-py-" + mode) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 484 | lock_file = open(lock_path, "w") |
| 485 | try: |
| 486 | lockf(lock_file, LOCK_EX | LOCK_NB) |
| 487 | return True # We are the only instance of this script in the build system. |
| 488 | except BlockingIOError: |
| 489 | return False # Some other instance is already running. |
| 490 | |
| 491 | |
| 492 | def main() -> None: |
| 493 | parser = ArgumentParser(description=__doc__) |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 494 | parser.add_argument("--out", type=Path, help="Final zip file") |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 495 | parser.add_argument("--mode", choices=["host", "jvm", "target"]) |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 496 | parser.add_argument("--bootclasspath", type=Path) |
| 497 | parser.add_argument("--d8", type=Path) |
| 498 | parser.add_argument("--hiddenapi", type=Path) |
| 499 | parser.add_argument("--jasmin", type=Path) |
| 500 | parser.add_argument("--smali", type=Path) |
| 501 | parser.add_argument("--soong_zip", type=Path) |
| 502 | parser.add_argument("--zipalign", type=Path) |
| 503 | parser.add_argument("srcs", nargs="+", type=Path) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 504 | args = parser.parse_args() |
| 505 | |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 506 | android_build_top = Path(getcwd()).absolute() |
| 507 | ziproot = args.out.absolute().parent / "zip" |
| 508 | srcdirs = set(s.parents[-4].absolute() for s in args.srcs) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 509 | |
David Srbecky | c282bef | 2022-11-16 21:52:33 +0000 | [diff] [blame] | 510 | # Special hidden-api shard: If the --hiddenapi flag is provided, build only |
| 511 | # hiddenapi tests. Otherwise exclude all hiddenapi tests from normal shards. |
| 512 | def filter_by_hiddenapi(srcdir: Path) -> bool: |
| 513 | return (args.hiddenapi != None) == ("hiddenapi" in srcdir.name) |
| 514 | |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 515 | # Initialize the test objects. |
| 516 | # We need to do this before we change the working directory below. |
| 517 | tests: List[BuildTestContext] = [] |
David Srbecky | c282bef | 2022-11-16 21:52:33 +0000 | [diff] [blame] | 518 | for srcdir in filter(filter_by_hiddenapi, srcdirs): |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 519 | dstdir = ziproot / args.mode / srcdir.name |
| 520 | copytree(srcdir, dstdir) |
| 521 | tests.append(BuildTestContext(args, android_build_top, dstdir)) |
| 522 | |
| 523 | # We can not change the working directory per each thread since they all run in parallel. |
| 524 | # Create invalid read-only directory to catch accidental use of current working directory. |
| 525 | with TemporaryDirectory("-do-not-use-cwd") as invalid_tmpdir: |
| 526 | os.chdir(invalid_tmpdir) |
| 527 | os.chmod(invalid_tmpdir, 0) |
| 528 | with ThreadPoolExecutor(cpu_count() if use_multiprocessing(args.mode) else 1) as pool: |
| 529 | jobs = {} |
| 530 | for ctx in tests: |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 531 | jobs[ctx.test_name] = pool.submit(ctx.build) |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 532 | for test_name, job in jobs.items(): |
| 533 | try: |
| 534 | job.result() |
| 535 | except Exception as e: |
| 536 | raise Exception("Failed to build " + test_name) from e |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 537 | |
| 538 | # Create the final zip file which contains the content of the temporary directory. |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 539 | proc = run([android_build_top / args.soong_zip, "-o", android_build_top / args.out, |
| 540 | "-C", ziproot, "-D", ziproot], check=True) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 541 | |
| 542 | |
| 543 | if __name__ == "__main__": |
| 544 | main() |