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=[], |
David Srbecky | 5424283 | 2023-11-16 15:41:46 +0000 | [diff] [blame] | 219 | d8_dex_container=True, |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 220 | smali_args=[], |
| 221 | use_smali=True, |
| 222 | use_jasmin=True, |
Nicolas Geoffray | bb8d6f6 | 2023-09-12 11:04:23 +0100 | [diff] [blame] | 223 | javac_source_arg="1.8", |
| 224 | javac_target_arg="1.8" |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 225 | ): |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 226 | javac_classpath = javac_classpath.copy() # Do not modify default value. |
David Srbecky | 51dec05 | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 227 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 228 | # 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] | 229 | # 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] | 230 | class Path(pathlib.Path): |
| 231 | def __new__(cls, filename: str): |
| 232 | path = pathlib.Path(filename) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 233 | return path if path.is_absolute() else (self.test_dir / path) |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 234 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 235 | 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] | 236 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 237 | if self.jvm: |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 238 | # No desugaring on jvm because it supports the latest functionality. |
| 239 | use_desugar = False |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 240 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 241 | # Set API level for smali and d8. |
David Srbecky | e82c87d | 2022-11-16 20:31:11 +0000 | [diff] [blame] | 242 | if isinstance(api_level, str): |
| 243 | API_LEVEL = { |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 244 | "default-methods": 24, |
| 245 | "parameter-annotations": 25, |
| 246 | "agents": 26, |
| 247 | "method-handles": 26, |
| 248 | "var-handles": 28, |
Almaz Mingaleev | ee42bfa | 2023-11-22 15:49:15 +0000 | [diff] [blame] | 249 | "const-method-type": 28, |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 250 | } |
David Srbecky | e82c87d | 2022-11-16 20:31:11 +0000 | [diff] [blame] | 251 | api_level = API_LEVEL[api_level] |
| 252 | assert isinstance(api_level, int), api_level |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 253 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 254 | def zip(zip_target: Path, *files: Path): |
| 255 | zip_args = ["-o", zip_target, "-C", zip_target.parent] |
| 256 | if zip_compression_method == "store": |
| 257 | zip_args.extend(["-L", "0"]) |
| 258 | for f in files: |
| 259 | zip_args.extend(["-f", f]) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 260 | self.soong_zip(zip_args) |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 261 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 262 | if zip_align_bytes: |
| 263 | # zipalign does not operate in-place, so write results to a temp file. |
| 264 | with TemporaryDirectory() as tmp_dir: |
| 265 | tmp_file = Path(tmp_dir) / "aligned.zip" |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 266 | self.zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file]) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 267 | # replace original zip target with our temp file. |
| 268 | tmp_file.rename(zip_target) |
| 269 | |
| 270 | |
| 271 | def make_jasmin(dst_dir: Path, src_dir: Path) -> Optional[Path]: |
| 272 | if not use_jasmin or not src_dir.exists(): |
| 273 | return None # No sources to compile. |
| 274 | dst_dir.mkdir() |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 275 | self.jasmin(["-d", dst_dir] + sorted(src_dir.glob("**/*.j"))) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 276 | return dst_dir |
| 277 | |
| 278 | def make_smali(dst_dex: Path, src_dir: Path) -> Optional[Path]: |
| 279 | if not use_smali or not src_dir.exists(): |
| 280 | return None # No sources to compile. |
David Srbecky | d7c770a | 2023-10-26 15:45:57 +0100 | [diff] [blame] | 281 | p = self.smali(["-JXmx512m", "assemble"] + smali_args + ["--api", str(api_level)] + |
| 282 | ["--output", dst_dex] + sorted(src_dir.glob("**/*.smali"))) |
| 283 | 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] | 284 | return dst_dex |
| 285 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 286 | def make_java(dst_dir: Path, *src_dirs: Path) -> Optional[Path]: |
| 287 | if not any(src_dir.exists() for src_dir in src_dirs): |
| 288 | return None # No sources to compile. |
| 289 | dst_dir.mkdir(exist_ok=True) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 290 | args = self.javac_args.split(" ") + javac_args |
| 291 | args += ["-implicit:none", "-encoding", "utf8", "-d", dst_dir] |
Nicolas Geoffray | bb8d6f6 | 2023-09-12 11:04:23 +0100 | [diff] [blame] | 292 | args += ["-source", javac_source_arg, "-target", javac_target_arg] |
| 293 | if not self.jvm and float(javac_target_arg) < 17.0: |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 294 | args += ["-bootclasspath", self.bootclasspath] |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 295 | if javac_classpath: |
| 296 | args += ["-classpath", javac_classpath] |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 297 | for src_dir in src_dirs: |
| 298 | args += sorted(src_dir.glob("**/*.java")) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 299 | self.javac(args) |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 300 | javac_post = Path("javac_post.sh") |
| 301 | if javac_post.exists(): |
| 302 | self.run(javac_post, [dst_dir]) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 303 | return dst_dir |
| 304 | |
| 305 | |
| 306 | # Make a "dex" file given a directory of classes. This will be |
| 307 | # packaged in a jar file. |
| 308 | def make_dex(src_dir: Path): |
| 309 | dst_jar = Path(src_dir.name + ".jar") |
David Srbecky | 5424283 | 2023-11-16 15:41:46 +0000 | [diff] [blame] | 310 | args = [] |
| 311 | if d8_dex_container: |
| 312 | args += ["-JDcom.android.tools.r8.dexContainerExperiment"] |
| 313 | args += d8_flags + ["--min-api", str(api_level), "--output", dst_jar] |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 314 | args += ["--lib", self.bootclasspath] if use_desugar else ["--no-desugaring"] |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 315 | args += sorted(src_dir.glob("**/*.class")) |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 316 | self.d8(args) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 317 | |
| 318 | # D8 outputs to JAR files today rather than DEX files as DX used |
| 319 | # to. To compensate, we extract the DEX from d8's output to meet the |
| 320 | # expectations of make_dex callers. |
| 321 | dst_dex = Path(src_dir.name + ".dex") |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 322 | with TemporaryDirectory() as tmp_dir: |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 323 | zipfile.ZipFile(dst_jar, "r").extractall(tmp_dir) |
| 324 | (Path(tmp_dir) / "classes.dex").rename(dst_dex) |
| 325 | |
| 326 | # Merge all the dex files. |
| 327 | # Skip non-existing files, but at least 1 file must exist. |
| 328 | def make_dexmerge(dst_dex: Path, *src_dexs: Path): |
| 329 | # Include destination. Skip any non-existing files. |
| 330 | srcs = [f for f in [dst_dex] + list(src_dexs) if f.exists()] |
| 331 | |
| 332 | # NB: We merge even if there is just single input. |
| 333 | # It is useful to normalize non-deterministic smali output. |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 334 | tmp_dir = self.test_dir / "dexmerge" |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 335 | tmp_dir.mkdir() |
David Srbecky | 5424283 | 2023-11-16 15:41:46 +0000 | [diff] [blame] | 336 | flags = [] |
| 337 | if d8_dex_container: |
| 338 | flags += ["-JDcom.android.tools.r8.dexContainerExperiment"] |
| 339 | flags += ["--min-api", str(api_level), "--output", tmp_dir] |
| 340 | self.d8(flags + srcs) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 341 | assert not (tmp_dir / "classes2.dex").exists() |
| 342 | for src_file in srcs: |
| 343 | src_file.unlink() |
| 344 | (tmp_dir / "classes.dex").rename(dst_dex) |
| 345 | tmp_dir.rmdir() |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 346 | |
| 347 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 348 | def make_hiddenapi(*dex_files: Path): |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 349 | if not use_hiddenapi or not Path("hiddenapi-flags.csv").exists(): |
| 350 | return # Nothing to do. |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 351 | args: List[Union[str, Path]] = ["encode"] |
| 352 | for dex_file in dex_files: |
| 353 | args.extend(["--input-dex=" + str(dex_file), "--output-dex=" + str(dex_file)]) |
| 354 | args.append("--api-flags=hiddenapi-flags.csv") |
| 355 | args.append("--no-force-assign-all") |
David Srbecky | feed4af | 2022-11-17 10:44:39 +0000 | [diff] [blame] | 356 | self.hiddenapi(args) |
David Srbecky | 8106b38 | 2022-04-20 13:37:15 +0100 | [diff] [blame] | 357 | |
| 358 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 359 | if Path("classes.dex").exists(): |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 360 | zip(Path(self.test_name + ".jar"), Path("classes.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 361 | return |
| 362 | |
| 363 | if Path("classes.dm").exists(): |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 364 | zip(Path(self.test_name + ".jar"), Path("classes.dm")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 365 | return |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 366 | |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 367 | if make_jasmin(Path("jasmin_classes"), Path("jasmin")): |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 368 | javac_classpath.append(Path("jasmin_classes")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 369 | |
| 370 | if make_jasmin(Path("jasmin_classes2"), Path("jasmin-multidex")): |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 371 | javac_classpath.append(Path("jasmin_classes2")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 372 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 373 | # To allow circular references, compile src/, src-multidex/, src-aotex/, |
| 374 | # src-bcpex/, src-ex/ together and pass the output as class path argument. |
| 375 | # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols |
| 376 | # used by the other src-* sources we compile here but everything needed to |
| 377 | # compile the other src-* sources should be present in src/ (and jasmin*/). |
| 378 | extra_srcs = ["src-multidex", "src-aotex", "src-bcpex", "src-ex"] |
| 379 | replacement_srcs = ["src2", "src-ex2"] + ([] if self.jvm else ["src-art"]) |
| 380 | if (Path("src").exists() and |
| 381 | any(Path(p).exists() for p in extra_srcs + replacement_srcs)): |
| 382 | make_java(Path("classes-tmp-all"), Path("src"), *map(Path, extra_srcs)) |
David Srbecky | f32b258 | 2022-11-17 15:28:35 +0000 | [diff] [blame] | 383 | javac_classpath.append(Path("classes-tmp-all")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 384 | |
| 385 | if make_java(Path("classes-aotex"), Path("src-aotex")) and need_dex: |
| 386 | make_dex(Path("classes-aotex")) |
| 387 | # rename it so it shows up as "classes.dex" in the zip file. |
| 388 | Path("classes-aotex.dex").rename(Path("classes.dex")) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 389 | zip(Path(self.test_name + "-aotex.jar"), Path("classes.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 390 | |
| 391 | if make_java(Path("classes-bcpex"), Path("src-bcpex")) and need_dex: |
| 392 | make_dex(Path("classes-bcpex")) |
| 393 | # rename it so it shows up as "classes.dex" in the zip file. |
| 394 | Path("classes-bcpex.dex").rename(Path("classes.dex")) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 395 | zip(Path(self.test_name + "-bcpex.jar"), Path("classes.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 396 | |
| 397 | make_java(Path("classes"), Path("src")) |
| 398 | |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 399 | if not self.jvm: |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 400 | # Do not attempt to build src-art directories on jvm, |
| 401 | # since it would fail without libcore. |
| 402 | make_java(Path("classes"), Path("src-art")) |
| 403 | |
| 404 | if make_java(Path("classes2"), Path("src-multidex")) and need_dex: |
| 405 | make_dex(Path("classes2")) |
| 406 | |
| 407 | make_java(Path("classes"), Path("src2")) |
| 408 | |
| 409 | # If the classes directory is not-empty, package classes in a DEX file. |
| 410 | # NB: some tests provide classes rather than java files. |
| 411 | if any(Path("classes").glob("*")) and need_dex: |
| 412 | make_dex(Path("classes")) |
| 413 | |
| 414 | if Path("jasmin_classes").exists(): |
| 415 | # Compile Jasmin classes as if they were part of the classes.dex file. |
| 416 | if need_dex: |
| 417 | make_dex(Path("jasmin_classes")) |
| 418 | make_dexmerge(Path("classes.dex"), Path("jasmin_classes.dex")) |
| 419 | else: |
| 420 | # Move jasmin classes into classes directory so that they are picked up |
| 421 | # with -cp classes. |
| 422 | Path("classes").mkdir(exist_ok=True) |
| 423 | copytree(Path("jasmin_classes"), Path("classes"), dirs_exist_ok=True) |
| 424 | |
| 425 | if need_dex and make_smali(Path("smali_classes.dex"), Path("smali")): |
| 426 | # Merge smali files into classes.dex, |
| 427 | # this takes priority over any jasmin files. |
| 428 | make_dexmerge(Path("classes.dex"), Path("smali_classes.dex")) |
| 429 | |
| 430 | # Compile Jasmin classes in jasmin-multidex as if they were part of |
| 431 | # the classes2.jar |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 432 | if Path("jasmin-multidex").exists(): |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 433 | if need_dex: |
| 434 | make_dex(Path("jasmin_classes2")) |
| 435 | make_dexmerge(Path("classes2.dex"), Path("jasmin_classes2.dex")) |
| 436 | else: |
| 437 | # Move jasmin classes into classes2 directory so that |
| 438 | # they are picked up with -cp classes2. |
| 439 | Path("classes2").mkdir() |
| 440 | copytree(Path("jasmin_classes2"), Path("classes2"), dirs_exist_ok=True) |
| 441 | rmtree(Path("jasmin_classes2")) |
| 442 | |
| 443 | if need_dex and make_smali(Path("smali_classes2.dex"), Path("smali-multidex")): |
| 444 | # Merge smali_classes2.dex into classes2.dex |
| 445 | make_dexmerge(Path("classes2.dex"), Path("smali_classes2.dex")) |
| 446 | |
| 447 | make_java(Path("classes-ex"), Path("src-ex")) |
| 448 | |
| 449 | make_java(Path("classes-ex"), Path("src-ex2")) |
| 450 | |
| 451 | if Path("classes-ex").exists() and need_dex: |
| 452 | make_dex(Path("classes-ex")) |
| 453 | |
| 454 | if need_dex and make_smali(Path("smali_classes-ex.dex"), Path("smali-ex")): |
| 455 | # Merge smali files into classes-ex.dex. |
| 456 | make_dexmerge(Path("classes-ex.dex"), Path("smali_classes-ex.dex")) |
| 457 | |
| 458 | if Path("classes-ex.dex").exists(): |
| 459 | # 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] | 460 | make_hiddenapi(Path("classes-ex.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 461 | |
| 462 | # quick shuffle so that the stored name is "classes.dex" |
| 463 | Path("classes.dex").rename(Path("classes-1.dex")) |
| 464 | Path("classes-ex.dex").rename(Path("classes.dex")) |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 465 | zip(Path(self.test_name + "-ex.jar"), Path("classes.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 466 | Path("classes.dex").rename(Path("classes-ex.dex")) |
| 467 | Path("classes-1.dex").rename(Path("classes.dex")) |
| 468 | |
| 469 | # 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] | 470 | if need_dex: |
| 471 | if any(Path(".").glob("*-multidex")): |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 472 | make_hiddenapi(Path("classes.dex"), Path("classes2.dex")) |
| 473 | else: |
| 474 | make_hiddenapi(Path("classes.dex")) |
| 475 | |
| 476 | # Create a single dex jar with two dex files for multidex. |
| 477 | if need_dex: |
| 478 | if Path("classes2.dex").exists(): |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 479 | zip(Path(self.test_name + ".jar"), Path("classes.dex"), Path("classes2.dex")) |
David Srbecky | af0ad5a | 2022-11-15 18:28:35 +0000 | [diff] [blame] | 480 | else: |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 481 | zip(Path(self.test_name + ".jar"), Path("classes.dex")) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 482 | |
| 483 | |
| 484 | # If we build just individual shard, we want to split the work among all the cores, |
| 485 | # but if the build system builds all shards, we don't want to overload the machine. |
| 486 | # We don't know which situation we are in, so as simple work-around, we use a lock |
| 487 | # file to allow only one shard to use multiprocessing at the same time. |
| 488 | def use_multiprocessing(mode: str) -> bool: |
David Srbecky | d26f907 | 2022-12-09 16:40:40 +0000 | [diff] [blame] | 489 | if "RBE_server_address" in os.environ: |
| 490 | return True |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 491 | global lock_file |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 492 | lock_path = Path(environ["TMPDIR"]) / ("art-test-run-test-build-py-" + mode) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 493 | lock_file = open(lock_path, "w") |
| 494 | try: |
| 495 | lockf(lock_file, LOCK_EX | LOCK_NB) |
| 496 | return True # We are the only instance of this script in the build system. |
| 497 | except BlockingIOError: |
| 498 | return False # Some other instance is already running. |
| 499 | |
| 500 | |
| 501 | def main() -> None: |
| 502 | parser = ArgumentParser(description=__doc__) |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 503 | parser.add_argument("--out", type=Path, help="Final zip file") |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 504 | parser.add_argument("--mode", choices=["host", "jvm", "target"]) |
David Srbecky | 6bd1cd1 | 2022-11-05 18:54:19 +0000 | [diff] [blame] | 505 | parser.add_argument("--bootclasspath", type=Path) |
| 506 | parser.add_argument("--d8", type=Path) |
| 507 | parser.add_argument("--hiddenapi", type=Path) |
| 508 | parser.add_argument("--jasmin", type=Path) |
| 509 | parser.add_argument("--smali", type=Path) |
| 510 | parser.add_argument("--soong_zip", type=Path) |
| 511 | parser.add_argument("--zipalign", type=Path) |
| 512 | parser.add_argument("srcs", nargs="+", type=Path) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 513 | args = parser.parse_args() |
| 514 | |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 515 | android_build_top = Path(getcwd()).absolute() |
| 516 | ziproot = args.out.absolute().parent / "zip" |
| 517 | srcdirs = set(s.parents[-4].absolute() for s in args.srcs) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 518 | |
David Srbecky | c282bef | 2022-11-16 21:52:33 +0000 | [diff] [blame] | 519 | # Special hidden-api shard: If the --hiddenapi flag is provided, build only |
| 520 | # hiddenapi tests. Otherwise exclude all hiddenapi tests from normal shards. |
| 521 | def filter_by_hiddenapi(srcdir: Path) -> bool: |
| 522 | return (args.hiddenapi != None) == ("hiddenapi" in srcdir.name) |
| 523 | |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 524 | # Initialize the test objects. |
| 525 | # We need to do this before we change the working directory below. |
| 526 | tests: List[BuildTestContext] = [] |
David Srbecky | c282bef | 2022-11-16 21:52:33 +0000 | [diff] [blame] | 527 | for srcdir in filter(filter_by_hiddenapi, srcdirs): |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 528 | dstdir = ziproot / args.mode / srcdir.name |
| 529 | copytree(srcdir, dstdir) |
| 530 | tests.append(BuildTestContext(args, android_build_top, dstdir)) |
| 531 | |
| 532 | # We can not change the working directory per each thread since they all run in parallel. |
| 533 | # Create invalid read-only directory to catch accidental use of current working directory. |
| 534 | with TemporaryDirectory("-do-not-use-cwd") as invalid_tmpdir: |
| 535 | os.chdir(invalid_tmpdir) |
| 536 | os.chmod(invalid_tmpdir, 0) |
| 537 | with ThreadPoolExecutor(cpu_count() if use_multiprocessing(args.mode) else 1) as pool: |
| 538 | jobs = {} |
| 539 | for ctx in tests: |
David Srbecky | d272f2d | 2022-11-15 19:13:34 +0000 | [diff] [blame] | 540 | jobs[ctx.test_name] = pool.submit(ctx.build) |
David Srbecky | 36b520b | 2022-10-27 14:43:12 +0100 | [diff] [blame] | 541 | for test_name, job in jobs.items(): |
| 542 | try: |
| 543 | job.result() |
| 544 | except Exception as e: |
| 545 | raise Exception("Failed to build " + test_name) from e |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 546 | |
| 547 | # 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] | 548 | proc = run([android_build_top / args.soong_zip, "-o", android_build_top / args.out, |
| 549 | "-C", ziproot, "-D", ziproot], check=True) |
David Srbecky | 37274d5 | 2022-11-03 11:04:16 +0000 | [diff] [blame] | 550 | |
| 551 | |
| 552 | if __name__ == "__main__": |
| 553 | main() |