blob: 765289d3cef879150f9d0f1e83b750eba7e2b07b [file] [log] [blame]
David Srbecky8106b382022-04-20 13:37:15 +01001#
2# Copyright (C) 2021 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""This is the default build script for run-tests.
17
18It can be overwrite by specific run-tests if needed.
19It is used from soong build and not intended to be called directly.
20"""
21
22import argparse
23import functools
24import glob
25import os
26from os import path
27import shlex
28import shutil
29import subprocess
30import tempfile
31import zipfile
32from shutil import rmtree
33from os import remove
David Srbeckye0c3cd82022-08-24 14:56:24 +010034from re import match
David Srbecky8106b382022-04-20 13:37:15 +010035
David Srbeckye9142f22022-08-03 11:05:19 +010036USE_RBE_FOR_JAVAC = 100 # Percentage of tests that can use RBE (between 0 and 100)
37USE_RBE_FOR_D8 = 100 # Percentage of tests that can use RBE (between 0 and 100)
David Srbecky89010a32022-07-14 16:47:30 +000038
David Srbecky8106b382022-04-20 13:37:15 +010039def rm(*patterns):
40 for pattern in patterns:
41 for path in glob.glob(pattern):
42 if os.path.isdir(path):
43 shutil.rmtree(path)
44 else:
45 os.remove(path)
46
47def build_run_test(
48 use_desugar=True,
49 use_hiddenapi=True,
50 need_dex=None,
51 experimental="no-experiment",
52 zip_compression_method="deflate",
53 zip_align_bytes=None,
54 api_level=None,
55 javac_args=[],
56 d8_flags=[],
57 smali_args=[],
58 has_smali=None,
59 has_jasmin=None,
60 ):
61
62 def parse_bool(text):
63 return {"true": True, "false": False}[text.lower()]
64
David Srbecky89010a32022-07-14 16:47:30 +000065 ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"]
66 SBOX_PATH = os.environ["SBOX_PATH"]
67 CWD = os.getcwd()
David Srbecky8106b382022-04-20 13:37:15 +010068 TEST_NAME = os.environ["TEST_NAME"]
David Srbecky89010a32022-07-14 16:47:30 +000069 ART_TEST_RUN_TEST_BOOTCLASSPATH = path.relpath(os.environ["ART_TEST_RUN_TEST_BOOTCLASSPATH"], CWD)
David Srbecky8106b382022-04-20 13:37:15 +010070 NEED_DEX = parse_bool(os.environ["NEED_DEX"]) if need_dex is None else need_dex
71
David Srbecky89010a32022-07-14 16:47:30 +000072 RBE_exec_root = os.environ.get("RBE_exec_root")
73 RBE_rewrapper = path.join(ANDROID_BUILD_TOP, "prebuilts/remoteexecution-client/live/rewrapper")
74
David Srbecky8106b382022-04-20 13:37:15 +010075 # Set default values for directories.
76 HAS_SMALI = path.exists("smali") if has_smali is None else has_smali
77 HAS_JASMIN = path.exists("jasmin") if has_jasmin is None else has_jasmin
78 HAS_SRC = path.exists("src")
79 HAS_SRC_ART = path.exists("src-art")
80 HAS_SRC2 = path.exists("src2")
81 HAS_SRC_MULTIDEX = path.exists("src-multidex")
82 HAS_SMALI_MULTIDEX = path.exists("smali-multidex")
83 HAS_JASMIN_MULTIDEX = path.exists("jasmin-multidex")
84 HAS_SMALI_EX = path.exists("smali-ex")
85 HAS_SRC_EX = path.exists("src-ex")
86 HAS_SRC_EX2 = path.exists("src-ex2")
87 HAS_SRC_AOTEX = path.exists("src-aotex")
88 HAS_SRC_BCPEX = path.exists("src-bcpex")
89 HAS_HIDDENAPI_SPEC = path.exists("hiddenapi-flags.csv")
90
91 JAVAC_ARGS = shlex.split(os.environ.get("JAVAC_ARGS", "")) + javac_args
92 SMALI_ARGS = shlex.split(os.environ.get("SMALI_ARGS", "")) + smali_args
93 D8_FLAGS = shlex.split(os.environ.get("D8_FLAGS", "")) + d8_flags
94
95 BUILD_MODE = os.environ["BUILD_MODE"]
96
97 # Setup experimental API level mappings in a bash associative array.
98 EXPERIMENTAL_API_LEVEL = {}
99 EXPERIMENTAL_API_LEVEL["no-experiment"] = "26"
100 EXPERIMENTAL_API_LEVEL["default-methods"] = "24"
101 EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25"
102 EXPERIMENTAL_API_LEVEL["agents"] = "26"
103 EXPERIMENTAL_API_LEVEL["method-handles"] = "26"
104 EXPERIMENTAL_API_LEVEL["var-handles"] = "28"
105
106 if BUILD_MODE == "jvm":
107 # No desugaring on jvm because it supports the latest functionality.
108 use_desugar = False
109 # Do not attempt to build src-art directories on jvm,
110 # since it would fail without libcore.
111 HAS_SRC_ART = False
112
113 # Set API level for smali and d8.
114 if not api_level:
115 api_level = EXPERIMENTAL_API_LEVEL[experimental]
116
117 # Add API level arguments to smali and dx
118 SMALI_ARGS.extend(["--api", str(api_level)])
119 D8_FLAGS.extend(["--min-api", str(api_level)])
120
121
122 def run(executable, args):
123 cmd = shlex.split(executable) + args
124 if executable.endswith(".sh"):
125 cmd = ["/bin/bash"] + cmd
126 p = subprocess.run(cmd,
127 encoding=os.sys.stdout.encoding,
128 stderr=subprocess.STDOUT,
129 stdout=subprocess.PIPE)
130 if p.returncode != 0:
131 raise Exception("Command failed with exit code {}\n$ {}\n{}".format(
132 p.returncode, " ".join(cmd), p.stdout))
David Srbeckye0c3cd82022-08-24 14:56:24 +0100133 return p
David Srbecky8106b382022-04-20 13:37:15 +0100134
135
136 # Helper functions to execute tools.
137 soong_zip = functools.partial(run, os.environ["SOONG_ZIP"])
138 zipalign = functools.partial(run, os.environ["ZIPALIGN"])
139 javac = functools.partial(run, os.environ["JAVAC"])
140 jasmin = functools.partial(run, os.environ["JASMIN"])
141 smali = functools.partial(run, os.environ["SMALI"])
142 d8 = functools.partial(run, os.environ["D8"])
143 hiddenapi = functools.partial(run, os.environ["HIDDENAPI"])
144
David Srbecky89010a32022-07-14 16:47:30 +0000145 if "RBE_server_address" in os.environ:
David Srbeckye0c3cd82022-08-24 14:56:24 +0100146 version = match(r"Version: (\d*)\.(\d*)\.(\d*)", run(RBE_rewrapper, ["--version"]).stdout)
147 assert version, "Could not parse RBE version"
148 assert tuple(map(int, version.groups())) >= (0, 76, 0), "Please update " + RBE_rewrapper
149
David Srbecky89010a32022-07-14 16:47:30 +0000150 def rbe_wrap(args, inputs=set()):
151 with tempfile.NamedTemporaryFile(mode="w+t", dir=RBE_exec_root) as input_list:
152 for arg in args:
153 inputs.update(filter(path.exists, arg.split(":")))
154 input_list.writelines([path.relpath(i, RBE_exec_root)+"\n" for i in inputs])
155 input_list.flush()
156 return run(RBE_rewrapper, [
157 "--platform=" + os.environ["RBE_platform"],
158 "--input_list_paths=" + input_list.name,
159 ] + args)
160
161 if USE_RBE_FOR_JAVAC > (hash(TEST_NAME) % 100): # Use for given percentage of tests.
162 def javac(args):
163 output = path.relpath(path.join(CWD, args[args.index("-d") + 1]), RBE_exec_root)
164 return rbe_wrap([
165 "--output_directories", output,
166 os.path.relpath(os.environ["JAVAC"], CWD),
167 ] + args)
168
169 if USE_RBE_FOR_D8 > (hash(TEST_NAME) % 100): # Use for given percentage of tests.
170 def d8(args):
171 inputs = set([path.join(SBOX_PATH, "tools/out/framework/d8.jar")])
172 output = path.relpath(path.join(CWD, args[args.index("--output") + 1]), RBE_exec_root)
173 return rbe_wrap([
174 "--output_files" if output.endswith(".jar") else "--output_directories", output,
David Srbeckye0c3cd82022-08-24 14:56:24 +0100175 "--toolchain_inputs=prebuilts/jdk/jdk11/linux-x86/bin/java",
David Srbecky89010a32022-07-14 16:47:30 +0000176 os.path.relpath(os.environ["D8"], CWD)] + args, inputs)
177
David Srbecky8106b382022-04-20 13:37:15 +0100178 # If wrapper script exists, use it instead of the default javac.
179 if os.path.exists("javac_wrapper.sh"):
180 javac = functools.partial(run, "javac_wrapper.sh")
181
182 def find(root, name):
183 return sorted(glob.glob(path.join(root, "**", name), recursive=True))
184
185
186 def zip(zip_target, *files):
187 zip_args = ["-o", zip_target]
188 if zip_compression_method == "store":
189 zip_args.extend(["-L", "0"])
190 for f in files:
191 zip_args.extend(["-f", f])
192 soong_zip(zip_args)
193
194 if zip_align_bytes:
195 # zipalign does not operate in-place, so write results to a temp file.
196 with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
197 tmp_file = path.join(tmp_dir, "aligned.zip")
198 zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file])
199 # replace original zip target with our temp file.
200 os.rename(tmp_file, zip_target)
201
202
203 def make_jasmin(out_directory, jasmin_sources):
204 os.makedirs(out_directory, exist_ok=True)
205 jasmin(["-d", out_directory] + sorted(jasmin_sources))
206
207
208 # Like regular javac but may include libcore on the bootclasspath.
209 def javac_with_bootclasspath(args):
210 flags = JAVAC_ARGS + ["-encoding", "utf8"]
211 if BUILD_MODE != "jvm":
212 flags.extend(["-bootclasspath", ART_TEST_RUN_TEST_BOOTCLASSPATH])
213 javac(flags + args)
214
215
216 # Make a "dex" file given a directory of classes. This will be
217 # packaged in a jar file.
218 def make_dex(name):
219 d8_inputs = find(name, "*.class")
220 d8_output = name + ".jar"
221 dex_output = name + ".dex"
222 if use_desugar:
223 flags = ["--lib", ART_TEST_RUN_TEST_BOOTCLASSPATH]
224 else:
225 flags = ["--no-desugaring"]
226 assert d8_inputs
227 d8(D8_FLAGS + flags + ["--output", d8_output] + d8_inputs)
228
229 # D8 outputs to JAR files today rather than DEX files as DX used
230 # to. To compensate, we extract the DEX from d8's output to meet the
231 # expectations of make_dex callers.
232 with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
233 zipfile.ZipFile(d8_output, "r").extractall(tmp_dir)
234 os.rename(path.join(tmp_dir, "classes.dex"), dex_output)
235
236
237 # Merge all the dex files.
238 # Skip non-existing files, but at least 1 file must exist.
239 def make_dexmerge(*dex_files_to_merge):
240 # Dex file that acts as the destination.
241 dst_file = dex_files_to_merge[0]
242
243 # Skip any non-existing files.
244 dex_files_to_merge = list(filter(path.exists, dex_files_to_merge))
245
246 # NB: We merge even if there is just single input.
247 # It is useful to normalize non-deterministic smali output.
248
David Srbecky89010a32022-07-14 16:47:30 +0000249 tmp_dir = "dexmerge"
250 os.makedirs(tmp_dir)
251 d8(["--min-api", api_level, "--output", tmp_dir] + dex_files_to_merge)
252 assert not path.exists(path.join(tmp_dir, "classes2.dex"))
253 for input_dex in dex_files_to_merge:
254 os.remove(input_dex)
255 os.rename(path.join(tmp_dir, "classes.dex"), dst_file)
256 os.rmdir(tmp_dir)
David Srbecky8106b382022-04-20 13:37:15 +0100257
258
259 def make_hiddenapi(*dex_files):
260 args = ["encode"]
261 for dex_file in dex_files:
262 args.extend(["--input-dex=" + dex_file, "--output-dex=" + dex_file])
263 args.append("--api-flags=hiddenapi-flags.csv")
264 args.append("--no-force-assign-all")
265 hiddenapi(args)
266
267
268 if path.exists("classes.dex"):
269 zip(TEST_NAME + ".jar", "classes.dex")
270 return
271
272
273 def has_multidex():
274 return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX
275
276
277 def add_to_cp_args(old_cp_args, path):
278 if len(old_cp_args) == 0:
279 return ["-cp", path]
280 else:
281 return ["-cp", old_cp_args[1] + ":" + path]
282
283
284 src_tmp_all = []
285
286 if HAS_JASMIN:
287 make_jasmin("jasmin_classes", find("jasmin", "*.j"))
288 src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes")
289
290 if HAS_JASMIN_MULTIDEX:
291 make_jasmin("jasmin_classes2", find("jasmin-multidex", "*.j"))
292 src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes2")
293
294 if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or
295 HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2):
296 # To allow circular references, compile src/, src-multidex/, src-aotex/,
297 # src-bcpex/, src-ex/ together and pass the output as class path argument.
298 # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
299 # used by the other src-* sources we compile here but everything needed to
300 # compile the other src-* sources should be present in src/ (and jasmin*/).
301 os.makedirs("classes-tmp-all")
302 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
303 ["-d", "classes-tmp-all"] +
304 find("src", "*.java") +
305 find("src-multidex", "*.java") +
306 find("src-aotex", "*.java") +
307 find("src-bcpex", "*.java") +
308 find("src-ex", "*.java"))
309 src_tmp_all = add_to_cp_args(src_tmp_all, "classes-tmp-all")
310
311 if HAS_SRC_AOTEX:
312 os.makedirs("classes-aotex")
313 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
314 ["-d", "classes-aotex"] +
315 find("src-aotex", "*.java"))
316 if NEED_DEX:
317 make_dex("classes-aotex")
318 # rename it so it shows up as "classes.dex" in the zip file.
319 os.rename("classes-aotex.dex", "classes.dex")
320 zip(TEST_NAME + "-aotex.jar", "classes.dex")
321
322 if HAS_SRC_BCPEX:
323 os.makedirs("classes-bcpex")
324 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
325 ["-d", "classes-bcpex"] +
326 find("src-bcpex", "*.java"))
327 if NEED_DEX:
328 make_dex("classes-bcpex")
329 # rename it so it shows up as "classes.dex" in the zip file.
330 os.rename("classes-bcpex.dex", "classes.dex")
331 zip(TEST_NAME + "-bcpex.jar", "classes.dex")
332
333 if HAS_SRC:
334 os.makedirs("classes", exist_ok=True)
335 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
336 ["-d", "classes"] + find("src", "*.java"))
337
338 if HAS_SRC_ART:
339 os.makedirs("classes", exist_ok=True)
340 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
341 ["-d", "classes"] + find("src-art", "*.java"))
342
343 if HAS_SRC_MULTIDEX:
344 os.makedirs("classes2")
345 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
346 ["-d", "classes2"] +
347 find("src-multidex", "*.java"))
348 if NEED_DEX:
349 make_dex("classes2")
350
351 if HAS_SRC2:
352 os.makedirs("classes", exist_ok=True)
353 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
354 ["-d", "classes"] +
355 find("src2", "*.java"))
356
357 # If the classes directory is not-empty, package classes in a DEX file.
358 # NB: some tests provide classes rather than java files.
359 if find("classes", "*"):
360 if NEED_DEX:
361 make_dex("classes")
362
363 if HAS_JASMIN:
364 # Compile Jasmin classes as if they were part of the classes.dex file.
365 if NEED_DEX:
366 make_dex("jasmin_classes")
367 make_dexmerge("classes.dex", "jasmin_classes.dex")
368 else:
369 # Move jasmin classes into classes directory so that they are picked up
370 # with -cp classes.
371 os.makedirs("classes", exist_ok=True)
372 shutil.copytree("jasmin_classes", "classes", dirs_exist_ok=True)
373
374 if HAS_SMALI and NEED_DEX:
375 # Compile Smali classes
376 smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
377 ["--output", "smali_classes.dex"] + find("smali", "*.smali"))
378 assert path.exists("smali_classes.dex")
379 # Merge smali files into classes.dex,
380 # this takes priority over any jasmin files.
381 make_dexmerge("classes.dex", "smali_classes.dex")
382
383 # Compile Jasmin classes in jasmin-multidex as if they were part of
384 # the classes2.jar
385 if HAS_JASMIN_MULTIDEX:
386 if NEED_DEX:
387 make_dex("jasmin_classes2")
388 make_dexmerge("classes2.dex", "jasmin_classes2.dex")
389 else:
390 # Move jasmin classes into classes2 directory so that
391 # they are picked up with -cp classes2.
392 os.makedirs("classes2", exist_ok=True)
393 shutil.copytree("jasmin_classes2", "classes2", dirs_exist_ok=True)
394 shutil.rmtree("jasmin_classes2")
395
396 if HAS_SMALI_MULTIDEX and NEED_DEX:
397 # Compile Smali classes
398 smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
399 ["--output", "smali_classes2.dex"] + find("smali-multidex", "*.smali"))
400
401 # Merge smali_classes2.dex into classes2.dex
402 make_dexmerge("classes2.dex", "smali_classes2.dex")
403
404 if HAS_SRC_EX:
405 os.makedirs("classes-ex", exist_ok=True)
406 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
407 ["-d", "classes-ex"] + find("src-ex", "*.java"))
408
409 if HAS_SRC_EX2:
410 os.makedirs("classes-ex", exist_ok=True)
411 javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
412 ["-d", "classes-ex"] + find("src-ex2", "*.java"))
413
414 if path.exists("classes-ex") and NEED_DEX:
415 make_dex("classes-ex")
416
417 if HAS_SMALI_EX and NEED_DEX:
418 # Compile Smali classes
419 smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
420 ["--output", "smali_classes-ex.dex"] + find("smali-ex", "*.smali"))
421 assert path.exists("smali_classes-ex.dex")
422 # Merge smali files into classes-ex.dex.
423 make_dexmerge("classes-ex.dex", "smali_classes-ex.dex")
424
425 if path.exists("classes-ex.dex"):
426 # Apply hiddenapi on the dex files if the test has API list file(s).
427 if use_hiddenapi and HAS_HIDDENAPI_SPEC:
428 make_hiddenapi("classes-ex.dex")
429
430 # quick shuffle so that the stored name is "classes.dex"
431 os.rename("classes.dex", "classes-1.dex")
432 os.rename("classes-ex.dex", "classes.dex")
433 zip(TEST_NAME + "-ex.jar", "classes.dex")
434 os.rename("classes.dex", "classes-ex.dex")
435 os.rename("classes-1.dex", "classes.dex")
436
437 # Apply hiddenapi on the dex files if the test has API list file(s).
438 if NEED_DEX and use_hiddenapi and HAS_HIDDENAPI_SPEC:
439 if has_multidex():
440 make_hiddenapi("classes.dex", "classes2.dex")
441 else:
442 make_hiddenapi("classes.dex")
443
444 # Create a single dex jar with two dex files for multidex.
445 if NEED_DEX:
446 if path.exists("classes2.dex"):
447 zip(TEST_NAME + ".jar", "classes.dex", "classes2.dex")
448 else:
449 zip(TEST_NAME + ".jar", "classes.dex")