blob: 05184ed2e1ddcaaf4c11c46ca7ed1a1ee0ef0c83 [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 Srbecky8106b382022-04-20 13:37:15 +010027import shlex
28import shutil
29import subprocess
David Srbecky6bd1cd12022-11-05 18:54:19 +000030import sys
David Srbecky8106b382022-04-20 13:37:15 +010031import zipfile
David Srbecky6bd1cd12022-11-05 18:54:19 +000032
David Srbecky37274d52022-11-03 11:04:16 +000033from argparse import ArgumentParser
34from fcntl import lockf, LOCK_EX, LOCK_NB
35from importlib.machinery import SourceFileLoader
David Srbecky36b520b2022-10-27 14:43:12 +010036from concurrent.futures import ThreadPoolExecutor
37from os import environ, getcwd, chdir, cpu_count, chmod
38from os.path import relpath
David Srbecky37274d52022-11-03 11:04:16 +000039from pathlib import Path
David Srbecky6bd1cd12022-11-05 18:54:19 +000040from pprint import pprint
David Srbeckye0c3cd82022-08-24 14:56:24 +010041from re import match
David Srbecky37274d52022-11-03 11:04:16 +000042from shutil import copytree, rmtree
43from subprocess import run
David Srbecky36b520b2022-10-27 14:43:12 +010044from tempfile import TemporaryDirectory, NamedTemporaryFile
David Srbecky100fe672022-11-14 11:32:27 +000045from typing import Dict, List, Union, Set, Optional
David Srbecky8106b382022-04-20 13:37:15 +010046
David Srbeckye9142f22022-08-03 11:05:19 +010047USE_RBE_FOR_JAVAC = 100 # Percentage of tests that can use RBE (between 0 and 100)
48USE_RBE_FOR_D8 = 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 Srbecky89010a32022-07-14 16:47:30 +000052
David Srbecky51dec052022-10-27 14:43:12 +010053class BuildTestContext:
David Srbecky36b520b2022-10-27 14:43:12 +010054 def __init__(self, args, android_build_top, test_dir):
David Srbecky6bd1cd12022-11-05 18:54:19 +000055 self.test_name = test_dir.name
56 self.test_dir = test_dir.absolute()
David Srbecky51dec052022-10-27 14:43:12 +010057 self.mode = args.mode
58 self.jvm = (self.mode == "jvm")
59 self.host = (self.mode == "host")
60 self.target = (self.mode == "target")
61 assert self.jvm or self.host or self.target
62
David Srbecky36b520b2022-10-27 14:43:12 +010063 self.android_build_top = android_build_top.absolute()
David Srbecky6bd1cd12022-11-05 18:54:19 +000064
David Srbecky36b520b2022-10-27 14:43:12 +010065 self.java_home = Path(os.environ.get("JAVA_HOME")).absolute()
David Srbecky6bd1cd12022-11-05 18:54:19 +000066 self.java = self.java_home / "bin/java"
67 self.javac = self.java_home / "bin/javac"
David Srbecky51dec052022-10-27 14:43:12 +010068 self.javac_args = "-g -Xlint:-options -source 1.8 -target 1.8"
David Srbecky6bd1cd12022-11-05 18:54:19 +000069
70 self.bootclasspath = args.bootclasspath.absolute()
71 self.d8 = args.d8.absolute()
72 self.hiddenapi = args.hiddenapi.absolute()
73 self.jasmin = args.jasmin.absolute()
David Srbecky6bd1cd12022-11-05 18:54:19 +000074 self.smali = args.smali.absolute()
75 self.soong_zip = args.soong_zip.absolute()
76 self.zipalign = args.zipalign.absolute()
David Srbecky51dec052022-10-27 14:43:12 +010077
78 # Minimal environment needed for bash commands that we execute.
79 self.bash_env = {
80 "ANDROID_BUILD_TOP": self.android_build_top,
81 "D8": self.d8,
82 "JAVA": self.java,
83 "JAVAC": self.javac,
84 "JAVAC_ARGS": self.javac_args,
David Srbecky6bd1cd12022-11-05 18:54:19 +000085 "JAVA_HOME": self.java_home,
David Srbecky51dec052022-10-27 14:43:12 +010086 "PATH": os.environ["PATH"],
87 "PYTHONDONTWRITEBYTECODE": "1",
88 "SMALI": self.smali,
89 "SOONG_ZIP": self.soong_zip,
90 "TEST_NAME": self.test_name,
91 }
92
93 def bash(self, cmd):
David Srbecky36b520b2022-10-27 14:43:12 +010094 return subprocess.run(cmd,
95 shell=True,
96 cwd=self.test_dir,
97 env=self.bash_env,
98 check=True)
David Srbecky51dec052022-10-27 14:43:12 +010099
100 def default_build(self, **kwargs):
101 globals()['default_build'](self, **kwargs)
102
David Srbecky8106b382022-04-20 13:37:15 +0100103def rm(*patterns):
104 for pattern in patterns:
105 for path in glob.glob(pattern):
106 if os.path.isdir(path):
107 shutil.rmtree(path)
108 else:
109 os.remove(path)
110
David Srbeckycf57dee2022-10-14 17:36:51 +0100111def default_build(
112 ctx,
David Srbecky8106b382022-04-20 13:37:15 +0100113 use_desugar=True,
114 use_hiddenapi=True,
115 need_dex=None,
116 experimental="no-experiment",
117 zip_compression_method="deflate",
118 zip_align_bytes=None,
119 api_level=None,
120 javac_args=[],
121 d8_flags=[],
122 smali_args=[],
David Srbecky100fe672022-11-14 11:32:27 +0000123 use_smali=True,
124 use_jasmin=True,
David Srbecky8106b382022-04-20 13:37:15 +0100125 ):
126
David Srbecky36b520b2022-10-27 14:43:12 +0100127 # Wrap "pathlib.Path" with our own version that ensures all paths are absolute.
128 # Plain filenames are assumed to be relative to ctx.test_dir and made absolute.
129 class Path(pathlib.Path):
130 def __new__(cls, filename: str):
131 path = pathlib.Path(filename)
132 return path if path.is_absolute() else (ctx.test_dir / path)
133
David Srbecky51dec052022-10-27 14:43:12 +0100134 ANDROID_BUILD_TOP = ctx.android_build_top
David Srbecky51dec052022-10-27 14:43:12 +0100135 TEST_NAME = ctx.test_name
David Srbecky100fe672022-11-14 11:32:27 +0000136 need_dex = (ctx.host or ctx.target) if need_dex is None else need_dex
David Srbecky8106b382022-04-20 13:37:15 +0100137
David Srbecky89010a32022-07-14 16:47:30 +0000138 RBE_exec_root = os.environ.get("RBE_exec_root")
David Srbecky6bd1cd12022-11-05 18:54:19 +0000139 RBE_rewrapper = ctx.android_build_top / "prebuilts/remoteexecution-client/live/rewrapper"
David Srbecky89010a32022-07-14 16:47:30 +0000140
David Srbecky8106b382022-04-20 13:37:15 +0100141 # Set default values for directories.
David Srbecky36b520b2022-10-27 14:43:12 +0100142 HAS_SRC = Path("src").exists()
143 HAS_SRC_ART = Path("src-art").exists()
144 HAS_SRC2 = Path("src2").exists()
145 HAS_SRC_MULTIDEX = Path("src-multidex").exists()
146 HAS_SMALI_MULTIDEX = Path("smali-multidex").exists()
147 HAS_JASMIN_MULTIDEX = Path("jasmin-multidex").exists()
148 HAS_SMALI_EX = Path("smali-ex").exists()
149 HAS_SRC_EX = Path("src-ex").exists()
150 HAS_SRC_EX2 = Path("src-ex2").exists()
151 HAS_SRC_AOTEX = Path("src-aotex").exists()
152 HAS_SRC_BCPEX = Path("src-bcpex").exists()
153 HAS_HIDDENAPI_SPEC = Path("hiddenapi-flags.csv").exists()
David Srbecky8106b382022-04-20 13:37:15 +0100154
David Srbecky51dec052022-10-27 14:43:12 +0100155 JAVAC_ARGS = shlex.split(ctx.javac_args) + javac_args
David Srbecky6bd1cd12022-11-05 18:54:19 +0000156 SMALI_ARGS = smali_args.copy()
157 D8_FLAGS = d8_flags.copy()
David Srbecky8106b382022-04-20 13:37:15 +0100158
David Srbecky51dec052022-10-27 14:43:12 +0100159 BUILD_MODE = ctx.mode
David Srbecky8106b382022-04-20 13:37:15 +0100160
161 # Setup experimental API level mappings in a bash associative array.
162 EXPERIMENTAL_API_LEVEL = {}
163 EXPERIMENTAL_API_LEVEL["no-experiment"] = "26"
164 EXPERIMENTAL_API_LEVEL["default-methods"] = "24"
165 EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25"
166 EXPERIMENTAL_API_LEVEL["agents"] = "26"
167 EXPERIMENTAL_API_LEVEL["method-handles"] = "26"
168 EXPERIMENTAL_API_LEVEL["var-handles"] = "28"
169
170 if BUILD_MODE == "jvm":
171 # No desugaring on jvm because it supports the latest functionality.
172 use_desugar = False
173 # Do not attempt to build src-art directories on jvm,
174 # since it would fail without libcore.
175 HAS_SRC_ART = False
176
177 # Set API level for smali and d8.
178 if not api_level:
179 api_level = EXPERIMENTAL_API_LEVEL[experimental]
180
181 # Add API level arguments to smali and dx
182 SMALI_ARGS.extend(["--api", str(api_level)])
183 D8_FLAGS.extend(["--min-api", str(api_level)])
184
David Srbecky36b520b2022-10-27 14:43:12 +0100185 def run(executable: pathlib.Path, args: List[str]):
186 assert isinstance(executable, pathlib.Path), executable
187 cmd: List[Union[pathlib.Path, str]] = []
David Srbecky6bd1cd12022-11-05 18:54:19 +0000188 if executable.suffix == ".sh":
189 cmd += ["/bin/bash"]
190 cmd += [executable]
191 cmd += args
David Srbecky51dec052022-10-27 14:43:12 +0100192 env = ctx.bash_env
193 env.update({k: v for k, v in os.environ.items() if k.startswith("RBE_")})
David Srbecky100fe672022-11-14 11:32:27 +0000194 # Make paths relative as otherwise we could create too long command line.
David Srbecky36b520b2022-10-27 14:43:12 +0100195 for i, arg in enumerate(cmd):
196 if isinstance(arg, pathlib.Path):
197 assert arg.absolute(), arg
198 cmd[i] = relpath(arg, ctx.test_dir)
David Srbecky100fe672022-11-14 11:32:27 +0000199 elif isinstance(arg, list):
200 assert all(p.absolute() for p in arg), arg
201 cmd[i] = ":".join(relpath(p, ctx.test_dir) for p in arg)
202 else:
203 assert isinstance(arg, str), arg
David Srbecky8106b382022-04-20 13:37:15 +0100204 p = subprocess.run(cmd,
David Srbecky6bd1cd12022-11-05 18:54:19 +0000205 encoding=sys.stdout.encoding,
David Srbecky36b520b2022-10-27 14:43:12 +0100206 cwd=ctx.test_dir,
David Srbecky51dec052022-10-27 14:43:12 +0100207 env=ctx.bash_env,
David Srbecky8106b382022-04-20 13:37:15 +0100208 stderr=subprocess.STDOUT,
209 stdout=subprocess.PIPE)
210 if p.returncode != 0:
211 raise Exception("Command failed with exit code {}\n$ {}\n{}".format(
David Srbecky6bd1cd12022-11-05 18:54:19 +0000212 p.returncode, " ".join(map(str, cmd)), p.stdout))
David Srbeckye0c3cd82022-08-24 14:56:24 +0100213 return p
David Srbecky8106b382022-04-20 13:37:15 +0100214
215
216 # Helper functions to execute tools.
David Srbecky51dec052022-10-27 14:43:12 +0100217 soong_zip = functools.partial(run, ctx.soong_zip)
218 zipalign = functools.partial(run, ctx.zipalign)
219 javac = functools.partial(run, ctx.javac)
220 jasmin = functools.partial(run, ctx.jasmin)
221 smali = functools.partial(run, ctx.smali)
222 d8 = functools.partial(run, ctx.d8)
223 hiddenapi = functools.partial(run, ctx.hiddenapi)
David Srbecky8106b382022-04-20 13:37:15 +0100224
David Srbecky89010a32022-07-14 16:47:30 +0000225 if "RBE_server_address" in os.environ:
David Srbeckye0c3cd82022-08-24 14:56:24 +0100226 version = match(r"Version: (\d*)\.(\d*)\.(\d*)", run(RBE_rewrapper, ["--version"]).stdout)
227 assert version, "Could not parse RBE version"
228 assert tuple(map(int, version.groups())) >= (0, 76, 0), "Please update " + RBE_rewrapper
229
David Srbecky36b520b2022-10-27 14:43:12 +0100230 def rbe_wrap(args, inputs: Set[pathlib.Path]=None):
231 with NamedTemporaryFile(mode="w+t") as input_list:
232 inputs = inputs or set()
David Srbecky36b520b2022-10-27 14:43:12 +0100233 for i, arg in enumerate(args):
David Srbecky100fe672022-11-14 11:32:27 +0000234 if isinstance(arg, pathlib.Path):
235 assert arg.absolute(), arg
236 inputs.add(arg)
237 elif isinstance(arg, list):
238 assert all(p.absolute() for p in arg), arg
239 inputs.update(arg)
David Srbecky36b520b2022-10-27 14:43:12 +0100240 input_list.writelines([relpath(i, RBE_exec_root)+"\n" for i in inputs])
David Srbecky89010a32022-07-14 16:47:30 +0000241 input_list.flush()
242 return run(RBE_rewrapper, [
243 "--platform=" + os.environ["RBE_platform"],
244 "--input_list_paths=" + input_list.name,
245 ] + args)
246
247 if USE_RBE_FOR_JAVAC > (hash(TEST_NAME) % 100): # Use for given percentage of tests.
248 def javac(args):
David Srbecky36b520b2022-10-27 14:43:12 +0100249 output = relpath(Path(args[args.index("-d") + 1]), RBE_exec_root)
250 return rbe_wrap(["--output_directories", output, ctx.javac] + args)
David Srbecky89010a32022-07-14 16:47:30 +0000251
252 if USE_RBE_FOR_D8 > (hash(TEST_NAME) % 100): # Use for given percentage of tests.
253 def d8(args):
David Srbecky6bd1cd12022-11-05 18:54:19 +0000254 inputs = set([ctx.d8.parent.parent / "framework/d8.jar"])
David Srbecky36b520b2022-10-27 14:43:12 +0100255 output = relpath(Path(args[args.index("--output") + 1]), RBE_exec_root)
David Srbecky89010a32022-07-14 16:47:30 +0000256 return rbe_wrap([
257 "--output_files" if output.endswith(".jar") else "--output_directories", output,
David Srbeckye0c3cd82022-08-24 14:56:24 +0100258 "--toolchain_inputs=prebuilts/jdk/jdk11/linux-x86/bin/java",
David Srbecky36b520b2022-10-27 14:43:12 +0100259 ctx.d8] + args, inputs)
David Srbecky89010a32022-07-14 16:47:30 +0000260
David Srbecky8106b382022-04-20 13:37:15 +0100261 # If wrapper script exists, use it instead of the default javac.
David Srbecky36b520b2022-10-27 14:43:12 +0100262 javac_wrapper = Path("javac_wrapper.sh")
David Srbecky6bd1cd12022-11-05 18:54:19 +0000263 if javac_wrapper.exists():
264 javac = functools.partial(run, javac_wrapper)
David Srbecky8106b382022-04-20 13:37:15 +0100265
David Srbecky36b520b2022-10-27 14:43:12 +0100266 def zip(zip_target: Path, *files: Path):
267 zip_args = ["-o", zip_target, "-C", zip_target.parent]
David Srbecky8106b382022-04-20 13:37:15 +0100268 if zip_compression_method == "store":
269 zip_args.extend(["-L", "0"])
270 for f in files:
271 zip_args.extend(["-f", f])
272 soong_zip(zip_args)
273
274 if zip_align_bytes:
275 # zipalign does not operate in-place, so write results to a temp file.
David Srbecky36b520b2022-10-27 14:43:12 +0100276 with TemporaryDirectory() as tmp_dir:
277 tmp_file = Path(tmp_dir) / "aligned.zip"
David Srbecky8106b382022-04-20 13:37:15 +0100278 zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file])
279 # replace original zip target with our temp file.
David Srbecky36b520b2022-10-27 14:43:12 +0100280 tmp_file.rename(zip_target)
David Srbecky8106b382022-04-20 13:37:15 +0100281
282
David Srbecky100fe672022-11-14 11:32:27 +0000283 def make_jasmin(dst_dir: Path, src_dir: Path) -> Optional[Path]:
284 if not use_jasmin or not src_dir.exists():
285 return None # No sources to compile.
286 dst_dir.mkdir()
287 jasmin(["-d", dst_dir] + sorted(src_dir.glob("**/*.j")))
288 return dst_dir
289
290 def make_smali(dst_dex: Path, src_dir: Path) -> Optional[Path]:
291 if not use_smali or not src_dir.exists():
292 return None # No sources to compile.
293 smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
294 ["--output", dst_dex] + sorted(src_dir.glob("**/*.smali")))
295 return dst_dex
David Srbecky8106b382022-04-20 13:37:15 +0100296
297
David Srbecky100fe672022-11-14 11:32:27 +0000298 java_classpath: List[Path] = []
299
300 def make_java(dst_dir: Path, *src_dirs: Path) -> Optional[Path]:
301 if not any(src_dir.exists() for src_dir in src_dirs):
302 return None # No sources to compile.
303 dst_dir.mkdir(exist_ok=True)
304 args = JAVAC_ARGS + ["-implicit:none", "-encoding", "utf8", "-d", dst_dir]
305 if not ctx.jvm:
306 args += ["-bootclasspath", ctx.bootclasspath]
307 if java_classpath:
308 args += ["-classpath", java_classpath]
309 for src_dir in src_dirs:
310 args += sorted(src_dir.glob("**/*.java"))
311 javac(args)
312 return dst_dir
David Srbecky8106b382022-04-20 13:37:15 +0100313
314
315 # Make a "dex" file given a directory of classes. This will be
316 # packaged in a jar file.
David Srbecky100fe672022-11-14 11:32:27 +0000317 def make_dex(src_dir: Path):
318 dst_jar = Path(src_dir.name + ".jar")
319 args = D8_FLAGS + ["--output", dst_jar]
320 args += ["--lib", ctx.bootclasspath] if use_desugar else ["--no-desugaring"]
321 args += sorted(src_dir.glob("**/*.class"))
322 d8(args)
David Srbecky8106b382022-04-20 13:37:15 +0100323
324 # D8 outputs to JAR files today rather than DEX files as DX used
325 # to. To compensate, we extract the DEX from d8's output to meet the
326 # expectations of make_dex callers.
David Srbecky100fe672022-11-14 11:32:27 +0000327 dst_dex = Path(src_dir.name + ".dex")
David Srbecky36b520b2022-10-27 14:43:12 +0100328 with TemporaryDirectory() as tmp_dir:
David Srbecky100fe672022-11-14 11:32:27 +0000329 zipfile.ZipFile(dst_jar, "r").extractall(tmp_dir)
330 (Path(tmp_dir) / "classes.dex").rename(dst_dex)
David Srbecky8106b382022-04-20 13:37:15 +0100331
332 # Merge all the dex files.
333 # Skip non-existing files, but at least 1 file must exist.
David Srbecky100fe672022-11-14 11:32:27 +0000334 def make_dexmerge(dst_dex: Path, *src_dexs: Path):
335 # Include destination. Skip any non-existing files.
336 srcs = [f for f in [dst_dex] + list(src_dexs) if f.exists()]
David Srbecky8106b382022-04-20 13:37:15 +0100337
338 # NB: We merge even if there is just single input.
339 # It is useful to normalize non-deterministic smali output.
David Srbecky36b520b2022-10-27 14:43:12 +0100340 tmp_dir = ctx.test_dir / "dexmerge"
341 tmp_dir.mkdir()
David Srbecky100fe672022-11-14 11:32:27 +0000342 d8(["--min-api", api_level, "--output", tmp_dir] + srcs)
David Srbecky36b520b2022-10-27 14:43:12 +0100343 assert not (tmp_dir / "classes2.dex").exists()
David Srbecky100fe672022-11-14 11:32:27 +0000344 for src_file in srcs:
David Srbecky36b520b2022-10-27 14:43:12 +0100345 src_file.unlink()
David Srbecky100fe672022-11-14 11:32:27 +0000346 (tmp_dir / "classes.dex").rename(dst_dex)
David Srbecky36b520b2022-10-27 14:43:12 +0100347 tmp_dir.rmdir()
David Srbecky8106b382022-04-20 13:37:15 +0100348
349
David Srbecky36b520b2022-10-27 14:43:12 +0100350 def make_hiddenapi(*dex_files: Path):
351 args: List[Union[str, Path]] = ["encode"]
David Srbecky8106b382022-04-20 13:37:15 +0100352 for dex_file in dex_files:
David Srbecky36b520b2022-10-27 14:43:12 +0100353 args.extend(["--input-dex=" + str(dex_file), "--output-dex=" + str(dex_file)])
David Srbecky8106b382022-04-20 13:37:15 +0100354 args.append("--api-flags=hiddenapi-flags.csv")
355 args.append("--no-force-assign-all")
356 hiddenapi(args)
357
358
David Srbecky36b520b2022-10-27 14:43:12 +0100359 if Path("classes.dex").exists():
360 zip(Path(TEST_NAME + ".jar"), Path("classes.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100361 return
362
David Srbecky36b520b2022-10-27 14:43:12 +0100363 if Path("classes.dm").exists():
364 zip(Path(TEST_NAME + ".jar"), Path("classes.dm"))
Nicolas Geoffray3b7475b2022-11-09 15:33:12 +0000365 return
366
David Srbecky8106b382022-04-20 13:37:15 +0100367
368 def has_multidex():
369 return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX
370
371
David Srbecky100fe672022-11-14 11:32:27 +0000372 if make_jasmin(Path("jasmin_classes"), Path("jasmin")):
373 java_classpath.append(Path("jasmin_classes"))
David Srbecky8106b382022-04-20 13:37:15 +0100374
David Srbecky100fe672022-11-14 11:32:27 +0000375 if make_jasmin(Path("jasmin_classes2"), Path("jasmin-multidex")):
376 java_classpath.append(Path("jasmin_classes2"))
David Srbecky8106b382022-04-20 13:37:15 +0100377
378 if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or
379 HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2):
380 # To allow circular references, compile src/, src-multidex/, src-aotex/,
381 # src-bcpex/, src-ex/ together and pass the output as class path argument.
382 # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
383 # used by the other src-* sources we compile here but everything needed to
384 # compile the other src-* sources should be present in src/ (and jasmin*/).
David Srbecky100fe672022-11-14 11:32:27 +0000385 make_java(Path("classes-tmp-all"),
386 Path("src"),
387 Path("src-multidex"),
388 Path("src-aotex"),
389 Path("src-bcpex"),
390 Path("src-ex"))
391 java_classpath.append(Path("classes-tmp-all"))
David Srbecky8106b382022-04-20 13:37:15 +0100392
David Srbecky100fe672022-11-14 11:32:27 +0000393 if make_java(Path("classes-aotex"), Path("src-aotex")) and need_dex:
394 make_dex(Path("classes-aotex"))
395 # rename it so it shows up as "classes.dex" in the zip file.
396 Path("classes-aotex.dex").rename(Path("classes.dex"))
397 zip(Path(TEST_NAME + "-aotex.jar"), Path("classes.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100398
David Srbecky100fe672022-11-14 11:32:27 +0000399 if make_java(Path("classes-bcpex"), Path("src-bcpex")) and need_dex:
400 make_dex(Path("classes-bcpex"))
401 # rename it so it shows up as "classes.dex" in the zip file.
402 Path("classes-bcpex.dex").rename(Path("classes.dex"))
403 zip(Path(TEST_NAME + "-bcpex.jar"), Path("classes.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100404
David Srbecky100fe672022-11-14 11:32:27 +0000405 make_java(Path("classes"), Path("src"))
David Srbecky8106b382022-04-20 13:37:15 +0100406
David Srbecky100fe672022-11-14 11:32:27 +0000407 if not ctx.jvm:
408 # Do not attempt to build src-art directories on jvm,
409 # since it would fail without libcore.
410 make_java(Path("classes"), Path("src-art"))
David Srbecky8106b382022-04-20 13:37:15 +0100411
David Srbecky100fe672022-11-14 11:32:27 +0000412 if make_java(Path("classes2"), Path("src-multidex")) and need_dex:
413 make_dex(Path("classes2"))
David Srbecky8106b382022-04-20 13:37:15 +0100414
David Srbecky100fe672022-11-14 11:32:27 +0000415 make_java(Path("classes"), Path("src2"))
David Srbecky8106b382022-04-20 13:37:15 +0100416
417 # If the classes directory is not-empty, package classes in a DEX file.
418 # NB: some tests provide classes rather than java files.
David Srbecky100fe672022-11-14 11:32:27 +0000419 if any(Path("classes").glob("*")) and need_dex:
420 make_dex(Path("classes"))
David Srbecky8106b382022-04-20 13:37:15 +0100421
David Srbecky100fe672022-11-14 11:32:27 +0000422 if Path("jasmin_classes").exists():
David Srbecky8106b382022-04-20 13:37:15 +0100423 # Compile Jasmin classes as if they were part of the classes.dex file.
David Srbecky100fe672022-11-14 11:32:27 +0000424 if need_dex:
425 make_dex(Path("jasmin_classes"))
David Srbecky36b520b2022-10-27 14:43:12 +0100426 make_dexmerge(Path("classes.dex"), Path("jasmin_classes.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100427 else:
428 # Move jasmin classes into classes directory so that they are picked up
429 # with -cp classes.
David Srbecky36b520b2022-10-27 14:43:12 +0100430 Path("classes").mkdir(exist_ok=True)
431 copytree(Path("jasmin_classes"), Path("classes"), dirs_exist_ok=True)
David Srbecky8106b382022-04-20 13:37:15 +0100432
David Srbecky100fe672022-11-14 11:32:27 +0000433 if need_dex and make_smali(Path("smali_classes.dex"), Path("smali")):
David Srbecky8106b382022-04-20 13:37:15 +0100434 # Merge smali files into classes.dex,
435 # this takes priority over any jasmin files.
David Srbecky36b520b2022-10-27 14:43:12 +0100436 make_dexmerge(Path("classes.dex"), Path("smali_classes.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100437
438 # Compile Jasmin classes in jasmin-multidex as if they were part of
439 # the classes2.jar
440 if HAS_JASMIN_MULTIDEX:
David Srbecky100fe672022-11-14 11:32:27 +0000441 if need_dex:
442 make_dex(Path("jasmin_classes2"))
David Srbecky36b520b2022-10-27 14:43:12 +0100443 make_dexmerge(Path("classes2.dex"), Path("jasmin_classes2.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100444 else:
445 # Move jasmin classes into classes2 directory so that
446 # they are picked up with -cp classes2.
David Srbecky36b520b2022-10-27 14:43:12 +0100447 Path("classes2").mkdir()
448 copytree(Path("jasmin_classes2"), Path("classes2"), dirs_exist_ok=True)
449 rmtree(Path("jasmin_classes2"))
David Srbecky8106b382022-04-20 13:37:15 +0100450
David Srbecky100fe672022-11-14 11:32:27 +0000451 if need_dex and make_smali(Path("smali_classes2.dex"), Path("smali-multidex")):
David Srbecky8106b382022-04-20 13:37:15 +0100452 # Merge smali_classes2.dex into classes2.dex
David Srbecky36b520b2022-10-27 14:43:12 +0100453 make_dexmerge(Path("classes2.dex"), Path("smali_classes2.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100454
David Srbecky100fe672022-11-14 11:32:27 +0000455 make_java(Path("classes-ex"), Path("src-ex"))
David Srbecky8106b382022-04-20 13:37:15 +0100456
David Srbecky100fe672022-11-14 11:32:27 +0000457 make_java(Path("classes-ex"), Path("src-ex2"))
David Srbecky8106b382022-04-20 13:37:15 +0100458
David Srbecky100fe672022-11-14 11:32:27 +0000459 if Path("classes-ex").exists() and need_dex:
460 make_dex(Path("classes-ex"))
David Srbecky8106b382022-04-20 13:37:15 +0100461
David Srbecky100fe672022-11-14 11:32:27 +0000462 if need_dex and make_smali(Path("smali_classes-ex.dex"), Path("smali-ex")):
David Srbecky8106b382022-04-20 13:37:15 +0100463 # Merge smali files into classes-ex.dex.
David Srbecky36b520b2022-10-27 14:43:12 +0100464 make_dexmerge(Path("classes-ex.dex"), Path("smali_classes-ex.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100465
David Srbecky36b520b2022-10-27 14:43:12 +0100466 if Path("classes-ex.dex").exists():
David Srbecky8106b382022-04-20 13:37:15 +0100467 # Apply hiddenapi on the dex files if the test has API list file(s).
468 if use_hiddenapi and HAS_HIDDENAPI_SPEC:
David Srbecky36b520b2022-10-27 14:43:12 +0100469 make_hiddenapi(Path("classes-ex.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100470
471 # quick shuffle so that the stored name is "classes.dex"
David Srbecky36b520b2022-10-27 14:43:12 +0100472 Path("classes.dex").rename(Path("classes-1.dex"))
473 Path("classes-ex.dex").rename(Path("classes.dex"))
474 zip(Path(TEST_NAME + "-ex.jar"), Path("classes.dex"))
475 Path("classes.dex").rename(Path("classes-ex.dex"))
476 Path("classes-1.dex").rename(Path("classes.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100477
478 # Apply hiddenapi on the dex files if the test has API list file(s).
David Srbecky100fe672022-11-14 11:32:27 +0000479 if need_dex and use_hiddenapi and HAS_HIDDENAPI_SPEC:
David Srbecky8106b382022-04-20 13:37:15 +0100480 if has_multidex():
David Srbecky36b520b2022-10-27 14:43:12 +0100481 make_hiddenapi(Path("classes.dex"), Path("classes2.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100482 else:
David Srbecky36b520b2022-10-27 14:43:12 +0100483 make_hiddenapi(Path("classes.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100484
485 # Create a single dex jar with two dex files for multidex.
David Srbecky100fe672022-11-14 11:32:27 +0000486 if need_dex:
David Srbecky36b520b2022-10-27 14:43:12 +0100487 if Path("classes2.dex").exists():
488 zip(Path(TEST_NAME + ".jar"), Path("classes.dex"), Path("classes2.dex"))
David Srbecky8106b382022-04-20 13:37:15 +0100489 else:
David Srbecky36b520b2022-10-27 14:43:12 +0100490 zip(Path(TEST_NAME + ".jar"), Path("classes.dex"))
David Srbecky37274d52022-11-03 11:04:16 +0000491
492
493def build_test(ctx: BuildTestContext) -> None:
494 """Run the build script for single run-test"""
495
David Srbecky37274d52022-11-03 11:04:16 +0000496 script = ctx.test_dir / "build.py"
497 if script.exists():
498 module = SourceFileLoader("build_" + ctx.test_name,
499 str(script)).load_module()
500 module.build(ctx)
501 else:
502 default_build(ctx)
503
504
505# If we build just individual shard, we want to split the work among all the cores,
506# but if the build system builds all shards, we don't want to overload the machine.
507# We don't know which situation we are in, so as simple work-around, we use a lock
508# file to allow only one shard to use multiprocessing at the same time.
509def use_multiprocessing(mode: str) -> bool:
510 global lock_file
David Srbecky36b520b2022-10-27 14:43:12 +0100511 lock_path = Path(environ["TMPDIR"]) / ("art-test-run-test-build-py-" + mode)
David Srbecky37274d52022-11-03 11:04:16 +0000512 lock_file = open(lock_path, "w")
513 try:
514 lockf(lock_file, LOCK_EX | LOCK_NB)
515 return True # We are the only instance of this script in the build system.
516 except BlockingIOError:
517 return False # Some other instance is already running.
518
519
520def main() -> None:
521 parser = ArgumentParser(description=__doc__)
David Srbecky6bd1cd12022-11-05 18:54:19 +0000522 parser.add_argument("--out", type=Path, help="Final zip file")
David Srbecky37274d52022-11-03 11:04:16 +0000523 parser.add_argument("--mode", choices=["host", "jvm", "target"])
David Srbecky6bd1cd12022-11-05 18:54:19 +0000524 parser.add_argument("--bootclasspath", type=Path)
525 parser.add_argument("--d8", type=Path)
526 parser.add_argument("--hiddenapi", type=Path)
527 parser.add_argument("--jasmin", type=Path)
528 parser.add_argument("--smali", type=Path)
529 parser.add_argument("--soong_zip", type=Path)
530 parser.add_argument("--zipalign", type=Path)
531 parser.add_argument("srcs", nargs="+", type=Path)
David Srbecky37274d52022-11-03 11:04:16 +0000532 args = parser.parse_args()
533
David Srbecky36b520b2022-10-27 14:43:12 +0100534 android_build_top = Path(getcwd()).absolute()
535 ziproot = args.out.absolute().parent / "zip"
536 srcdirs = set(s.parents[-4].absolute() for s in args.srcs)
David Srbecky37274d52022-11-03 11:04:16 +0000537
David Srbecky36b520b2022-10-27 14:43:12 +0100538 # Initialize the test objects.
539 # We need to do this before we change the working directory below.
540 tests: List[BuildTestContext] = []
541 for srcdir in srcdirs:
542 dstdir = ziproot / args.mode / srcdir.name
543 copytree(srcdir, dstdir)
544 tests.append(BuildTestContext(args, android_build_top, dstdir))
545
546 # We can not change the working directory per each thread since they all run in parallel.
547 # Create invalid read-only directory to catch accidental use of current working directory.
548 with TemporaryDirectory("-do-not-use-cwd") as invalid_tmpdir:
549 os.chdir(invalid_tmpdir)
550 os.chmod(invalid_tmpdir, 0)
551 with ThreadPoolExecutor(cpu_count() if use_multiprocessing(args.mode) else 1) as pool:
552 jobs = {}
553 for ctx in tests:
554 jobs[ctx.test_name] = pool.submit(build_test, ctx)
555 for test_name, job in jobs.items():
556 try:
557 job.result()
558 except Exception as e:
559 raise Exception("Failed to build " + test_name) from e
David Srbecky37274d52022-11-03 11:04:16 +0000560
561 # Create the final zip file which contains the content of the temporary directory.
David Srbecky36b520b2022-10-27 14:43:12 +0100562 proc = run([android_build_top / args.soong_zip, "-o", android_build_top / args.out,
563 "-C", ziproot, "-D", ziproot], check=True)
David Srbecky37274d52022-11-03 11:04:16 +0000564
565
566if __name__ == "__main__":
567 main()