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