1*795d594fSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*795d594fSAndroid Build Coastguard Worker# 3*795d594fSAndroid Build Coastguard Worker# Copyright (C) 2021 The Android Open Source Project 4*795d594fSAndroid Build Coastguard Worker# 5*795d594fSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*795d594fSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*795d594fSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*795d594fSAndroid Build Coastguard Worker# 9*795d594fSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*795d594fSAndroid Build Coastguard Worker# 11*795d594fSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*795d594fSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*795d594fSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*795d594fSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*795d594fSAndroid Build Coastguard Worker# limitations under the License. 16*795d594fSAndroid Build Coastguard Worker 17*795d594fSAndroid Build Coastguard Worker""" 18*795d594fSAndroid Build Coastguard WorkerThis scripts compiles Java files which are needed to execute run-tests. 19*795d594fSAndroid Build Coastguard WorkerIt is intended to be used only from soong genrule. 20*795d594fSAndroid Build Coastguard Worker""" 21*795d594fSAndroid Build Coastguard Worker 22*795d594fSAndroid Build Coastguard Workerimport functools 23*795d594fSAndroid Build Coastguard Workerimport json 24*795d594fSAndroid Build Coastguard Workerimport os 25*795d594fSAndroid Build Coastguard Workerimport pathlib 26*795d594fSAndroid Build Coastguard Workerimport re 27*795d594fSAndroid Build Coastguard Workerimport subprocess 28*795d594fSAndroid Build Coastguard Workerimport sys 29*795d594fSAndroid Build Coastguard Workerimport zipfile 30*795d594fSAndroid Build Coastguard Worker 31*795d594fSAndroid Build Coastguard Workerfrom argparse import ArgumentParser 32*795d594fSAndroid Build Coastguard Workerfrom concurrent.futures import ThreadPoolExecutor 33*795d594fSAndroid Build Coastguard Workerfrom fcntl import lockf, LOCK_EX, LOCK_NB 34*795d594fSAndroid Build Coastguard Workerfrom importlib.machinery import SourceFileLoader 35*795d594fSAndroid Build Coastguard Workerfrom os import environ, getcwd, cpu_count 36*795d594fSAndroid Build Coastguard Workerfrom os.path import relpath 37*795d594fSAndroid Build Coastguard Workerfrom pathlib import Path 38*795d594fSAndroid Build Coastguard Workerfrom pprint import pprint 39*795d594fSAndroid Build Coastguard Workerfrom shutil import copytree, rmtree 40*795d594fSAndroid Build Coastguard Workerfrom subprocess import PIPE, run 41*795d594fSAndroid Build Coastguard Workerfrom tempfile import TemporaryDirectory, NamedTemporaryFile 42*795d594fSAndroid Build Coastguard Workerfrom typing import Dict, List, Union, Set, Optional 43*795d594fSAndroid Build Coastguard Workerfrom multiprocessing import cpu_count 44*795d594fSAndroid Build Coastguard Worker 45*795d594fSAndroid Build Coastguard Workerfrom globals import BOOTCLASSPATH 46*795d594fSAndroid Build Coastguard Worker 47*795d594fSAndroid Build Coastguard WorkerUSE_RBE = 100 # Percentage of tests that can use RBE (between 0 and 100) 48*795d594fSAndroid Build Coastguard Worker 49*795d594fSAndroid Build Coastguard Workerlock_file = None # Keep alive as long as this process is alive. 50*795d594fSAndroid Build Coastguard Worker 51*795d594fSAndroid Build Coastguard WorkerRBE_COMPARE = False # Debugging: Check that RBE and local output are identical. 52*795d594fSAndroid Build Coastguard Worker 53*795d594fSAndroid Build Coastguard WorkerRBE_D8_DISABLED_FOR = { 54*795d594fSAndroid Build Coastguard Worker "952-invoke-custom", # b/228312861: RBE uses wrong inputs. 55*795d594fSAndroid Build Coastguard Worker "979-const-method-handle", # b/228312861: RBE uses wrong inputs. 56*795d594fSAndroid Build Coastguard Worker} 57*795d594fSAndroid Build Coastguard Worker 58*795d594fSAndroid Build Coastguard Worker# Debug option. Report commands that are taking a lot of user CPU time. 59*795d594fSAndroid Build Coastguard WorkerREPORT_SLOW_COMMANDS = False 60*795d594fSAndroid Build Coastguard Worker 61*795d594fSAndroid Build Coastguard Workerclass BuildTestContext: 62*795d594fSAndroid Build Coastguard Worker def __init__(self, args, android_build_top, test_dir): 63*795d594fSAndroid Build Coastguard Worker self.android_build_top = android_build_top.absolute() 64*795d594fSAndroid Build Coastguard Worker self.bootclasspath = args.bootclasspath.absolute() 65*795d594fSAndroid Build Coastguard Worker self.test_name = test_dir.name 66*795d594fSAndroid Build Coastguard Worker self.test_dir = test_dir.absolute() 67*795d594fSAndroid Build Coastguard Worker self.mode = args.mode 68*795d594fSAndroid Build Coastguard Worker self.jvm = (self.mode == "jvm") 69*795d594fSAndroid Build Coastguard Worker self.host = (self.mode == "host") 70*795d594fSAndroid Build Coastguard Worker self.target = (self.mode == "target") 71*795d594fSAndroid Build Coastguard Worker assert self.jvm or self.host or self.target 72*795d594fSAndroid Build Coastguard Worker 73*795d594fSAndroid Build Coastguard Worker self.java_home = Path(os.environ.get("JAVA_HOME")).absolute() 74*795d594fSAndroid Build Coastguard Worker self.java_path = self.java_home / "bin/java" 75*795d594fSAndroid Build Coastguard Worker self.javac_path = self.java_home / "bin/javac" 76*795d594fSAndroid Build Coastguard Worker self.javac_args = "-g -Xlint:-options" 77*795d594fSAndroid Build Coastguard Worker 78*795d594fSAndroid Build Coastguard Worker # Helper functions to execute tools. 79*795d594fSAndroid Build Coastguard Worker self.d8_path = args.d8.absolute() 80*795d594fSAndroid Build Coastguard Worker self.d8 = functools.partial(self.run, args.d8.absolute()) 81*795d594fSAndroid Build Coastguard Worker self.jasmin = functools.partial(self.run, args.jasmin.absolute()) 82*795d594fSAndroid Build Coastguard Worker self.javac = functools.partial(self.run, self.javac_path) 83*795d594fSAndroid Build Coastguard Worker self.smali_path = args.smali.absolute() 84*795d594fSAndroid Build Coastguard Worker self.rbe_rewrapper = args.rewrapper.absolute() 85*795d594fSAndroid Build Coastguard Worker self.smali = functools.partial(self.run, args.smali.absolute()) 86*795d594fSAndroid Build Coastguard Worker self.soong_zip = functools.partial(self.run, args.soong_zip.absolute()) 87*795d594fSAndroid Build Coastguard Worker self.zipalign = functools.partial(self.run, args.zipalign.absolute()) 88*795d594fSAndroid Build Coastguard Worker if args.hiddenapi: 89*795d594fSAndroid Build Coastguard Worker self.hiddenapi = functools.partial(self.run, args.hiddenapi.absolute()) 90*795d594fSAndroid Build Coastguard Worker 91*795d594fSAndroid Build Coastguard Worker # RBE wrapper for some of the tools. 92*795d594fSAndroid Build Coastguard Worker if "RBE_server_address" in os.environ and USE_RBE > (hash(self.test_name) % 100): 93*795d594fSAndroid Build Coastguard Worker self.rbe_exec_root = os.environ.get("RBE_exec_root") 94*795d594fSAndroid Build Coastguard Worker 95*795d594fSAndroid Build Coastguard Worker # TODO(b/307932183) Regression: RBE produces wrong output for D8 in ART 96*795d594fSAndroid Build Coastguard Worker disable_d8 = any((self.test_dir / n).exists() for n in ["classes", "src2", "src-art"]) 97*795d594fSAndroid Build Coastguard Worker 98*795d594fSAndroid Build Coastguard Worker if self.test_name not in RBE_D8_DISABLED_FOR and not disable_d8: 99*795d594fSAndroid Build Coastguard Worker self.d8 = functools.partial(self.rbe_d8, args.d8.absolute()) 100*795d594fSAndroid Build Coastguard Worker self.javac = functools.partial(self.rbe_javac, self.javac_path) 101*795d594fSAndroid Build Coastguard Worker self.smali = functools.partial(self.rbe_smali, args.smali.absolute()) 102*795d594fSAndroid Build Coastguard Worker 103*795d594fSAndroid Build Coastguard Worker # Minimal environment needed for bash commands that we execute. 104*795d594fSAndroid Build Coastguard Worker self.bash_env = { 105*795d594fSAndroid Build Coastguard Worker "ANDROID_BUILD_TOP": self.android_build_top, 106*795d594fSAndroid Build Coastguard Worker "D8": args.d8.absolute(), 107*795d594fSAndroid Build Coastguard Worker "JAVA": self.java_path, 108*795d594fSAndroid Build Coastguard Worker "JAVAC": self.javac_path, 109*795d594fSAndroid Build Coastguard Worker "JAVAC_ARGS": self.javac_args, 110*795d594fSAndroid Build Coastguard Worker "JAVA_HOME": self.java_home, 111*795d594fSAndroid Build Coastguard Worker "PATH": os.environ["PATH"], 112*795d594fSAndroid Build Coastguard Worker "PYTHONDONTWRITEBYTECODE": "1", 113*795d594fSAndroid Build Coastguard Worker "SMALI": args.smali.absolute(), 114*795d594fSAndroid Build Coastguard Worker "SOONG_ZIP": args.soong_zip.absolute(), 115*795d594fSAndroid Build Coastguard Worker "TEST_NAME": self.test_name, 116*795d594fSAndroid Build Coastguard Worker } 117*795d594fSAndroid Build Coastguard Worker 118*795d594fSAndroid Build Coastguard Worker def bash(self, cmd): 119*795d594fSAndroid Build Coastguard Worker return subprocess.run(cmd, 120*795d594fSAndroid Build Coastguard Worker shell=True, 121*795d594fSAndroid Build Coastguard Worker cwd=self.test_dir, 122*795d594fSAndroid Build Coastguard Worker env=self.bash_env, 123*795d594fSAndroid Build Coastguard Worker check=True) 124*795d594fSAndroid Build Coastguard Worker 125*795d594fSAndroid Build Coastguard Worker def run(self, executable: pathlib.Path, args: List[Union[pathlib.Path, str]]): 126*795d594fSAndroid Build Coastguard Worker assert isinstance(executable, pathlib.Path), executable 127*795d594fSAndroid Build Coastguard Worker cmd: List[Union[pathlib.Path, str]] = [] 128*795d594fSAndroid Build Coastguard Worker if REPORT_SLOW_COMMANDS: 129*795d594fSAndroid Build Coastguard Worker cmd += ["/usr/bin/time"] 130*795d594fSAndroid Build Coastguard Worker if executable.suffix == ".sh": 131*795d594fSAndroid Build Coastguard Worker cmd += ["/bin/bash"] 132*795d594fSAndroid Build Coastguard Worker cmd += [executable] 133*795d594fSAndroid Build Coastguard Worker cmd += args 134*795d594fSAndroid Build Coastguard Worker env = self.bash_env 135*795d594fSAndroid Build Coastguard Worker env.update({k: v for k, v in os.environ.items() if k.startswith("RBE_")}) 136*795d594fSAndroid Build Coastguard Worker # Make paths relative as otherwise we could create too long command line. 137*795d594fSAndroid Build Coastguard Worker for i, arg in enumerate(cmd): 138*795d594fSAndroid Build Coastguard Worker if isinstance(arg, pathlib.Path): 139*795d594fSAndroid Build Coastguard Worker assert arg.absolute(), arg 140*795d594fSAndroid Build Coastguard Worker cmd[i] = relpath(arg, self.test_dir) 141*795d594fSAndroid Build Coastguard Worker elif isinstance(arg, list): 142*795d594fSAndroid Build Coastguard Worker assert all(p.absolute() for p in arg), arg 143*795d594fSAndroid Build Coastguard Worker cmd[i] = ":".join(relpath(p, self.test_dir) for p in arg) 144*795d594fSAndroid Build Coastguard Worker else: 145*795d594fSAndroid Build Coastguard Worker assert isinstance(arg, str), arg 146*795d594fSAndroid Build Coastguard Worker p = subprocess.run(cmd, 147*795d594fSAndroid Build Coastguard Worker encoding=sys.stdout.encoding, 148*795d594fSAndroid Build Coastguard Worker cwd=self.test_dir, 149*795d594fSAndroid Build Coastguard Worker env=self.bash_env, 150*795d594fSAndroid Build Coastguard Worker stderr=subprocess.STDOUT, 151*795d594fSAndroid Build Coastguard Worker stdout=subprocess.PIPE) 152*795d594fSAndroid Build Coastguard Worker if REPORT_SLOW_COMMANDS: 153*795d594fSAndroid Build Coastguard Worker m = re.search("([0-9\.]+)user", p.stdout) 154*795d594fSAndroid Build Coastguard Worker assert m, p.stdout 155*795d594fSAndroid Build Coastguard Worker t = float(m.group(1)) 156*795d594fSAndroid Build Coastguard Worker if t > 1.0: 157*795d594fSAndroid Build Coastguard Worker cmd_text = " ".join(map(str, cmd[1:]))[:100] 158*795d594fSAndroid Build Coastguard Worker print(f"[{self.test_name}] Command took {t:.2f}s: {cmd_text}") 159*795d594fSAndroid Build Coastguard Worker 160*795d594fSAndroid Build Coastguard Worker if p.returncode != 0: 161*795d594fSAndroid Build Coastguard Worker raise Exception("Command failed with exit code {}\n$ {}\n{}".format( 162*795d594fSAndroid Build Coastguard Worker p.returncode, " ".join(map(str, cmd)), p.stdout)) 163*795d594fSAndroid Build Coastguard Worker return p 164*795d594fSAndroid Build Coastguard Worker 165*795d594fSAndroid Build Coastguard Worker def rbe_wrap(self, args, inputs: Set[pathlib.Path]=None): 166*795d594fSAndroid Build Coastguard Worker with NamedTemporaryFile(mode="w+t") as input_list: 167*795d594fSAndroid Build Coastguard Worker inputs = inputs or set() 168*795d594fSAndroid Build Coastguard Worker for i in inputs: 169*795d594fSAndroid Build Coastguard Worker assert i.exists(), i 170*795d594fSAndroid Build Coastguard Worker for i, arg in enumerate(args): 171*795d594fSAndroid Build Coastguard Worker if isinstance(arg, pathlib.Path): 172*795d594fSAndroid Build Coastguard Worker assert arg.absolute(), arg 173*795d594fSAndroid Build Coastguard Worker inputs.add(arg) 174*795d594fSAndroid Build Coastguard Worker elif isinstance(arg, list): 175*795d594fSAndroid Build Coastguard Worker assert all(p.absolute() for p in arg), arg 176*795d594fSAndroid Build Coastguard Worker inputs.update(arg) 177*795d594fSAndroid Build Coastguard Worker input_list.writelines([relpath(i, self.rbe_exec_root)+"\n" for i in inputs]) 178*795d594fSAndroid Build Coastguard Worker input_list.flush() 179*795d594fSAndroid Build Coastguard Worker dbg_args = ["-compare", "-num_local_reruns=1", "-num_remote_reruns=1"] if RBE_COMPARE else [] 180*795d594fSAndroid Build Coastguard Worker return self.run(self.rbe_rewrapper, [ 181*795d594fSAndroid Build Coastguard Worker "--platform=" + os.environ["RBE_platform"], 182*795d594fSAndroid Build Coastguard Worker "--input_list_paths=" + input_list.name, 183*795d594fSAndroid Build Coastguard Worker ] + dbg_args + args) 184*795d594fSAndroid Build Coastguard Worker 185*795d594fSAndroid Build Coastguard Worker def rbe_javac(self, javac_path:Path, args): 186*795d594fSAndroid Build Coastguard Worker output = relpath(Path(args[args.index("-d") + 1]), self.rbe_exec_root) 187*795d594fSAndroid Build Coastguard Worker return self.rbe_wrap(["--output_directories", output, javac_path] + args) 188*795d594fSAndroid Build Coastguard Worker 189*795d594fSAndroid Build Coastguard Worker def rbe_d8(self, d8_path:Path, args): 190*795d594fSAndroid Build Coastguard Worker inputs = set([d8_path.parent.parent / "framework/d8.jar"]) 191*795d594fSAndroid Build Coastguard Worker output = relpath(Path(args[args.index("--output") + 1]), self.rbe_exec_root) 192*795d594fSAndroid Build Coastguard Worker return self.rbe_wrap([ 193*795d594fSAndroid Build Coastguard Worker "--output_files" if output.endswith(".jar") else "--output_directories", output, 194*795d594fSAndroid Build Coastguard Worker "--toolchain_inputs=prebuilts/jdk/jdk21/linux-x86/bin/java", 195*795d594fSAndroid Build Coastguard Worker d8_path] + args, inputs) 196*795d594fSAndroid Build Coastguard Worker 197*795d594fSAndroid Build Coastguard Worker def rbe_smali(self, smali_path:Path, args): 198*795d594fSAndroid Build Coastguard Worker # The output of smali is non-deterministic, so create wrapper script, 199*795d594fSAndroid Build Coastguard Worker # which runs D8 on the output to normalize it. 200*795d594fSAndroid Build Coastguard Worker api = args[args.index("--api") + 1] 201*795d594fSAndroid Build Coastguard Worker output = Path(args[args.index("--output") + 1]) 202*795d594fSAndroid Build Coastguard Worker wrapper = output.with_suffix(".sh") 203*795d594fSAndroid Build Coastguard Worker wrapper.write_text(''' 204*795d594fSAndroid Build Coastguard Worker set -e 205*795d594fSAndroid Build Coastguard Worker {smali} $@ 206*795d594fSAndroid Build Coastguard Worker mkdir dex_normalize 207*795d594fSAndroid Build Coastguard Worker {d8} --min-api {api} --output dex_normalize {output} 208*795d594fSAndroid Build Coastguard Worker cp dex_normalize/classes.dex {output} 209*795d594fSAndroid Build Coastguard Worker rm -rf dex_normalize 210*795d594fSAndroid Build Coastguard Worker '''.strip().format( 211*795d594fSAndroid Build Coastguard Worker smali=relpath(self.smali_path, self.test_dir), 212*795d594fSAndroid Build Coastguard Worker d8=relpath(self.d8_path, self.test_dir), 213*795d594fSAndroid Build Coastguard Worker api=api, 214*795d594fSAndroid Build Coastguard Worker output=relpath(output, self.test_dir), 215*795d594fSAndroid Build Coastguard Worker )) 216*795d594fSAndroid Build Coastguard Worker 217*795d594fSAndroid Build Coastguard Worker inputs = set([ 218*795d594fSAndroid Build Coastguard Worker wrapper, 219*795d594fSAndroid Build Coastguard Worker self.smali_path, 220*795d594fSAndroid Build Coastguard Worker self.smali_path.parent.parent / "framework/android-smali.jar", 221*795d594fSAndroid Build Coastguard Worker self.d8_path, 222*795d594fSAndroid Build Coastguard Worker self.d8_path.parent.parent / "framework/d8.jar", 223*795d594fSAndroid Build Coastguard Worker ]) 224*795d594fSAndroid Build Coastguard Worker res = self.rbe_wrap([ 225*795d594fSAndroid Build Coastguard Worker "--output_files", relpath(output, self.rbe_exec_root), 226*795d594fSAndroid Build Coastguard Worker "--toolchain_inputs=prebuilts/jdk/jdk21/linux-x86/bin/java", 227*795d594fSAndroid Build Coastguard Worker "/bin/bash", wrapper] + args, inputs) 228*795d594fSAndroid Build Coastguard Worker wrapper.unlink() 229*795d594fSAndroid Build Coastguard Worker return res 230*795d594fSAndroid Build Coastguard Worker 231*795d594fSAndroid Build Coastguard Worker def build(self) -> None: 232*795d594fSAndroid Build Coastguard Worker script = self.test_dir / "build.py" 233*795d594fSAndroid Build Coastguard Worker if script.exists(): 234*795d594fSAndroid Build Coastguard Worker module = SourceFileLoader("build_" + self.test_name, 235*795d594fSAndroid Build Coastguard Worker str(script)).load_module() 236*795d594fSAndroid Build Coastguard Worker module.build(self) 237*795d594fSAndroid Build Coastguard Worker else: 238*795d594fSAndroid Build Coastguard Worker self.default_build() 239*795d594fSAndroid Build Coastguard Worker 240*795d594fSAndroid Build Coastguard Worker def default_build( 241*795d594fSAndroid Build Coastguard Worker self, 242*795d594fSAndroid Build Coastguard Worker use_desugar=True, 243*795d594fSAndroid Build Coastguard Worker use_hiddenapi=True, 244*795d594fSAndroid Build Coastguard Worker need_dex=None, 245*795d594fSAndroid Build Coastguard Worker zip_compression_method="deflate", 246*795d594fSAndroid Build Coastguard Worker zip_align_bytes=None, 247*795d594fSAndroid Build Coastguard Worker api_level:Union[int, str]=26, # Can also be named alias (string). 248*795d594fSAndroid Build Coastguard Worker javac_args=[], 249*795d594fSAndroid Build Coastguard Worker javac_classpath: List[Path]=[], 250*795d594fSAndroid Build Coastguard Worker d8_flags=[], 251*795d594fSAndroid Build Coastguard Worker d8_dex_container=True, 252*795d594fSAndroid Build Coastguard Worker smali_args=[], 253*795d594fSAndroid Build Coastguard Worker use_smali=True, 254*795d594fSAndroid Build Coastguard Worker use_jasmin=True, 255*795d594fSAndroid Build Coastguard Worker javac_source_arg="1.8", 256*795d594fSAndroid Build Coastguard Worker javac_target_arg="1.8" 257*795d594fSAndroid Build Coastguard Worker ): 258*795d594fSAndroid Build Coastguard Worker javac_classpath = javac_classpath.copy() # Do not modify default value. 259*795d594fSAndroid Build Coastguard Worker 260*795d594fSAndroid Build Coastguard Worker # Wrap "pathlib.Path" with our own version that ensures all paths are absolute. 261*795d594fSAndroid Build Coastguard Worker # Plain filenames are assumed to be relative to self.test_dir and made absolute. 262*795d594fSAndroid Build Coastguard Worker class Path(pathlib.Path): 263*795d594fSAndroid Build Coastguard Worker def __new__(cls, filename: str): 264*795d594fSAndroid Build Coastguard Worker path = pathlib.Path(filename) 265*795d594fSAndroid Build Coastguard Worker return path if path.is_absolute() else (self.test_dir / path) 266*795d594fSAndroid Build Coastguard Worker 267*795d594fSAndroid Build Coastguard Worker need_dex = (self.host or self.target) if need_dex is None else need_dex 268*795d594fSAndroid Build Coastguard Worker 269*795d594fSAndroid Build Coastguard Worker if self.jvm: 270*795d594fSAndroid Build Coastguard Worker # No desugaring on jvm because it supports the latest functionality. 271*795d594fSAndroid Build Coastguard Worker use_desugar = False 272*795d594fSAndroid Build Coastguard Worker 273*795d594fSAndroid Build Coastguard Worker # Set API level for smali and d8. 274*795d594fSAndroid Build Coastguard Worker if isinstance(api_level, str): 275*795d594fSAndroid Build Coastguard Worker API_LEVEL = { 276*795d594fSAndroid Build Coastguard Worker "default-methods": 24, 277*795d594fSAndroid Build Coastguard Worker "parameter-annotations": 25, 278*795d594fSAndroid Build Coastguard Worker "agents": 26, 279*795d594fSAndroid Build Coastguard Worker "method-handles": 26, 280*795d594fSAndroid Build Coastguard Worker "var-handles": 28, 281*795d594fSAndroid Build Coastguard Worker "const-method-type": 28, 282*795d594fSAndroid Build Coastguard Worker } 283*795d594fSAndroid Build Coastguard Worker api_level = API_LEVEL[api_level] 284*795d594fSAndroid Build Coastguard Worker assert isinstance(api_level, int), api_level 285*795d594fSAndroid Build Coastguard Worker 286*795d594fSAndroid Build Coastguard Worker def zip(zip_target: Path, *files: Path): 287*795d594fSAndroid Build Coastguard Worker zip_args = ["-o", zip_target, "-C", zip_target.parent] 288*795d594fSAndroid Build Coastguard Worker if zip_compression_method == "store": 289*795d594fSAndroid Build Coastguard Worker zip_args.extend(["-L", "0"]) 290*795d594fSAndroid Build Coastguard Worker for f in files: 291*795d594fSAndroid Build Coastguard Worker zip_args.extend(["-f", f]) 292*795d594fSAndroid Build Coastguard Worker self.soong_zip(zip_args) 293*795d594fSAndroid Build Coastguard Worker 294*795d594fSAndroid Build Coastguard Worker if zip_align_bytes: 295*795d594fSAndroid Build Coastguard Worker # zipalign does not operate in-place, so write results to a temp file. 296*795d594fSAndroid Build Coastguard Worker with TemporaryDirectory() as tmp_dir: 297*795d594fSAndroid Build Coastguard Worker tmp_file = Path(tmp_dir) / "aligned.zip" 298*795d594fSAndroid Build Coastguard Worker self.zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file]) 299*795d594fSAndroid Build Coastguard Worker # replace original zip target with our temp file. 300*795d594fSAndroid Build Coastguard Worker tmp_file.rename(zip_target) 301*795d594fSAndroid Build Coastguard Worker 302*795d594fSAndroid Build Coastguard Worker 303*795d594fSAndroid Build Coastguard Worker def make_jasmin(dst_dir: Path, src_dir: Path) -> Optional[Path]: 304*795d594fSAndroid Build Coastguard Worker if not use_jasmin or not src_dir.exists(): 305*795d594fSAndroid Build Coastguard Worker return None # No sources to compile. 306*795d594fSAndroid Build Coastguard Worker dst_dir.mkdir() 307*795d594fSAndroid Build Coastguard Worker self.jasmin(["-d", dst_dir] + sorted(src_dir.glob("**/*.j"))) 308*795d594fSAndroid Build Coastguard Worker return dst_dir 309*795d594fSAndroid Build Coastguard Worker 310*795d594fSAndroid Build Coastguard Worker def make_smali(dst_dex: Path, src_dir: Path) -> Optional[Path]: 311*795d594fSAndroid Build Coastguard Worker if not use_smali or not src_dir.exists(): 312*795d594fSAndroid Build Coastguard Worker return None # No sources to compile. 313*795d594fSAndroid Build Coastguard Worker p = self.smali(["-JXmx512m", "assemble"] + smali_args + ["--api", str(api_level)] + 314*795d594fSAndroid Build Coastguard Worker ["--output", dst_dex] + sorted(src_dir.glob("**/*.smali"))) 315*795d594fSAndroid Build Coastguard Worker assert dst_dex.exists(), p.stdout # NB: smali returns 0 exit code even on failure. 316*795d594fSAndroid Build Coastguard Worker return dst_dex 317*795d594fSAndroid Build Coastguard Worker 318*795d594fSAndroid Build Coastguard Worker def make_java(dst_dir: Path, *src_dirs: Path) -> Optional[Path]: 319*795d594fSAndroid Build Coastguard Worker if not any(src_dir.exists() for src_dir in src_dirs): 320*795d594fSAndroid Build Coastguard Worker return None # No sources to compile. 321*795d594fSAndroid Build Coastguard Worker dst_dir.mkdir(exist_ok=True) 322*795d594fSAndroid Build Coastguard Worker args = self.javac_args.split(" ") + javac_args 323*795d594fSAndroid Build Coastguard Worker args += ["-implicit:none", "-encoding", "utf8", "-d", dst_dir] 324*795d594fSAndroid Build Coastguard Worker args += ["-source", javac_source_arg, "-target", javac_target_arg] 325*795d594fSAndroid Build Coastguard Worker if not self.jvm and float(javac_target_arg) < 17.0: 326*795d594fSAndroid Build Coastguard Worker args += ["-bootclasspath", self.bootclasspath] 327*795d594fSAndroid Build Coastguard Worker if javac_classpath: 328*795d594fSAndroid Build Coastguard Worker args += ["-classpath", javac_classpath] 329*795d594fSAndroid Build Coastguard Worker for src_dir in src_dirs: 330*795d594fSAndroid Build Coastguard Worker args += sorted(src_dir.glob("**/*.java")) 331*795d594fSAndroid Build Coastguard Worker self.javac(args) 332*795d594fSAndroid Build Coastguard Worker javac_post = Path("javac_post.sh") 333*795d594fSAndroid Build Coastguard Worker if javac_post.exists(): 334*795d594fSAndroid Build Coastguard Worker self.run(javac_post, [dst_dir]) 335*795d594fSAndroid Build Coastguard Worker return dst_dir 336*795d594fSAndroid Build Coastguard Worker 337*795d594fSAndroid Build Coastguard Worker 338*795d594fSAndroid Build Coastguard Worker # Make a "dex" file given a directory of classes. This will be 339*795d594fSAndroid Build Coastguard Worker # packaged in a jar file. 340*795d594fSAndroid Build Coastguard Worker def make_dex(src_dir: Path): 341*795d594fSAndroid Build Coastguard Worker dst_jar = Path(src_dir.name + ".jar") 342*795d594fSAndroid Build Coastguard Worker args = [] 343*795d594fSAndroid Build Coastguard Worker if d8_dex_container: 344*795d594fSAndroid Build Coastguard Worker args += ["-JDcom.android.tools.r8.dexContainerExperiment"] 345*795d594fSAndroid Build Coastguard Worker args += d8_flags + ["--min-api", str(api_level), "--output", dst_jar] 346*795d594fSAndroid Build Coastguard Worker args += ["--lib", self.bootclasspath] if use_desugar else ["--no-desugaring"] 347*795d594fSAndroid Build Coastguard Worker args += sorted(src_dir.glob("**/*.class")) 348*795d594fSAndroid Build Coastguard Worker self.d8(args) 349*795d594fSAndroid Build Coastguard Worker 350*795d594fSAndroid Build Coastguard Worker # D8 outputs to JAR files today rather than DEX files as DX used 351*795d594fSAndroid Build Coastguard Worker # to. To compensate, we extract the DEX from d8's output to meet the 352*795d594fSAndroid Build Coastguard Worker # expectations of make_dex callers. 353*795d594fSAndroid Build Coastguard Worker dst_dex = Path(src_dir.name + ".dex") 354*795d594fSAndroid Build Coastguard Worker with TemporaryDirectory() as tmp_dir: 355*795d594fSAndroid Build Coastguard Worker zipfile.ZipFile(dst_jar, "r").extractall(tmp_dir) 356*795d594fSAndroid Build Coastguard Worker (Path(tmp_dir) / "classes.dex").rename(dst_dex) 357*795d594fSAndroid Build Coastguard Worker 358*795d594fSAndroid Build Coastguard Worker # Merge all the dex files. 359*795d594fSAndroid Build Coastguard Worker # Skip non-existing files, but at least 1 file must exist. 360*795d594fSAndroid Build Coastguard Worker def make_dexmerge(dst_dex: Path, *src_dexs: Path): 361*795d594fSAndroid Build Coastguard Worker # Include destination. Skip any non-existing files. 362*795d594fSAndroid Build Coastguard Worker srcs = [f for f in [dst_dex] + list(src_dexs) if f.exists()] 363*795d594fSAndroid Build Coastguard Worker 364*795d594fSAndroid Build Coastguard Worker # NB: We merge even if there is just single input. 365*795d594fSAndroid Build Coastguard Worker # It is useful to normalize non-deterministic smali output. 366*795d594fSAndroid Build Coastguard Worker tmp_dir = self.test_dir / "dexmerge" 367*795d594fSAndroid Build Coastguard Worker tmp_dir.mkdir() 368*795d594fSAndroid Build Coastguard Worker flags = [] 369*795d594fSAndroid Build Coastguard Worker if d8_dex_container: 370*795d594fSAndroid Build Coastguard Worker flags += ["-JDcom.android.tools.r8.dexContainerExperiment"] 371*795d594fSAndroid Build Coastguard Worker flags += ["--min-api", str(api_level), "--output", tmp_dir] 372*795d594fSAndroid Build Coastguard Worker self.d8(flags + srcs) 373*795d594fSAndroid Build Coastguard Worker assert not (tmp_dir / "classes2.dex").exists() 374*795d594fSAndroid Build Coastguard Worker for src_file in srcs: 375*795d594fSAndroid Build Coastguard Worker src_file.unlink() 376*795d594fSAndroid Build Coastguard Worker (tmp_dir / "classes.dex").rename(dst_dex) 377*795d594fSAndroid Build Coastguard Worker tmp_dir.rmdir() 378*795d594fSAndroid Build Coastguard Worker 379*795d594fSAndroid Build Coastguard Worker 380*795d594fSAndroid Build Coastguard Worker def make_hiddenapi(*dex_files: Path): 381*795d594fSAndroid Build Coastguard Worker if not use_hiddenapi or not Path("hiddenapi-flags.csv").exists(): 382*795d594fSAndroid Build Coastguard Worker return # Nothing to do. 383*795d594fSAndroid Build Coastguard Worker args: List[Union[str, Path]] = ["encode"] 384*795d594fSAndroid Build Coastguard Worker for dex_file in dex_files: 385*795d594fSAndroid Build Coastguard Worker args.extend(["--input-dex=" + str(dex_file), "--output-dex=" + str(dex_file)]) 386*795d594fSAndroid Build Coastguard Worker args.append("--api-flags=hiddenapi-flags.csv") 387*795d594fSAndroid Build Coastguard Worker args.append("--no-force-assign-all") 388*795d594fSAndroid Build Coastguard Worker self.hiddenapi(args) 389*795d594fSAndroid Build Coastguard Worker 390*795d594fSAndroid Build Coastguard Worker 391*795d594fSAndroid Build Coastguard Worker if Path("classes.dex").exists(): 392*795d594fSAndroid Build Coastguard Worker zip(Path(self.test_name + ".jar"), Path("classes.dex")) 393*795d594fSAndroid Build Coastguard Worker return 394*795d594fSAndroid Build Coastguard Worker 395*795d594fSAndroid Build Coastguard Worker if Path("classes.dm").exists(): 396*795d594fSAndroid Build Coastguard Worker zip(Path(self.test_name + ".jar"), Path("classes.dm")) 397*795d594fSAndroid Build Coastguard Worker return 398*795d594fSAndroid Build Coastguard Worker 399*795d594fSAndroid Build Coastguard Worker if make_jasmin(Path("jasmin_classes"), Path("jasmin")): 400*795d594fSAndroid Build Coastguard Worker javac_classpath.append(Path("jasmin_classes")) 401*795d594fSAndroid Build Coastguard Worker 402*795d594fSAndroid Build Coastguard Worker if make_jasmin(Path("jasmin_classes2"), Path("jasmin-multidex")): 403*795d594fSAndroid Build Coastguard Worker javac_classpath.append(Path("jasmin_classes2")) 404*795d594fSAndroid Build Coastguard Worker 405*795d594fSAndroid Build Coastguard Worker # To allow circular references, compile src/, src-multidex/, src-aotex/, 406*795d594fSAndroid Build Coastguard Worker # src-bcpex/, src-ex/ together and pass the output as class path argument. 407*795d594fSAndroid Build Coastguard Worker # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols 408*795d594fSAndroid Build Coastguard Worker # used by the other src-* sources we compile here but everything needed to 409*795d594fSAndroid Build Coastguard Worker # compile the other src-* sources should be present in src/ (and jasmin*/). 410*795d594fSAndroid Build Coastguard Worker extra_srcs = ["src-multidex", "src-aotex", "src-bcpex", "src-ex"] 411*795d594fSAndroid Build Coastguard Worker replacement_srcs = ["src2", "src-ex2"] + ([] if self.jvm else ["src-art"]) 412*795d594fSAndroid Build Coastguard Worker if (Path("src").exists() and 413*795d594fSAndroid Build Coastguard Worker any(Path(p).exists() for p in extra_srcs + replacement_srcs)): 414*795d594fSAndroid Build Coastguard Worker make_java(Path("classes-tmp-all"), Path("src"), *map(Path, extra_srcs)) 415*795d594fSAndroid Build Coastguard Worker javac_classpath.append(Path("classes-tmp-all")) 416*795d594fSAndroid Build Coastguard Worker 417*795d594fSAndroid Build Coastguard Worker if make_java(Path("classes-aotex"), Path("src-aotex")) and need_dex: 418*795d594fSAndroid Build Coastguard Worker make_dex(Path("classes-aotex")) 419*795d594fSAndroid Build Coastguard Worker # rename it so it shows up as "classes.dex" in the zip file. 420*795d594fSAndroid Build Coastguard Worker Path("classes-aotex.dex").rename(Path("classes.dex")) 421*795d594fSAndroid Build Coastguard Worker zip(Path(self.test_name + "-aotex.jar"), Path("classes.dex")) 422*795d594fSAndroid Build Coastguard Worker 423*795d594fSAndroid Build Coastguard Worker if make_java(Path("classes-bcpex"), Path("src-bcpex")) and need_dex: 424*795d594fSAndroid Build Coastguard Worker make_dex(Path("classes-bcpex")) 425*795d594fSAndroid Build Coastguard Worker # rename it so it shows up as "classes.dex" in the zip file. 426*795d594fSAndroid Build Coastguard Worker Path("classes-bcpex.dex").rename(Path("classes.dex")) 427*795d594fSAndroid Build Coastguard Worker zip(Path(self.test_name + "-bcpex.jar"), Path("classes.dex")) 428*795d594fSAndroid Build Coastguard Worker 429*795d594fSAndroid Build Coastguard Worker make_java(Path("classes"), Path("src")) 430*795d594fSAndroid Build Coastguard Worker 431*795d594fSAndroid Build Coastguard Worker if not self.jvm: 432*795d594fSAndroid Build Coastguard Worker # Do not attempt to build src-art directories on jvm, 433*795d594fSAndroid Build Coastguard Worker # since it would fail without libcore. 434*795d594fSAndroid Build Coastguard Worker make_java(Path("classes"), Path("src-art")) 435*795d594fSAndroid Build Coastguard Worker 436*795d594fSAndroid Build Coastguard Worker if make_java(Path("classes2"), Path("src-multidex")) and need_dex: 437*795d594fSAndroid Build Coastguard Worker make_dex(Path("classes2")) 438*795d594fSAndroid Build Coastguard Worker 439*795d594fSAndroid Build Coastguard Worker make_java(Path("classes"), Path("src2")) 440*795d594fSAndroid Build Coastguard Worker 441*795d594fSAndroid Build Coastguard Worker # If the classes directory is not-empty, package classes in a DEX file. 442*795d594fSAndroid Build Coastguard Worker # NB: some tests provide classes rather than java files. 443*795d594fSAndroid Build Coastguard Worker if any(Path("classes").glob("*")) and need_dex: 444*795d594fSAndroid Build Coastguard Worker make_dex(Path("classes")) 445*795d594fSAndroid Build Coastguard Worker 446*795d594fSAndroid Build Coastguard Worker if Path("jasmin_classes").exists(): 447*795d594fSAndroid Build Coastguard Worker # Compile Jasmin classes as if they were part of the classes.dex file. 448*795d594fSAndroid Build Coastguard Worker if need_dex: 449*795d594fSAndroid Build Coastguard Worker make_dex(Path("jasmin_classes")) 450*795d594fSAndroid Build Coastguard Worker make_dexmerge(Path("classes.dex"), Path("jasmin_classes.dex")) 451*795d594fSAndroid Build Coastguard Worker else: 452*795d594fSAndroid Build Coastguard Worker # Move jasmin classes into classes directory so that they are picked up 453*795d594fSAndroid Build Coastguard Worker # with -cp classes. 454*795d594fSAndroid Build Coastguard Worker Path("classes").mkdir(exist_ok=True) 455*795d594fSAndroid Build Coastguard Worker copytree(Path("jasmin_classes"), Path("classes"), dirs_exist_ok=True) 456*795d594fSAndroid Build Coastguard Worker 457*795d594fSAndroid Build Coastguard Worker if need_dex and make_smali(Path("smali_classes.dex"), Path("smali")): 458*795d594fSAndroid Build Coastguard Worker # Merge smali files into classes.dex, 459*795d594fSAndroid Build Coastguard Worker # this takes priority over any jasmin files. 460*795d594fSAndroid Build Coastguard Worker make_dexmerge(Path("classes.dex"), Path("smali_classes.dex")) 461*795d594fSAndroid Build Coastguard Worker 462*795d594fSAndroid Build Coastguard Worker # Compile Jasmin classes in jasmin-multidex as if they were part of 463*795d594fSAndroid Build Coastguard Worker # the classes2.jar 464*795d594fSAndroid Build Coastguard Worker if Path("jasmin-multidex").exists(): 465*795d594fSAndroid Build Coastguard Worker if need_dex: 466*795d594fSAndroid Build Coastguard Worker make_dex(Path("jasmin_classes2")) 467*795d594fSAndroid Build Coastguard Worker make_dexmerge(Path("classes2.dex"), Path("jasmin_classes2.dex")) 468*795d594fSAndroid Build Coastguard Worker else: 469*795d594fSAndroid Build Coastguard Worker # Move jasmin classes into classes2 directory so that 470*795d594fSAndroid Build Coastguard Worker # they are picked up with -cp classes2. 471*795d594fSAndroid Build Coastguard Worker Path("classes2").mkdir() 472*795d594fSAndroid Build Coastguard Worker copytree(Path("jasmin_classes2"), Path("classes2"), dirs_exist_ok=True) 473*795d594fSAndroid Build Coastguard Worker rmtree(Path("jasmin_classes2")) 474*795d594fSAndroid Build Coastguard Worker 475*795d594fSAndroid Build Coastguard Worker if need_dex and make_smali(Path("smali_classes2.dex"), Path("smali-multidex")): 476*795d594fSAndroid Build Coastguard Worker # Merge smali_classes2.dex into classes2.dex 477*795d594fSAndroid Build Coastguard Worker make_dexmerge(Path("classes2.dex"), Path("smali_classes2.dex")) 478*795d594fSAndroid Build Coastguard Worker 479*795d594fSAndroid Build Coastguard Worker make_java(Path("classes-ex"), Path("src-ex")) 480*795d594fSAndroid Build Coastguard Worker 481*795d594fSAndroid Build Coastguard Worker make_java(Path("classes-ex"), Path("src-ex2")) 482*795d594fSAndroid Build Coastguard Worker 483*795d594fSAndroid Build Coastguard Worker if Path("classes-ex").exists() and need_dex: 484*795d594fSAndroid Build Coastguard Worker make_dex(Path("classes-ex")) 485*795d594fSAndroid Build Coastguard Worker 486*795d594fSAndroid Build Coastguard Worker if need_dex and make_smali(Path("smali_classes-ex.dex"), Path("smali-ex")): 487*795d594fSAndroid Build Coastguard Worker # Merge smali files into classes-ex.dex. 488*795d594fSAndroid Build Coastguard Worker make_dexmerge(Path("classes-ex.dex"), Path("smali_classes-ex.dex")) 489*795d594fSAndroid Build Coastguard Worker 490*795d594fSAndroid Build Coastguard Worker if Path("classes-ex.dex").exists(): 491*795d594fSAndroid Build Coastguard Worker # Apply hiddenapi on the dex files if the test has API list file(s). 492*795d594fSAndroid Build Coastguard Worker make_hiddenapi(Path("classes-ex.dex")) 493*795d594fSAndroid Build Coastguard Worker 494*795d594fSAndroid Build Coastguard Worker # quick shuffle so that the stored name is "classes.dex" 495*795d594fSAndroid Build Coastguard Worker Path("classes.dex").rename(Path("classes-1.dex")) 496*795d594fSAndroid Build Coastguard Worker Path("classes-ex.dex").rename(Path("classes.dex")) 497*795d594fSAndroid Build Coastguard Worker zip(Path(self.test_name + "-ex.jar"), Path("classes.dex")) 498*795d594fSAndroid Build Coastguard Worker Path("classes.dex").rename(Path("classes-ex.dex")) 499*795d594fSAndroid Build Coastguard Worker Path("classes-1.dex").rename(Path("classes.dex")) 500*795d594fSAndroid Build Coastguard Worker 501*795d594fSAndroid Build Coastguard Worker # Apply hiddenapi on the dex files if the test has API list file(s). 502*795d594fSAndroid Build Coastguard Worker if need_dex: 503*795d594fSAndroid Build Coastguard Worker if any(Path(".").glob("*-multidex")): 504*795d594fSAndroid Build Coastguard Worker make_hiddenapi(Path("classes.dex"), Path("classes2.dex")) 505*795d594fSAndroid Build Coastguard Worker else: 506*795d594fSAndroid Build Coastguard Worker make_hiddenapi(Path("classes.dex")) 507*795d594fSAndroid Build Coastguard Worker 508*795d594fSAndroid Build Coastguard Worker # Create a single dex jar with two dex files for multidex. 509*795d594fSAndroid Build Coastguard Worker if need_dex: 510*795d594fSAndroid Build Coastguard Worker if Path("classes2.dex").exists(): 511*795d594fSAndroid Build Coastguard Worker zip(Path(self.test_name + ".jar"), Path("classes.dex"), Path("classes2.dex")) 512*795d594fSAndroid Build Coastguard Worker else: 513*795d594fSAndroid Build Coastguard Worker zip(Path(self.test_name + ".jar"), Path("classes.dex")) 514*795d594fSAndroid Build Coastguard Worker 515*795d594fSAndroid Build Coastguard Worker# Create bash script that compiles the boot image on device. 516*795d594fSAndroid Build Coastguard Worker# This is currently only used for eng-prod testing (which is different 517*795d594fSAndroid Build Coastguard Worker# to the local and LUCI code paths that use buildbot-sync.sh script). 518*795d594fSAndroid Build Coastguard Workerdef create_setup_script(is64: bool): 519*795d594fSAndroid Build Coastguard Worker out = "/data/local/tmp/art/apex/art_boot_images" 520*795d594fSAndroid Build Coastguard Worker isa = 'arm64' if is64 else 'arm' 521*795d594fSAndroid Build Coastguard Worker jar = BOOTCLASSPATH 522*795d594fSAndroid Build Coastguard Worker cmd = [ 523*795d594fSAndroid Build Coastguard Worker f"/apex/com.android.art/bin/{'dex2oat64' if is64 else 'dex2oat32'}", 524*795d594fSAndroid Build Coastguard Worker "--runtime-arg", f"-Xbootclasspath:{':'.join(jar)}", 525*795d594fSAndroid Build Coastguard Worker "--runtime-arg", f"-Xbootclasspath-locations:{':'.join(jar)}", 526*795d594fSAndroid Build Coastguard Worker ] + [f"--dex-file={j}" for j in jar] + [f"--dex-location={j}" for j in jar] + [ 527*795d594fSAndroid Build Coastguard Worker f"--instruction-set={isa}", 528*795d594fSAndroid Build Coastguard Worker "--base=0x70000000", 529*795d594fSAndroid Build Coastguard Worker "--compiler-filter=speed-profile", 530*795d594fSAndroid Build Coastguard Worker "--profile-file=/apex/com.android.art/etc/boot-image.prof", 531*795d594fSAndroid Build Coastguard Worker "--avoid-storing-invocation", 532*795d594fSAndroid Build Coastguard Worker "--generate-debug-info", 533*795d594fSAndroid Build Coastguard Worker "--generate-build-id", 534*795d594fSAndroid Build Coastguard Worker "--image-format=lz4hc", 535*795d594fSAndroid Build Coastguard Worker "--strip", 536*795d594fSAndroid Build Coastguard Worker "--android-root=out/empty", 537*795d594fSAndroid Build Coastguard Worker f"--image={out}/{isa}/boot.art", 538*795d594fSAndroid Build Coastguard Worker f"--oat-file={out}/{isa}/boot.oat", 539*795d594fSAndroid Build Coastguard Worker ] 540*795d594fSAndroid Build Coastguard Worker return [ 541*795d594fSAndroid Build Coastguard Worker f"rm -rf {out}/{isa}", 542*795d594fSAndroid Build Coastguard Worker f"mkdir -p {out}/{isa}", 543*795d594fSAndroid Build Coastguard Worker " ".join(cmd), 544*795d594fSAndroid Build Coastguard Worker ] 545*795d594fSAndroid Build Coastguard Worker 546*795d594fSAndroid Build Coastguard Worker# Create bash scripts that can fully execute the run tests. 547*795d594fSAndroid Build Coastguard Worker# This can be used in CI to execute the tests without running `testrunner.py`. 548*795d594fSAndroid Build Coastguard Worker# This takes into account any custom behaviour defined in per-test `run.py`. 549*795d594fSAndroid Build Coastguard Worker# We generate distinct scripts for all of the pre-defined variants. 550*795d594fSAndroid Build Coastguard Workerdef create_ci_runner_scripts(out, mode, test_names): 551*795d594fSAndroid Build Coastguard Worker out.mkdir(parents=True) 552*795d594fSAndroid Build Coastguard Worker setup = out / "setup.sh" 553*795d594fSAndroid Build Coastguard Worker setup_script = create_setup_script(False) + create_setup_script(True) 554*795d594fSAndroid Build Coastguard Worker setup.write_text("\n".join(setup_script)) 555*795d594fSAndroid Build Coastguard Worker 556*795d594fSAndroid Build Coastguard Worker python = sys.executable 557*795d594fSAndroid Build Coastguard Worker script = 'art/test/testrunner/testrunner.py' 558*795d594fSAndroid Build Coastguard Worker envs = { 559*795d594fSAndroid Build Coastguard Worker "ANDROID_BUILD_TOP": str(Path(getcwd()).absolute()), 560*795d594fSAndroid Build Coastguard Worker "ART_TEST_RUN_FROM_SOONG": "true", 561*795d594fSAndroid Build Coastguard Worker # TODO: Make the runner scripts target agnostic. 562*795d594fSAndroid Build Coastguard Worker # The only dependency is setting of "-Djava.library.path". 563*795d594fSAndroid Build Coastguard Worker "TARGET_ARCH": "arm64", 564*795d594fSAndroid Build Coastguard Worker "TARGET_2ND_ARCH": "arm", 565*795d594fSAndroid Build Coastguard Worker "TMPDIR": Path(getcwd()) / "tmp", 566*795d594fSAndroid Build Coastguard Worker } 567*795d594fSAndroid Build Coastguard Worker args = [ 568*795d594fSAndroid Build Coastguard Worker f"--run-test-option=--create-runner={out}", 569*795d594fSAndroid Build Coastguard Worker f"-j={cpu_count()}", 570*795d594fSAndroid Build Coastguard Worker f"--{mode}", 571*795d594fSAndroid Build Coastguard Worker ] 572*795d594fSAndroid Build Coastguard Worker run([python, script] + args + test_names, env=envs, check=True) 573*795d594fSAndroid Build Coastguard Worker tests = { 574*795d594fSAndroid Build Coastguard Worker "setup": { 575*795d594fSAndroid Build Coastguard Worker "adb push": [[str(setup.relative_to(out)), "/data/local/tmp/art/setup.sh"]], 576*795d594fSAndroid Build Coastguard Worker "adb shell": [["sh", "/data/local/tmp/art/setup.sh"]], 577*795d594fSAndroid Build Coastguard Worker }, 578*795d594fSAndroid Build Coastguard Worker } 579*795d594fSAndroid Build Coastguard Worker for runner in Path(out).glob("*/*.sh"): 580*795d594fSAndroid Build Coastguard Worker test_name = runner.parent.name 581*795d594fSAndroid Build Coastguard Worker test_hash = runner.stem 582*795d594fSAndroid Build Coastguard Worker target_dir = f"/data/local/tmp/art/test/{test_hash}" 583*795d594fSAndroid Build Coastguard Worker tests[f"{test_name}-{test_hash}"] = { 584*795d594fSAndroid Build Coastguard Worker "dependencies": ["setup"], 585*795d594fSAndroid Build Coastguard Worker "adb push": [ 586*795d594fSAndroid Build Coastguard Worker [f"../{mode}/{test_name}/", f"{target_dir}/"], 587*795d594fSAndroid Build Coastguard Worker [str(runner.relative_to(out)), f"{target_dir}/run.sh"] 588*795d594fSAndroid Build Coastguard Worker ], 589*795d594fSAndroid Build Coastguard Worker "adb shell": [["sh", f"{target_dir}/run.sh"]], 590*795d594fSAndroid Build Coastguard Worker } 591*795d594fSAndroid Build Coastguard Worker return tests 592*795d594fSAndroid Build Coastguard Worker 593*795d594fSAndroid Build Coastguard Worker# If we build just individual shard, we want to split the work among all the cores, 594*795d594fSAndroid Build Coastguard Worker# but if the build system builds all shards, we don't want to overload the machine. 595*795d594fSAndroid Build Coastguard Worker# We don't know which situation we are in, so as simple work-around, we use a lock 596*795d594fSAndroid Build Coastguard Worker# file to allow only one shard to use multiprocessing at the same time. 597*795d594fSAndroid Build Coastguard Workerdef use_multiprocessing(mode: str) -> bool: 598*795d594fSAndroid Build Coastguard Worker if "RBE_server_address" in os.environ: 599*795d594fSAndroid Build Coastguard Worker return True 600*795d594fSAndroid Build Coastguard Worker global lock_file 601*795d594fSAndroid Build Coastguard Worker lock_path = Path(environ["TMPDIR"]) / ("art-test-run-test-build-py-" + mode) 602*795d594fSAndroid Build Coastguard Worker lock_file = open(lock_path, "w") 603*795d594fSAndroid Build Coastguard Worker try: 604*795d594fSAndroid Build Coastguard Worker lockf(lock_file, LOCK_EX | LOCK_NB) 605*795d594fSAndroid Build Coastguard Worker return True # We are the only instance of this script in the build system. 606*795d594fSAndroid Build Coastguard Worker except BlockingIOError: 607*795d594fSAndroid Build Coastguard Worker return False # Some other instance is already running. 608*795d594fSAndroid Build Coastguard Worker 609*795d594fSAndroid Build Coastguard Worker 610*795d594fSAndroid Build Coastguard Workerdef main() -> None: 611*795d594fSAndroid Build Coastguard Worker parser = ArgumentParser(description=__doc__) 612*795d594fSAndroid Build Coastguard Worker parser.add_argument("--out", type=Path, help="Final zip file") 613*795d594fSAndroid Build Coastguard Worker parser.add_argument("--mode", choices=["host", "jvm", "target"]) 614*795d594fSAndroid Build Coastguard Worker parser.add_argument("--bootclasspath", type=Path) 615*795d594fSAndroid Build Coastguard Worker parser.add_argument("--d8", type=Path) 616*795d594fSAndroid Build Coastguard Worker parser.add_argument("--hiddenapi", type=Path) 617*795d594fSAndroid Build Coastguard Worker parser.add_argument("--jasmin", type=Path) 618*795d594fSAndroid Build Coastguard Worker parser.add_argument("--rewrapper", type=Path) 619*795d594fSAndroid Build Coastguard Worker parser.add_argument("--smali", type=Path) 620*795d594fSAndroid Build Coastguard Worker parser.add_argument("--soong_zip", type=Path) 621*795d594fSAndroid Build Coastguard Worker parser.add_argument("--zipalign", type=Path) 622*795d594fSAndroid Build Coastguard Worker parser.add_argument("--test-dir-regex") 623*795d594fSAndroid Build Coastguard Worker parser.add_argument("srcs", nargs="+", type=Path) 624*795d594fSAndroid Build Coastguard Worker args = parser.parse_args() 625*795d594fSAndroid Build Coastguard Worker 626*795d594fSAndroid Build Coastguard Worker android_build_top = Path(getcwd()).absolute() 627*795d594fSAndroid Build Coastguard Worker ziproot = args.out.absolute().parent / "zip" 628*795d594fSAndroid Build Coastguard Worker test_dir_regex = re.compile(args.test_dir_regex) if args.test_dir_regex else re.compile(".*") 629*795d594fSAndroid Build Coastguard Worker srcdirs = set(s.parents[-4].absolute() for s in args.srcs if test_dir_regex.search(str(s))) 630*795d594fSAndroid Build Coastguard Worker 631*795d594fSAndroid Build Coastguard Worker # Special hidden-api shard: If the --hiddenapi flag is provided, build only 632*795d594fSAndroid Build Coastguard Worker # hiddenapi tests. Otherwise exclude all hiddenapi tests from normal shards. 633*795d594fSAndroid Build Coastguard Worker def filter_by_hiddenapi(srcdir: Path) -> bool: 634*795d594fSAndroid Build Coastguard Worker return (args.hiddenapi != None) == ("hiddenapi" in srcdir.name) 635*795d594fSAndroid Build Coastguard Worker 636*795d594fSAndroid Build Coastguard Worker # Initialize the test objects. 637*795d594fSAndroid Build Coastguard Worker # We need to do this before we change the working directory below. 638*795d594fSAndroid Build Coastguard Worker tests: List[BuildTestContext] = [] 639*795d594fSAndroid Build Coastguard Worker for srcdir in filter(filter_by_hiddenapi, srcdirs): 640*795d594fSAndroid Build Coastguard Worker dstdir = ziproot / args.mode / srcdir.name 641*795d594fSAndroid Build Coastguard Worker copytree(srcdir, dstdir) 642*795d594fSAndroid Build Coastguard Worker tests.append(BuildTestContext(args, android_build_top, dstdir)) 643*795d594fSAndroid Build Coastguard Worker 644*795d594fSAndroid Build Coastguard Worker # We can not change the working directory per each thread since they all run in parallel. 645*795d594fSAndroid Build Coastguard Worker # Create invalid read-only directory to catch accidental use of current working directory. 646*795d594fSAndroid Build Coastguard Worker with TemporaryDirectory("-do-not-use-cwd") as invalid_tmpdir: 647*795d594fSAndroid Build Coastguard Worker os.chdir(invalid_tmpdir) 648*795d594fSAndroid Build Coastguard Worker os.chmod(invalid_tmpdir, 0) 649*795d594fSAndroid Build Coastguard Worker with ThreadPoolExecutor(cpu_count() if use_multiprocessing(args.mode) else 1) as pool: 650*795d594fSAndroid Build Coastguard Worker jobs = {ctx.test_name: pool.submit(ctx.build) for ctx in tests} 651*795d594fSAndroid Build Coastguard Worker for test_name, job in jobs.items(): 652*795d594fSAndroid Build Coastguard Worker try: 653*795d594fSAndroid Build Coastguard Worker job.result() 654*795d594fSAndroid Build Coastguard Worker except Exception as e: 655*795d594fSAndroid Build Coastguard Worker raise Exception("Failed to build " + test_name) from e 656*795d594fSAndroid Build Coastguard Worker 657*795d594fSAndroid Build Coastguard Worker if args.mode == "target": 658*795d594fSAndroid Build Coastguard Worker os.chdir(android_build_top) 659*795d594fSAndroid Build Coastguard Worker test_names = [ctx.test_name for ctx in tests] 660*795d594fSAndroid Build Coastguard Worker dst = ziproot / "runner" / args.out.with_suffix(".tests.json").name 661*795d594fSAndroid Build Coastguard Worker tests = create_ci_runner_scripts(dst.parent, args.mode, test_names) 662*795d594fSAndroid Build Coastguard Worker dst.write_text(json.dumps(tests, indent=2, sort_keys=True)) 663*795d594fSAndroid Build Coastguard Worker 664*795d594fSAndroid Build Coastguard Worker # Create the final zip file which contains the content of the temporary directory. 665*795d594fSAndroid Build Coastguard Worker soong_zip = android_build_top / args.soong_zip 666*795d594fSAndroid Build Coastguard Worker zip_file = android_build_top / args.out 667*795d594fSAndroid Build Coastguard Worker run([soong_zip, "-L", "0", "-o", zip_file, "-C", ziproot, "-D", ziproot], check=True) 668*795d594fSAndroid Build Coastguard Worker 669*795d594fSAndroid Build Coastguard Workerif __name__ == "__main__": 670*795d594fSAndroid Build Coastguard Worker main() 671