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