• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
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
17"""This is the default build script for run-tests.
18
19It can be overwrite by specific run-tests if needed.
20It is used from soong build and not intended to be called directly.
21"""
22
23import argparse
24import functools
25import glob
26import os
27from os import path
28import shlex
29import shutil
30import subprocess
31import tempfile
32import zipfile
33
34if not os.sys.argv:
35  print(
36      'Error: default-build should have the parameters from the "build" script forwarded to it'
37  )
38  print('Error: An example of how do it correctly is ./default-build "$@"')
39  os.sys.exit(1)
40
41
42def parse_bool(text):
43  return {"true": True, "false": False}[text.lower()]
44
45
46TEST_NAME = os.environ["TEST_NAME"]
47ART_TEST_RUN_TEST_BOOTCLASSPATH = os.environ["ART_TEST_RUN_TEST_BOOTCLASSPATH"]
48NEED_DEX = parse_bool(os.environ["NEED_DEX"])
49
50# Set default values for directories.
51HAS_SMALI = path.exists("smali")
52HAS_JASMIN = path.exists("jasmin")
53HAS_SRC = path.exists("src")
54HAS_SRC_ART = path.exists("src-art")
55HAS_SRC2 = path.exists("src2")
56HAS_SRC_MULTIDEX = path.exists("src-multidex")
57HAS_SMALI_MULTIDEX = path.exists("smali-multidex")
58HAS_JASMIN_MULTIDEX = path.exists("jasmin-multidex")
59HAS_SMALI_EX = path.exists("smali-ex")
60HAS_SRC_EX = path.exists("src-ex")
61HAS_SRC_EX2 = path.exists("src-ex2")
62HAS_SRC_AOTEX = path.exists("src-aotex")
63HAS_SRC_BCPEX = path.exists("src-bcpex")
64HAS_HIDDENAPI_SPEC = path.exists("hiddenapi-flags.csv")
65
66# USE_HIDDENAPI=false run-test... will disable hiddenapi.
67USE_HIDDENAPI = parse_bool(os.environ.get("USE_HIDDENAPI", "true"))
68
69# USE_DESUGAR=false run-test... will disable desugaring.
70USE_DESUGAR = parse_bool(os.environ.get("USE_DESUGAR", "true"))
71
72JAVAC_ARGS = shlex.split(os.environ.get("JAVAC_ARGS", ""))
73SMALI_ARGS = shlex.split(os.environ.get("SMALI_ARGS", ""))
74D8_FLAGS = shlex.split(os.environ.get("D8_FLAGS", ""))
75
76# Allow overriding ZIP_COMPRESSION_METHOD with e.g. 'store'
77ZIP_COMPRESSION_METHOD = "deflate"
78# Align every ZIP file made by calling $ZIPALIGN command?
79ZIP_ALIGN_BYTES = None
80
81DEV_MODE = False
82BUILD_MODE = "target"
83API_LEVEL = None
84DEFAULT_EXPERIMENT = "no-experiment"
85EXPERIMENTAL = DEFAULT_EXPERIMENT
86
87# Setup experimental API level mappings in a bash associative array.
88EXPERIMENTAL_API_LEVEL = {}
89EXPERIMENTAL_API_LEVEL[DEFAULT_EXPERIMENT] = "26"
90EXPERIMENTAL_API_LEVEL["default-methods"] = "24"
91EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25"
92EXPERIMENTAL_API_LEVEL["agents"] = "26"
93EXPERIMENTAL_API_LEVEL["method-handles"] = "26"
94EXPERIMENTAL_API_LEVEL["var-handles"] = "28"
95
96# Parse command line arguments.
97opt_bool = argparse.BooleanOptionalAction  # Bool also accepts the --no- prefix.
98parser = argparse.ArgumentParser(description=__doc__)
99parser.add_argument("--src", dest="HAS_SRC", action=opt_bool)
100parser.add_argument("--src2", dest="HAS_SRC2", action=opt_bool)
101parser.add_argument("--src-multidex", dest="HAS_SRC_MULTIDEX", action=opt_bool)
102parser.add_argument(
103    "--smali-multidex", dest="HAS_SMALI_MULTIDEX", action=opt_bool)
104parser.add_argument("--src-ex", dest="HAS_SRC_EX", action=opt_bool)
105parser.add_argument("--src-ex2", dest="HAS_SRC_EX2", action=opt_bool)
106parser.add_argument("--smali", dest="HAS_SMALI", action=opt_bool)
107parser.add_argument("--jasmin", dest="HAS_JASMIN", action=opt_bool)
108parser.add_argument("--api-level", dest="API_LEVEL", type=int)
109parser.add_argument(
110    "--experimental", dest="EXPERIMENTAL", type=str)
111parser.add_argument(
112    "--zip-compression-method", dest="ZIP_COMPRESSION_METHOD", type=str)
113parser.add_argument("--zip-align", dest="ZIP_ALIGN_BYTES", type=int)
114parser.add_argument(
115    "--host", dest="BUILD_MODE", action="store_const", const="host")
116parser.add_argument(
117    "--target", dest="BUILD_MODE", action="store_const", const="target")
118parser.add_argument(
119    "--jvm", dest="BUILD_MODE", action="store_const", const="jvm")
120parser.add_argument("--dev", dest="DEV_MODE", action=opt_bool)
121# Update variables with command line arguments that were set.
122globals().update(
123    {k: v for k, v in parser.parse_args().__dict__.items() if v is not None})
124
125if BUILD_MODE == "jvm":
126  # No desugaring on jvm because it supports the latest functionality.
127  USE_DESUGAR = False
128  # Do not attempt to build src-art directories on jvm,
129  # since it would fail without libcore.
130  HAS_SRC_ART = False
131
132# Set API level for smali and d8.
133if not API_LEVEL:
134  API_LEVEL = EXPERIMENTAL_API_LEVEL[EXPERIMENTAL]
135
136# Add API level arguments to smali and dx
137SMALI_ARGS.extend(["--api", str(API_LEVEL)])
138D8_FLAGS.extend(["--min-api", str(API_LEVEL)])
139
140
141def run(executable, args):
142  cmd = shlex.split(executable) + args
143  if executable.endswith(".sh"):
144    cmd = ["/bin/bash"] + cmd
145  if DEV_MODE:
146    print("Run:", " ".join(cmd))
147  p = subprocess.run(cmd, check=True)
148  if p.returncode != 0:
149    raise Exception("Failed command: " + " ".join(cmd))
150
151
152# Helper functions to execute tools.
153soong_zip = functools.partial(run, os.environ["SOONG_ZIP"])
154zipalign = functools.partial(run, os.environ["ZIPALIGN"])
155javac = functools.partial(run, os.environ["JAVAC"])
156jasmin = functools.partial(run, os.environ["JASMIN"])
157smali = functools.partial(run, os.environ["SMALI"])
158d8 = functools.partial(run, os.environ["D8"])
159hiddenapi = functools.partial(run, os.environ["HIDDENAPI"])
160
161# If wrapper script exists, use it instead of the default javac.
162if os.path.exists("javac_wrapper.sh"):
163  javac = functools.partial(run, "javac_wrapper.sh")
164
165def find(root, name):
166  return sorted(glob.glob(path.join(root, "**", name), recursive=True))
167
168
169def zip(zip_target, *files):
170  zip_args = ["-o", zip_target]
171  if ZIP_COMPRESSION_METHOD == "store":
172    zip_args.extend(["-L", "0"])
173  for f in files:
174    zip_args.extend(["-f", f])
175  soong_zip(zip_args)
176
177  if ZIP_ALIGN_BYTES:
178    # zipalign does not operate in-place, so write results to a temp file.
179    with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
180      tmp_file = path.join(tmp_dir, "aligned.zip")
181      zipalign(["-f", str(ZIP_ALIGN_BYTES), zip_target, tmp_file])
182      # replace original zip target with our temp file.
183      os.rename(tmp_file, zip_target)
184
185
186def make_jasmin(out_directory, jasmin_sources):
187  os.makedirs(out_directory, exist_ok=True)
188  jasmin(["-d", out_directory] + sorted(jasmin_sources))
189
190
191# Like regular javac but may include libcore on the bootclasspath.
192def javac_with_bootclasspath(args):
193  flags = JAVAC_ARGS + ["-encoding", "utf8"]
194  if BUILD_MODE != "jvm":
195    flags.extend(["-bootclasspath", ART_TEST_RUN_TEST_BOOTCLASSPATH])
196  javac(flags + args)
197
198
199# Make a "dex" file given a directory of classes. This will be
200# packaged in a jar file.
201def make_dex(name):
202  d8_inputs = find(name, "*.class")
203  d8_output = name + ".jar"
204  dex_output = name + ".dex"
205  if USE_DESUGAR:
206    flags = ["--lib", ART_TEST_RUN_TEST_BOOTCLASSPATH]
207  else:
208    flags = ["--no-desugaring"]
209  assert d8_inputs
210  d8(D8_FLAGS + flags + ["--output", d8_output] + d8_inputs)
211
212  # D8 outputs to JAR files today rather than DEX files as DX used
213  # to. To compensate, we extract the DEX from d8's output to meet the
214  # expectations of make_dex callers.
215  with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
216    zipfile.ZipFile(d8_output, "r").extractall(tmp_dir)
217    os.rename(path.join(tmp_dir, "classes.dex"), dex_output)
218
219
220# Merge all the dex files.
221# Skip non-existing files, but at least 1 file must exist.
222def make_dexmerge(*dex_files_to_merge):
223  # Dex file that acts as the destination.
224  dst_file = dex_files_to_merge[0]
225
226  # Skip any non-existing files.
227  dex_files_to_merge = list(filter(path.exists, dex_files_to_merge))
228
229  # NB: We merge even if there is just single input.
230  # It is useful to normalize non-deterministic smali output.
231
232  with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
233    d8(["--min-api", API_LEVEL, "--output", tmp_dir] + dex_files_to_merge)
234    assert not path.exists(path.join(tmp_dir, "classes2.dex"))
235    for input_dex in dex_files_to_merge:
236      os.remove(input_dex)
237    os.rename(path.join(tmp_dir, "classes.dex"), dst_file)
238
239
240def make_hiddenapi(*dex_files):
241  args = ["encode"]
242  for dex_file in dex_files:
243    args.extend(["--input-dex=" + dex_file, "--output-dex=" + dex_file])
244  args.append("--api-flags=hiddenapi-flags.csv")
245  args.append("--no-force-assign-all")
246  hiddenapi(args)
247
248
249if path.exists("classes.dex"):
250  zip(TEST_NAME + ".jar", "classes.dex")
251  os.sys.exit(0)
252
253
254def has_multidex():
255  return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX
256
257
258def add_to_cp_args(old_cp_args, path):
259  if len(old_cp_args) == 0:
260    return ["-cp", path]
261  else:
262    return ["-cp", old_cp_args[1] + ":" + path]
263
264
265src_tmp_all = []
266
267if HAS_JASMIN:
268  make_jasmin("jasmin_classes", find("jasmin", "*.j"))
269  src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes")
270
271if HAS_JASMIN_MULTIDEX:
272  make_jasmin("jasmin_classes2", find("jasmin-multidex", "*.j"))
273  src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes2")
274
275if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or
276                HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2):
277  # To allow circular references, compile src/, src-multidex/, src-aotex/,
278  # src-bcpex/, src-ex/ together and pass the output as class path argument.
279  # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
280  # used by the other src-* sources we compile here but everything needed to
281  # compile the other src-* sources should be present in src/ (and jasmin*/).
282  os.makedirs("classes-tmp-all")
283  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
284                           ["-d", "classes-tmp-all"] +
285                           find("src", "*.java") +
286                           find("src-multidex", "*.java") +
287                           find("src-aotex", "*.java") +
288                           find("src-bcpex", "*.java") +
289                           find("src-ex", "*.java"))
290  src_tmp_all = add_to_cp_args(src_tmp_all, "classes-tmp-all")
291
292if HAS_SRC_AOTEX:
293  os.makedirs("classes-aotex")
294  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
295                           ["-d", "classes-aotex"] +
296                           find("src-aotex", "*.java"))
297  if NEED_DEX:
298    make_dex("classes-aotex")
299    # rename it so it shows up as "classes.dex" in the zip file.
300    os.rename("classes-aotex.dex", "classes.dex")
301    zip(TEST_NAME + "-aotex.jar", "classes.dex")
302
303if HAS_SRC_BCPEX:
304  os.makedirs("classes-bcpex")
305  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
306                           ["-d", "classes-bcpex"] +
307                           find("src-bcpex", "*.java"))
308  if NEED_DEX:
309    make_dex("classes-bcpex")
310    # rename it so it shows up as "classes.dex" in the zip file.
311    os.rename("classes-bcpex.dex", "classes.dex")
312    zip(TEST_NAME + "-bcpex.jar", "classes.dex")
313
314if HAS_SRC:
315  os.makedirs("classes", exist_ok=True)
316  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
317                           ["-d", "classes"] + find("src", "*.java"))
318
319if HAS_SRC_ART:
320  os.makedirs("classes", exist_ok=True)
321  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
322                           ["-d", "classes"] + find("src-art", "*.java"))
323
324if HAS_SRC_MULTIDEX:
325  os.makedirs("classes2")
326  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
327                           ["-d", "classes2"] +
328                           find("src-multidex", "*.java"))
329  if NEED_DEX:
330    make_dex("classes2")
331
332if HAS_SRC2:
333  os.makedirs("classes", exist_ok=True)
334  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
335                           ["-d", "classes"] +
336                           find("src2", "*.java"))
337
338# If the classes directory is not-empty, package classes in a DEX file.
339# NB: some tests provide classes rather than java files.
340if find("classes", "*"):
341  if NEED_DEX:
342    make_dex("classes")
343
344if HAS_JASMIN:
345  # Compile Jasmin classes as if they were part of the classes.dex file.
346  if NEED_DEX:
347    make_dex("jasmin_classes")
348    make_dexmerge("classes.dex", "jasmin_classes.dex")
349  else:
350    # Move jasmin classes into classes directory so that they are picked up
351    # with -cp classes.
352    os.makedirs("classes", exist_ok=True)
353    shutil.copytree("jasmin_classes", "classes", dirs_exist_ok=True)
354
355if HAS_SMALI and NEED_DEX:
356  # Compile Smali classes
357  smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
358        ["--output", "smali_classes.dex"] + find("smali", "*.smali"))
359  assert path.exists("smali_classes.dex")
360  # Merge smali files into classes.dex,
361  # this takes priority over any jasmin files.
362  make_dexmerge("classes.dex", "smali_classes.dex")
363
364# Compile Jasmin classes in jasmin-multidex as if they were part of
365# the classes2.jar
366if HAS_JASMIN_MULTIDEX:
367  if NEED_DEX:
368    make_dex("jasmin_classes2")
369    make_dexmerge("classes2.dex", "jasmin_classes2.dex")
370  else:
371    # Move jasmin classes into classes2 directory so that
372    # they are picked up with -cp classes2.
373    os.makedirs("classes2", exist_ok=True)
374    shutil.copytree("jasmin_classes2", "classes2", dirs_exist_ok=True)
375    shutil.rmtree("jasmin_classes2")
376
377if HAS_SMALI_MULTIDEX and NEED_DEX:
378  # Compile Smali classes
379  smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
380        ["--output", "smali_classes2.dex"] + find("smali-multidex", "*.smali"))
381
382  # Merge smali_classes2.dex into classes2.dex
383  make_dexmerge("classes2.dex", "smali_classes2.dex")
384
385if HAS_SRC_EX:
386  os.makedirs("classes-ex", exist_ok=True)
387  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
388                           ["-d", "classes-ex"] + find("src-ex", "*.java"))
389
390if HAS_SRC_EX2:
391  os.makedirs("classes-ex", exist_ok=True)
392  javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
393                           ["-d", "classes-ex"] + find("src-ex2", "*.java"))
394
395if path.exists("classes-ex") and NEED_DEX:
396  make_dex("classes-ex")
397
398if HAS_SMALI_EX and NEED_DEX:
399  # Compile Smali classes
400  smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
401        ["--output", "smali_classes-ex.dex"] + find("smali-ex", "*.smali"))
402  assert path.exists("smali_classes-ex.dex")
403  # Merge smali files into classes-ex.dex.
404  make_dexmerge("classes-ex.dex", "smali_classes-ex.dex")
405
406if path.exists("classes-ex.dex"):
407  # Apply hiddenapi on the dex files if the test has API list file(s).
408  if USE_HIDDENAPI and HAS_HIDDENAPI_SPEC:
409    make_hiddenapi("classes-ex.dex")
410
411  # quick shuffle so that the stored name is "classes.dex"
412  os.rename("classes.dex", "classes-1.dex")
413  os.rename("classes-ex.dex", "classes.dex")
414  zip(TEST_NAME + "-ex.jar", "classes.dex")
415  os.rename("classes.dex", "classes-ex.dex")
416  os.rename("classes-1.dex", "classes.dex")
417
418# Apply hiddenapi on the dex files if the test has API list file(s).
419if NEED_DEX and USE_HIDDENAPI and HAS_HIDDENAPI_SPEC:
420  if has_multidex():
421    make_hiddenapi("classes.dex", "classes2.dex")
422  else:
423    make_hiddenapi("classes.dex")
424
425# Create a single dex jar with two dex files for multidex.
426if NEED_DEX:
427  if path.exists("classes2.dex"):
428    zip(TEST_NAME + ".jar", "classes.dex", "classes2.dex")
429  else:
430    zip(TEST_NAME + ".jar", "classes.dex")
431