blob: 36eddaeeef2e3846fc0e3d8756d2e80780097424 [file] [log] [blame]
David Srbecky37274d52022-11-03 11:04:16 +00001#!/usr/bin/env python3
David Srbecky8106b382022-04-20 13:37:15 +01002#
3# Copyright (C) 2021 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
David Srbecky37274d52022-11-03 11:04:16 +000017"""
18This scripts compiles Java files which are needed to execute run-tests.
19It is intended to be used only from soong genrule.
David Srbecky8106b382022-04-20 13:37:15 +010020"""
21
22import argparse
23import functools
24import glob
25import os
David Srbecky36b520b2022-10-27 14:43:12 +010026import pathlib
David Srbeckyc9453d32022-11-17 13:28:28 +000027import re
David Srbecky8106b382022-04-20 13:37:15 +010028import shlex
29import shutil
30import subprocess
David Srbecky6bd1cd12022-11-05 18:54:19 +000031import sys
David Srbecky8106b382022-04-20 13:37:15 +010032import zipfile
David Srbecky6bd1cd12022-11-05 18:54:19 +000033
David Srbecky37274d52022-11-03 11:04:16 +000034from argparse import ArgumentParser
David Srbeckyc9453d32022-11-17 13:28:28 +000035from concurrent.futures import ThreadPoolExecutor
David Srbecky37274d52022-11-03 11:04:16 +000036from fcntl import lockf, LOCK_EX, LOCK_NB
37from importlib.machinery import SourceFileLoader
David Srbecky36b520b2022-10-27 14:43:12 +010038from os import environ, getcwd, chdir, cpu_count, chmod
39from os.path import relpath
David Srbecky37274d52022-11-03 11:04:16 +000040from pathlib import Path
David Srbecky6bd1cd12022-11-05 18:54:19 +000041from pprint import pprint
David Srbeckye0c3cd82022-08-24 14:56:24 +010042from re import match
David Srbecky37274d52022-11-03 11:04:16 +000043from shutil import copytree, rmtree
44from subprocess import run
David Srbecky36b520b2022-10-27 14:43:12 +010045from tempfile import TemporaryDirectory, NamedTemporaryFile
David Srbecky100fe672022-11-14 11:32:27 +000046from typing import Dict, List, Union, Set, Optional
David Srbecky8106b382022-04-20 13:37:15 +010047
David Srbeckya42a2e32022-11-17 13:43:22 +000048USE_RBE = 100 # Percentage of tests that can use RBE (between 0 and 100)
David Srbecky37274d52022-11-03 11:04:16 +000049
50lock_file = None # Keep alive as long as this process is alive.
51
David Srbeckyf32b2582022-11-17 15:28:35 +000052RBE_D8_DISABLED_FOR = {
David Srbeckyf32b2582022-11-17 15:28:35 +000053 "952-invoke-custom", # b/228312861: RBE uses wrong inputs.
54 "979-const-method-handle", # b/228312861: RBE uses wrong inputs.
55}
David Srbecky89010a32022-07-14 16:47:30 +000056
David Srbeckyc9453d32022-11-17 13:28:28 +000057# Debug option. Report commands that are taking a lot of user CPU time.
58REPORT_SLOW_COMMANDS = False
59
David Srbecky51dec052022-10-27 14:43:12 +010060class BuildTestContext:
David Srbecky36b520b2022-10-27 14:43:12 +010061 def __init__(self, args, android_build_top, test_dir):
David Srbeckyfeed4af2022-11-17 10:44:39 +000062 self.android_build_top = android_build_top.absolute()
63 self.bootclasspath = args.bootclasspath.absolute()
David Srbecky6bd1cd12022-11-05 18:54:19 +000064 self.test_name = test_dir.name
65 self.test_dir = test_dir.absolute()
David Srbecky51dec052022-10-27 14:43:12 +010066 self.mode = args.mode
67 self.jvm = (self.mode == "jvm")
68 self.host = (self.mode == "host")
69 self.target = (self.mode == "target")
70 assert self.jvm or self.host or self.target
71
David Srbecky36b520b2022-10-27 14:43:12 +010072 self.java_home = Path(os.environ.get("JAVA_HOME")).absolute()
David Srbeckyfeed4af2022-11-17 10:44:39 +000073 self.java_path = self.java_home / "bin/java"
74 self.javac_path = self.java_home / "bin/javac"
Nicolas Geoffraybb8d6f62023-09-12 11:04:23 +010075 self.javac_args = "-g -Xlint:-options"
David Srbecky6bd1cd12022-11-05 18:54:19 +000076
David Srbeckyfeed4af2022-11-17 10:44:39 +000077 # Helper functions to execute tools.
78 self.d8 = functools.partial(self.run, args.d8.absolute())
79 self.jasmin = functools.partial(self.run, args.jasmin.absolute())
80 self.javac = functools.partial(self.run, self.javac_path)
81 self.smali = functools.partial(self.run, args.smali.absolute())
82 self.soong_zip = functools.partial(self.run, args.soong_zip.absolute())
83 self.zipalign = functools.partial(self.run, args.zipalign.absolute())
84 if args.hiddenapi:
85 self.hiddenapi = functools.partial(self.run, args.hiddenapi.absolute())
86
87 # RBE wrapper for some of the tools.
Hans Boehm6183e752023-01-11 00:45:46 +000088 if "RBE_server_address" in os.environ and USE_RBE > (hash(self.test_name) % 100):
David Srbeckyfeed4af2022-11-17 10:44:39 +000089 self.rbe_exec_root = os.environ.get("RBE_exec_root")
90 self.rbe_rewrapper = self.android_build_top / "prebuilts/remoteexecution-client/live/rewrapper"
David Srbecky17b50422023-10-26 15:25:41 +010091
92 # TODO(b/307932183) Regression: RBE produces wrong output for D8 in ART
93 disable_d8 = any((self.test_dir / n).exists() for n in ["classes", "src2", "src-art"])
94
95 if self.test_name not in RBE_D8_DISABLED_FOR and not disable_d8:
David Srbeckyf32b2582022-11-17 15:28:35 +000096 self.d8 = functools.partial(self.rbe_d8, args.d8.absolute())
David Srbeckya42a2e32022-11-17 13:43:22 +000097 self.javac = functools.partial(self.rbe_javac, self.javac_path)
98 self.smali = functools.partial(self.rbe_smali, args.smali.absolute())
David Srbecky51dec052022-10-27 14:43:12 +010099
100 # Minimal environment needed for bash commands that we execute.
101 self.bash_env = {
102 "ANDROID_BUILD_TOP": self.android_build_top,
David Srbeckyfeed4af2022-11-17 10:44:39 +0000103 "D8": args.d8.absolute(),
104 "JAVA": self.java_path,
105 "JAVAC": self.javac_path,
David Srbecky51dec052022-10-27 14:43:12 +0100106 "JAVAC_ARGS": self.javac_args,
David Srbecky6bd1cd12022-11-05 18:54:19 +0000107 "JAVA_HOME": self.java_home,
David Srbecky51dec052022-10-27 14:43:12 +0100108 "PATH": os.environ["PATH"],
109 "PYTHONDONTWRITEBYTECODE": "1",
David Srbeckyfeed4af2022-11-17 10:44:39 +0000110 "SMALI": args.smali.absolute(),
111 "SOONG_ZIP": args.soong_zip.absolute(),
David Srbecky51dec052022-10-27 14:43:12 +0100112 "TEST_NAME": self.test_name,
113 }
114
115 def bash(self, cmd):
David Srbecky36b520b2022-10-27 14:43:12 +0100116 return subprocess.run(cmd,
117 shell=True,
118 cwd=self.test_dir,
119 env=self.bash_env,
120 check=True)
David Srbecky51dec052022-10-27 14:43:12 +0100121
David Srbeckyf32b2582022-11-17 15:28:35 +0000122 def run(self, executable: pathlib.Path, args: List[Union[pathlib.Path, str]]):
David Srbeckyfeed4af2022-11-17 10:44:39 +0000123 assert isinstance(executable, pathlib.Path), executable
124 cmd: List[Union[pathlib.Path, str]] = []
David Srbeckyc9453d32022-11-17 13:28:28 +0000125 if REPORT_SLOW_COMMANDS:
126 cmd += ["/usr/bin/time"]
David Srbeckyfeed4af2022-11-17 10:44:39 +0000127 if executable.suffix == ".sh":
128 cmd += ["/bin/bash"]
129 cmd += [executable]
130 cmd += args
131 env = self.bash_env
132 env.update({k: v for k, v in os.environ.items() if k.startswith("RBE_")})
133 # Make paths relative as otherwise we could create too long command line.
134 for i, arg in enumerate(cmd):
135 if isinstance(arg, pathlib.Path):
136 assert arg.absolute(), arg
137 cmd[i] = relpath(arg, self.test_dir)
138 elif isinstance(arg, list):
139 assert all(p.absolute() for p in arg), arg
140 cmd[i] = ":".join(relpath(p, self.test_dir) for p in arg)
141 else:
142 assert isinstance(arg, str), arg
143 p = subprocess.run(cmd,
144 encoding=sys.stdout.encoding,
145 cwd=self.test_dir,
146 env=self.bash_env,
147 stderr=subprocess.STDOUT,
148 stdout=subprocess.PIPE)
David Srbeckyc9453d32022-11-17 13:28:28 +0000149 if REPORT_SLOW_COMMANDS:
150 m = re.search("([0-9\.]+)user", p.stdout)
151 assert m, p.stdout
152 t = float(m.group(1))
153 if t > 1.0:
154 cmd_text = " ".join(map(str, cmd[1:]))[:100]
155 print(f"[{self.test_name}] Command took {t:.2f}s: {cmd_text}")
156
David Srbeckyfeed4af2022-11-17 10:44:39 +0000157 if p.returncode != 0:
158 raise Exception("Command failed with exit code {}\n$ {}\n{}".format(
159 p.returncode, " ".join(map(str, cmd)), p.stdout))
160 return p
161
162 def rbe_wrap(self, args, inputs: Set[pathlib.Path]=None):
163 with NamedTemporaryFile(mode="w+t") as input_list:
164 inputs = inputs or set()
165 for i, arg in enumerate(args):
166 if isinstance(arg, pathlib.Path):
167 assert arg.absolute(), arg
168 inputs.add(arg)
169 elif isinstance(arg, list):
170 assert all(p.absolute() for p in arg), arg
171 inputs.update(arg)
172 input_list.writelines([relpath(i, self.rbe_exec_root)+"\n" for i in inputs])
173 input_list.flush()
174 return self.run(self.rbe_rewrapper, [
175 "--platform=" + os.environ["RBE_platform"],
176 "--input_list_paths=" + input_list.name,
177 ] + args)
178
David Srbeckya42a2e32022-11-17 13:43:22 +0000179 def rbe_javac(self, javac_path:Path, args):
David Srbeckyfeed4af2022-11-17 10:44:39 +0000180 output = relpath(Path(args[args.index("-d") + 1]), self.rbe_exec_root)
David Srbeckya42a2e32022-11-17 13:43:22 +0000181 return self.rbe_wrap(["--output_directories", output, javac_path] + args)
David Srbeckyfeed4af2022-11-17 10:44:39 +0000182
David Srbeckya42a2e32022-11-17 13:43:22 +0000183 def rbe_d8(self, d8_path:Path, args):
184 inputs = set([d8_path.parent.parent / "framework/d8.jar"])
David Srbeckyfeed4af2022-11-17 10:44:39 +0000185 output = relpath(Path(args[args.index("--output") + 1]), self.rbe_exec_root)
186 return self.rbe_wrap([
187 "--output_files" if output.endswith(".jar") else "--output_directories", output,
Sorin Bascae1f8e3c2022-12-21 15:58:58 +0000188 "--toolchain_inputs=prebuilts/jdk/jdk17/linux-x86/bin/java",
David Srbeckya42a2e32022-11-17 13:43:22 +0000189 d8_path] + args, inputs)
190
191 def rbe_smali(self, smali_path:Path, args):
192 inputs = set([smali_path.parent.parent / "framework/smali.jar"])
193 output = relpath(Path(args[args.index("--output") + 1]), self.rbe_exec_root)
194 return self.rbe_wrap([
195 "--output_files", output,
Sorin Bascae1f8e3c2022-12-21 15:58:58 +0000196 "--toolchain_inputs=prebuilts/jdk/jdk17/linux-x86/bin/java",
David Srbeckya42a2e32022-11-17 13:43:22 +0000197 smali_path] + args, inputs)
David Srbeckyfeed4af2022-11-17 10:44:39 +0000198
David Srbeckyd272f2d2022-11-15 19:13:34 +0000199 def build(self) -> None:
200 script = self.test_dir / "build.py"
201 if script.exists():
202 module = SourceFileLoader("build_" + self.test_name,
203 str(script)).load_module()
204 module.build(self)
205 else:
206 self.default_build()
207
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000208 def default_build(
David Srbeckyd272f2d2022-11-15 19:13:34 +0000209 self,
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000210 use_desugar=True,
211 use_hiddenapi=True,
212 need_dex=None,
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000213 zip_compression_method="deflate",
214 zip_align_bytes=None,
David Srbeckye82c87d2022-11-16 20:31:11 +0000215 api_level:Union[int, str]=26, # Can also be named alias (string).
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000216 javac_args=[],
David Srbeckyf32b2582022-11-17 15:28:35 +0000217 javac_classpath: List[Path]=[],
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000218 d8_flags=[],
David Srbecky54242832023-11-16 15:41:46 +0000219 d8_dex_container=True,
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000220 smali_args=[],
221 use_smali=True,
222 use_jasmin=True,
Nicolas Geoffraybb8d6f62023-09-12 11:04:23 +0100223 javac_source_arg="1.8",
224 javac_target_arg="1.8"
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000225 ):
David Srbeckyf32b2582022-11-17 15:28:35 +0000226 javac_classpath = javac_classpath.copy() # Do not modify default value.
David Srbecky51dec052022-10-27 14:43:12 +0100227
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000228 # Wrap "pathlib.Path" with our own version that ensures all paths are absolute.
David Srbeckyd272f2d2022-11-15 19:13:34 +0000229 # Plain filenames are assumed to be relative to self.test_dir and made absolute.
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000230 class Path(pathlib.Path):
231 def __new__(cls, filename: str):
232 path = pathlib.Path(filename)
David Srbeckyd272f2d2022-11-15 19:13:34 +0000233 return path if path.is_absolute() else (self.test_dir / path)
David Srbecky8106b382022-04-20 13:37:15 +0100234
David Srbeckyd272f2d2022-11-15 19:13:34 +0000235 need_dex = (self.host or self.target) if need_dex is None else need_dex
David Srbecky8106b382022-04-20 13:37:15 +0100236
David Srbeckyd272f2d2022-11-15 19:13:34 +0000237 if self.jvm:
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000238 # No desugaring on jvm because it supports the latest functionality.
239 use_desugar = False
David Srbecky8106b382022-04-20 13:37:15 +0100240
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000241 # Set API level for smali and d8.
David Srbeckye82c87d2022-11-16 20:31:11 +0000242 if isinstance(api_level, str):
243 API_LEVEL = {
David Srbeckyd272f2d2022-11-15 19:13:34 +0000244 "default-methods": 24,
245 "parameter-annotations": 25,
246 "agents": 26,
247 "method-handles": 26,
248 "var-handles": 28,
Almaz Mingaleevee42bfa2023-11-22 15:49:15 +0000249 "const-method-type": 28,
David Srbeckyd272f2d2022-11-15 19:13:34 +0000250 }
David Srbeckye82c87d2022-11-16 20:31:11 +0000251 api_level = API_LEVEL[api_level]
252 assert isinstance(api_level, int), api_level
David Srbecky8106b382022-04-20 13:37:15 +0100253
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000254 def zip(zip_target: Path, *files: Path):
255 zip_args = ["-o", zip_target, "-C", zip_target.parent]
256 if zip_compression_method == "store":
257 zip_args.extend(["-L", "0"])
258 for f in files:
259 zip_args.extend(["-f", f])
David Srbeckyfeed4af2022-11-17 10:44:39 +0000260 self.soong_zip(zip_args)
David Srbecky8106b382022-04-20 13:37:15 +0100261
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000262 if zip_align_bytes:
263 # zipalign does not operate in-place, so write results to a temp file.
264 with TemporaryDirectory() as tmp_dir:
265 tmp_file = Path(tmp_dir) / "aligned.zip"
David Srbeckyfeed4af2022-11-17 10:44:39 +0000266 self.zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file])
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000267 # replace original zip target with our temp file.
268 tmp_file.rename(zip_target)
269
270
271 def make_jasmin(dst_dir: Path, src_dir: Path) -> Optional[Path]:
272 if not use_jasmin or not src_dir.exists():
273 return None # No sources to compile.
274 dst_dir.mkdir()
David Srbeckyfeed4af2022-11-17 10:44:39 +0000275 self.jasmin(["-d", dst_dir] + sorted(src_dir.glob("**/*.j")))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000276 return dst_dir
277
278 def make_smali(dst_dex: Path, src_dir: Path) -> Optional[Path]:
279 if not use_smali or not src_dir.exists():
280 return None # No sources to compile.
David Srbeckyd7c770a2023-10-26 15:45:57 +0100281 p = self.smali(["-JXmx512m", "assemble"] + smali_args + ["--api", str(api_level)] +
282 ["--output", dst_dex] + sorted(src_dir.glob("**/*.smali")))
283 assert dst_dex.exists(), p.stdout # NB: smali returns 0 exit code even on failure.
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000284 return dst_dex
285
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000286 def make_java(dst_dir: Path, *src_dirs: Path) -> Optional[Path]:
287 if not any(src_dir.exists() for src_dir in src_dirs):
288 return None # No sources to compile.
289 dst_dir.mkdir(exist_ok=True)
David Srbeckyd272f2d2022-11-15 19:13:34 +0000290 args = self.javac_args.split(" ") + javac_args
291 args += ["-implicit:none", "-encoding", "utf8", "-d", dst_dir]
Nicolas Geoffraybb8d6f62023-09-12 11:04:23 +0100292 args += ["-source", javac_source_arg, "-target", javac_target_arg]
293 if not self.jvm and float(javac_target_arg) < 17.0:
David Srbeckyd272f2d2022-11-15 19:13:34 +0000294 args += ["-bootclasspath", self.bootclasspath]
David Srbeckyf32b2582022-11-17 15:28:35 +0000295 if javac_classpath:
296 args += ["-classpath", javac_classpath]
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000297 for src_dir in src_dirs:
298 args += sorted(src_dir.glob("**/*.java"))
David Srbeckyfeed4af2022-11-17 10:44:39 +0000299 self.javac(args)
David Srbeckyf32b2582022-11-17 15:28:35 +0000300 javac_post = Path("javac_post.sh")
301 if javac_post.exists():
302 self.run(javac_post, [dst_dir])
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000303 return dst_dir
304
305
306 # Make a "dex" file given a directory of classes. This will be
307 # packaged in a jar file.
308 def make_dex(src_dir: Path):
309 dst_jar = Path(src_dir.name + ".jar")
David Srbecky54242832023-11-16 15:41:46 +0000310 args = []
311 if d8_dex_container:
312 args += ["-JDcom.android.tools.r8.dexContainerExperiment"]
313 args += d8_flags + ["--min-api", str(api_level), "--output", dst_jar]
David Srbeckyd272f2d2022-11-15 19:13:34 +0000314 args += ["--lib", self.bootclasspath] if use_desugar else ["--no-desugaring"]
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000315 args += sorted(src_dir.glob("**/*.class"))
David Srbeckyfeed4af2022-11-17 10:44:39 +0000316 self.d8(args)
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000317
318 # D8 outputs to JAR files today rather than DEX files as DX used
319 # to. To compensate, we extract the DEX from d8's output to meet the
320 # expectations of make_dex callers.
321 dst_dex = Path(src_dir.name + ".dex")
David Srbecky36b520b2022-10-27 14:43:12 +0100322 with TemporaryDirectory() as tmp_dir:
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000323 zipfile.ZipFile(dst_jar, "r").extractall(tmp_dir)
324 (Path(tmp_dir) / "classes.dex").rename(dst_dex)
325
326 # Merge all the dex files.
327 # Skip non-existing files, but at least 1 file must exist.
328 def make_dexmerge(dst_dex: Path, *src_dexs: Path):
329 # Include destination. Skip any non-existing files.
330 srcs = [f for f in [dst_dex] + list(src_dexs) if f.exists()]
331
332 # NB: We merge even if there is just single input.
333 # It is useful to normalize non-deterministic smali output.
David Srbeckyd272f2d2022-11-15 19:13:34 +0000334 tmp_dir = self.test_dir / "dexmerge"
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000335 tmp_dir.mkdir()
David Srbecky54242832023-11-16 15:41:46 +0000336 flags = []
337 if d8_dex_container:
338 flags += ["-JDcom.android.tools.r8.dexContainerExperiment"]
339 flags += ["--min-api", str(api_level), "--output", tmp_dir]
340 self.d8(flags + srcs)
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000341 assert not (tmp_dir / "classes2.dex").exists()
342 for src_file in srcs:
343 src_file.unlink()
344 (tmp_dir / "classes.dex").rename(dst_dex)
345 tmp_dir.rmdir()
David Srbecky8106b382022-04-20 13:37:15 +0100346
347
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000348 def make_hiddenapi(*dex_files: Path):
David Srbeckyd272f2d2022-11-15 19:13:34 +0000349 if not use_hiddenapi or not Path("hiddenapi-flags.csv").exists():
350 return # Nothing to do.
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000351 args: List[Union[str, Path]] = ["encode"]
352 for dex_file in dex_files:
353 args.extend(["--input-dex=" + str(dex_file), "--output-dex=" + str(dex_file)])
354 args.append("--api-flags=hiddenapi-flags.csv")
355 args.append("--no-force-assign-all")
David Srbeckyfeed4af2022-11-17 10:44:39 +0000356 self.hiddenapi(args)
David Srbecky8106b382022-04-20 13:37:15 +0100357
358
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000359 if Path("classes.dex").exists():
David Srbeckyd272f2d2022-11-15 19:13:34 +0000360 zip(Path(self.test_name + ".jar"), Path("classes.dex"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000361 return
362
363 if Path("classes.dm").exists():
David Srbeckyd272f2d2022-11-15 19:13:34 +0000364 zip(Path(self.test_name + ".jar"), Path("classes.dm"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000365 return
David Srbecky37274d52022-11-03 11:04:16 +0000366
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000367 if make_jasmin(Path("jasmin_classes"), Path("jasmin")):
David Srbeckyf32b2582022-11-17 15:28:35 +0000368 javac_classpath.append(Path("jasmin_classes"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000369
370 if make_jasmin(Path("jasmin_classes2"), Path("jasmin-multidex")):
David Srbeckyf32b2582022-11-17 15:28:35 +0000371 javac_classpath.append(Path("jasmin_classes2"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000372
David Srbeckyd272f2d2022-11-15 19:13:34 +0000373 # To allow circular references, compile src/, src-multidex/, src-aotex/,
374 # src-bcpex/, src-ex/ together and pass the output as class path argument.
375 # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
376 # used by the other src-* sources we compile here but everything needed to
377 # compile the other src-* sources should be present in src/ (and jasmin*/).
378 extra_srcs = ["src-multidex", "src-aotex", "src-bcpex", "src-ex"]
379 replacement_srcs = ["src2", "src-ex2"] + ([] if self.jvm else ["src-art"])
380 if (Path("src").exists() and
381 any(Path(p).exists() for p in extra_srcs + replacement_srcs)):
382 make_java(Path("classes-tmp-all"), Path("src"), *map(Path, extra_srcs))
David Srbeckyf32b2582022-11-17 15:28:35 +0000383 javac_classpath.append(Path("classes-tmp-all"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000384
385 if make_java(Path("classes-aotex"), Path("src-aotex")) and need_dex:
386 make_dex(Path("classes-aotex"))
387 # rename it so it shows up as "classes.dex" in the zip file.
388 Path("classes-aotex.dex").rename(Path("classes.dex"))
David Srbeckyd272f2d2022-11-15 19:13:34 +0000389 zip(Path(self.test_name + "-aotex.jar"), Path("classes.dex"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000390
391 if make_java(Path("classes-bcpex"), Path("src-bcpex")) and need_dex:
392 make_dex(Path("classes-bcpex"))
393 # rename it so it shows up as "classes.dex" in the zip file.
394 Path("classes-bcpex.dex").rename(Path("classes.dex"))
David Srbeckyd272f2d2022-11-15 19:13:34 +0000395 zip(Path(self.test_name + "-bcpex.jar"), Path("classes.dex"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000396
397 make_java(Path("classes"), Path("src"))
398
David Srbeckyd272f2d2022-11-15 19:13:34 +0000399 if not self.jvm:
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000400 # Do not attempt to build src-art directories on jvm,
401 # since it would fail without libcore.
402 make_java(Path("classes"), Path("src-art"))
403
404 if make_java(Path("classes2"), Path("src-multidex")) and need_dex:
405 make_dex(Path("classes2"))
406
407 make_java(Path("classes"), Path("src2"))
408
409 # If the classes directory is not-empty, package classes in a DEX file.
410 # NB: some tests provide classes rather than java files.
411 if any(Path("classes").glob("*")) and need_dex:
412 make_dex(Path("classes"))
413
414 if Path("jasmin_classes").exists():
415 # Compile Jasmin classes as if they were part of the classes.dex file.
416 if need_dex:
417 make_dex(Path("jasmin_classes"))
418 make_dexmerge(Path("classes.dex"), Path("jasmin_classes.dex"))
419 else:
420 # Move jasmin classes into classes directory so that they are picked up
421 # with -cp classes.
422 Path("classes").mkdir(exist_ok=True)
423 copytree(Path("jasmin_classes"), Path("classes"), dirs_exist_ok=True)
424
425 if need_dex and make_smali(Path("smali_classes.dex"), Path("smali")):
426 # Merge smali files into classes.dex,
427 # this takes priority over any jasmin files.
428 make_dexmerge(Path("classes.dex"), Path("smali_classes.dex"))
429
430 # Compile Jasmin classes in jasmin-multidex as if they were part of
431 # the classes2.jar
David Srbeckyd272f2d2022-11-15 19:13:34 +0000432 if Path("jasmin-multidex").exists():
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000433 if need_dex:
434 make_dex(Path("jasmin_classes2"))
435 make_dexmerge(Path("classes2.dex"), Path("jasmin_classes2.dex"))
436 else:
437 # Move jasmin classes into classes2 directory so that
438 # they are picked up with -cp classes2.
439 Path("classes2").mkdir()
440 copytree(Path("jasmin_classes2"), Path("classes2"), dirs_exist_ok=True)
441 rmtree(Path("jasmin_classes2"))
442
443 if need_dex and make_smali(Path("smali_classes2.dex"), Path("smali-multidex")):
444 # Merge smali_classes2.dex into classes2.dex
445 make_dexmerge(Path("classes2.dex"), Path("smali_classes2.dex"))
446
447 make_java(Path("classes-ex"), Path("src-ex"))
448
449 make_java(Path("classes-ex"), Path("src-ex2"))
450
451 if Path("classes-ex").exists() and need_dex:
452 make_dex(Path("classes-ex"))
453
454 if need_dex and make_smali(Path("smali_classes-ex.dex"), Path("smali-ex")):
455 # Merge smali files into classes-ex.dex.
456 make_dexmerge(Path("classes-ex.dex"), Path("smali_classes-ex.dex"))
457
458 if Path("classes-ex.dex").exists():
459 # Apply hiddenapi on the dex files if the test has API list file(s).
David Srbeckyd272f2d2022-11-15 19:13:34 +0000460 make_hiddenapi(Path("classes-ex.dex"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000461
462 # quick shuffle so that the stored name is "classes.dex"
463 Path("classes.dex").rename(Path("classes-1.dex"))
464 Path("classes-ex.dex").rename(Path("classes.dex"))
David Srbeckyd272f2d2022-11-15 19:13:34 +0000465 zip(Path(self.test_name + "-ex.jar"), Path("classes.dex"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000466 Path("classes.dex").rename(Path("classes-ex.dex"))
467 Path("classes-1.dex").rename(Path("classes.dex"))
468
469 # Apply hiddenapi on the dex files if the test has API list file(s).
David Srbeckyd272f2d2022-11-15 19:13:34 +0000470 if need_dex:
471 if any(Path(".").glob("*-multidex")):
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000472 make_hiddenapi(Path("classes.dex"), Path("classes2.dex"))
473 else:
474 make_hiddenapi(Path("classes.dex"))
475
476 # Create a single dex jar with two dex files for multidex.
477 if need_dex:
478 if Path("classes2.dex").exists():
David Srbeckyd272f2d2022-11-15 19:13:34 +0000479 zip(Path(self.test_name + ".jar"), Path("classes.dex"), Path("classes2.dex"))
David Srbeckyaf0ad5a2022-11-15 18:28:35 +0000480 else:
David Srbeckyd272f2d2022-11-15 19:13:34 +0000481 zip(Path(self.test_name + ".jar"), Path("classes.dex"))
David Srbecky37274d52022-11-03 11:04:16 +0000482
483
484# If we build just individual shard, we want to split the work among all the cores,
485# but if the build system builds all shards, we don't want to overload the machine.
486# We don't know which situation we are in, so as simple work-around, we use a lock
487# file to allow only one shard to use multiprocessing at the same time.
488def use_multiprocessing(mode: str) -> bool:
David Srbeckyd26f9072022-12-09 16:40:40 +0000489 if "RBE_server_address" in os.environ:
490 return True
David Srbecky37274d52022-11-03 11:04:16 +0000491 global lock_file
David Srbecky36b520b2022-10-27 14:43:12 +0100492 lock_path = Path(environ["TMPDIR"]) / ("art-test-run-test-build-py-" + mode)
David Srbecky37274d52022-11-03 11:04:16 +0000493 lock_file = open(lock_path, "w")
494 try:
495 lockf(lock_file, LOCK_EX | LOCK_NB)
496 return True # We are the only instance of this script in the build system.
497 except BlockingIOError:
498 return False # Some other instance is already running.
499
500
501def main() -> None:
502 parser = ArgumentParser(description=__doc__)
David Srbecky6bd1cd12022-11-05 18:54:19 +0000503 parser.add_argument("--out", type=Path, help="Final zip file")
David Srbecky37274d52022-11-03 11:04:16 +0000504 parser.add_argument("--mode", choices=["host", "jvm", "target"])
David Srbecky6bd1cd12022-11-05 18:54:19 +0000505 parser.add_argument("--bootclasspath", type=Path)
506 parser.add_argument("--d8", type=Path)
507 parser.add_argument("--hiddenapi", type=Path)
508 parser.add_argument("--jasmin", type=Path)
509 parser.add_argument("--smali", type=Path)
510 parser.add_argument("--soong_zip", type=Path)
511 parser.add_argument("--zipalign", type=Path)
512 parser.add_argument("srcs", nargs="+", type=Path)
David Srbecky37274d52022-11-03 11:04:16 +0000513 args = parser.parse_args()
514
David Srbecky36b520b2022-10-27 14:43:12 +0100515 android_build_top = Path(getcwd()).absolute()
516 ziproot = args.out.absolute().parent / "zip"
517 srcdirs = set(s.parents[-4].absolute() for s in args.srcs)
David Srbecky37274d52022-11-03 11:04:16 +0000518
David Srbeckyc282bef2022-11-16 21:52:33 +0000519 # Special hidden-api shard: If the --hiddenapi flag is provided, build only
520 # hiddenapi tests. Otherwise exclude all hiddenapi tests from normal shards.
521 def filter_by_hiddenapi(srcdir: Path) -> bool:
522 return (args.hiddenapi != None) == ("hiddenapi" in srcdir.name)
523
David Srbecky36b520b2022-10-27 14:43:12 +0100524 # Initialize the test objects.
525 # We need to do this before we change the working directory below.
526 tests: List[BuildTestContext] = []
David Srbeckyc282bef2022-11-16 21:52:33 +0000527 for srcdir in filter(filter_by_hiddenapi, srcdirs):
David Srbecky36b520b2022-10-27 14:43:12 +0100528 dstdir = ziproot / args.mode / srcdir.name
529 copytree(srcdir, dstdir)
530 tests.append(BuildTestContext(args, android_build_top, dstdir))
531
532 # We can not change the working directory per each thread since they all run in parallel.
533 # Create invalid read-only directory to catch accidental use of current working directory.
534 with TemporaryDirectory("-do-not-use-cwd") as invalid_tmpdir:
535 os.chdir(invalid_tmpdir)
536 os.chmod(invalid_tmpdir, 0)
537 with ThreadPoolExecutor(cpu_count() if use_multiprocessing(args.mode) else 1) as pool:
538 jobs = {}
539 for ctx in tests:
David Srbeckyd272f2d2022-11-15 19:13:34 +0000540 jobs[ctx.test_name] = pool.submit(ctx.build)
David Srbecky36b520b2022-10-27 14:43:12 +0100541 for test_name, job in jobs.items():
542 try:
543 job.result()
544 except Exception as e:
545 raise Exception("Failed to build " + test_name) from e
David Srbecky37274d52022-11-03 11:04:16 +0000546
547 # Create the final zip file which contains the content of the temporary directory.
David Srbecky36b520b2022-10-27 14:43:12 +0100548 proc = run([android_build_top / args.soong_zip, "-o", android_build_top / args.out,
549 "-C", ziproot, "-D", ziproot], check=True)
David Srbecky37274d52022-11-03 11:04:16 +0000550
551
552if __name__ == "__main__":
553 main()