xref: /aosp_15_r20/art/test/run_test_build.py (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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