| #!/usr/bin/env python3 |
| # |
| # Copyright (C) 2021 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """This is the default build script for run-tests. |
| |
| It can be overwrite by specific run-tests if needed. |
| It is used from soong build and not intended to be called directly. |
| """ |
| |
| import argparse |
| import functools |
| import glob |
| import os |
| from os import path |
| import shlex |
| import shutil |
| import subprocess |
| import tempfile |
| import zipfile |
| |
| if not os.sys.argv: |
| print( |
| 'Error: default-build should have the parameters from the "build" script forwarded to it' |
| ) |
| print('Error: An example of how do it correctly is ./default-build "$@"') |
| os.sys.exit(1) |
| |
| |
| def parse_bool(text): |
| return {"true": True, "false": False}[text.lower()] |
| |
| |
| TEST_NAME = os.environ["TEST_NAME"] |
| ART_TEST_RUN_TEST_BOOTCLASSPATH = os.environ["ART_TEST_RUN_TEST_BOOTCLASSPATH"] |
| NEED_DEX = parse_bool(os.environ["NEED_DEX"]) |
| |
| # Set default values for directories. |
| HAS_SMALI = path.exists("smali") |
| HAS_JASMIN = path.exists("jasmin") |
| HAS_SRC = path.exists("src") |
| HAS_SRC_ART = path.exists("src-art") |
| HAS_SRC2 = path.exists("src2") |
| HAS_SRC_MULTIDEX = path.exists("src-multidex") |
| HAS_SMALI_MULTIDEX = path.exists("smali-multidex") |
| HAS_JASMIN_MULTIDEX = path.exists("jasmin-multidex") |
| HAS_SMALI_EX = path.exists("smali-ex") |
| HAS_SRC_EX = path.exists("src-ex") |
| HAS_SRC_EX2 = path.exists("src-ex2") |
| HAS_SRC_AOTEX = path.exists("src-aotex") |
| HAS_SRC_BCPEX = path.exists("src-bcpex") |
| HAS_HIDDENAPI_SPEC = path.exists("hiddenapi-flags.csv") |
| |
| # USE_HIDDENAPI=false run-test... will disable hiddenapi. |
| USE_HIDDENAPI = parse_bool(os.environ.get("USE_HIDDENAPI", "true")) |
| |
| # USE_DESUGAR=false run-test... will disable desugaring. |
| USE_DESUGAR = parse_bool(os.environ.get("USE_DESUGAR", "true")) |
| |
| JAVAC_ARGS = shlex.split(os.environ.get("JAVAC_ARGS", "")) |
| SMALI_ARGS = shlex.split(os.environ.get("SMALI_ARGS", "")) |
| D8_FLAGS = shlex.split(os.environ.get("D8_FLAGS", "")) |
| |
| # Allow overriding ZIP_COMPRESSION_METHOD with e.g. 'store' |
| ZIP_COMPRESSION_METHOD = "deflate" |
| # Align every ZIP file made by calling $ZIPALIGN command? |
| ZIP_ALIGN_BYTES = None |
| |
| DEV_MODE = False |
| BUILD_MODE = "target" |
| API_LEVEL = None |
| DEFAULT_EXPERIMENT = "no-experiment" |
| EXPERIMENTAL = DEFAULT_EXPERIMENT |
| |
| # Setup experimental API level mappings in a bash associative array. |
| EXPERIMENTAL_API_LEVEL = {} |
| EXPERIMENTAL_API_LEVEL[DEFAULT_EXPERIMENT] = "26" |
| EXPERIMENTAL_API_LEVEL["default-methods"] = "24" |
| EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25" |
| EXPERIMENTAL_API_LEVEL["agents"] = "26" |
| EXPERIMENTAL_API_LEVEL["method-handles"] = "26" |
| EXPERIMENTAL_API_LEVEL["var-handles"] = "28" |
| |
| # Parse command line arguments. |
| opt_bool = argparse.BooleanOptionalAction # Bool also accepts the --no- prefix. |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument("--src", dest="HAS_SRC", action=opt_bool) |
| parser.add_argument("--src2", dest="HAS_SRC2", action=opt_bool) |
| parser.add_argument("--src-multidex", dest="HAS_SRC_MULTIDEX", action=opt_bool) |
| parser.add_argument( |
| "--smali-multidex", dest="HAS_SMALI_MULTIDEX", action=opt_bool) |
| parser.add_argument("--src-ex", dest="HAS_SRC_EX", action=opt_bool) |
| parser.add_argument("--src-ex2", dest="HAS_SRC_EX2", action=opt_bool) |
| parser.add_argument("--smali", dest="HAS_SMALI", action=opt_bool) |
| parser.add_argument("--jasmin", dest="HAS_JASMIN", action=opt_bool) |
| parser.add_argument("--api-level", dest="API_LEVEL", type=int) |
| parser.add_argument( |
| "--experimental", dest="EXPERIMENTAL", type=str) |
| parser.add_argument( |
| "--zip-compression-method", dest="ZIP_COMPRESSION_METHOD", type=str) |
| parser.add_argument("--zip-align", dest="ZIP_ALIGN_BYTES", type=int) |
| parser.add_argument( |
| "--host", dest="BUILD_MODE", action="store_const", const="host") |
| parser.add_argument( |
| "--target", dest="BUILD_MODE", action="store_const", const="target") |
| parser.add_argument( |
| "--jvm", dest="BUILD_MODE", action="store_const", const="jvm") |
| parser.add_argument("--dev", dest="DEV_MODE", action=opt_bool) |
| # Update variables with command line arguments that were set. |
| globals().update( |
| {k: v for k, v in parser.parse_args().__dict__.items() if v is not None}) |
| |
| if BUILD_MODE == "jvm": |
| # No desugaring on jvm because it supports the latest functionality. |
| USE_DESUGAR = False |
| # Do not attempt to build src-art directories on jvm, |
| # since it would fail without libcore. |
| HAS_SRC_ART = False |
| |
| # Set API level for smali and d8. |
| if not API_LEVEL: |
| API_LEVEL = EXPERIMENTAL_API_LEVEL[EXPERIMENTAL] |
| |
| # Add API level arguments to smali and dx |
| SMALI_ARGS.extend(["--api", str(API_LEVEL)]) |
| D8_FLAGS.extend(["--min-api", str(API_LEVEL)]) |
| |
| |
| def run(executable, args): |
| cmd = shlex.split(executable) + args |
| if executable.endswith(".sh"): |
| cmd = ["/bin/bash"] + cmd |
| if DEV_MODE: |
| print("Run:", " ".join(cmd)) |
| p = subprocess.run(cmd, check=True) |
| if p.returncode != 0: |
| raise Exception("Failed command: " + " ".join(cmd)) |
| |
| |
| # Helper functions to execute tools. |
| soong_zip = functools.partial(run, os.environ["SOONG_ZIP"]) |
| zipalign = functools.partial(run, os.environ["ZIPALIGN"]) |
| javac = functools.partial(run, os.environ["JAVAC"]) |
| jasmin = functools.partial(run, os.environ["JASMIN"]) |
| smali = functools.partial(run, os.environ["SMALI"]) |
| d8 = functools.partial(run, os.environ["D8"]) |
| hiddenapi = functools.partial(run, os.environ["HIDDENAPI"]) |
| |
| # If wrapper script exists, use it instead of the default javac. |
| if os.path.exists("javac_wrapper.sh"): |
| javac = functools.partial(run, "javac_wrapper.sh") |
| |
| def find(root, name): |
| return sorted(glob.glob(path.join(root, "**", name), recursive=True)) |
| |
| |
| def zip(zip_target, *files): |
| zip_args = ["-o", zip_target] |
| if ZIP_COMPRESSION_METHOD == "store": |
| zip_args.extend(["-L", "0"]) |
| for f in files: |
| zip_args.extend(["-f", f]) |
| soong_zip(zip_args) |
| |
| if ZIP_ALIGN_BYTES: |
| # zipalign does not operate in-place, so write results to a temp file. |
| with tempfile.TemporaryDirectory(dir=".") as tmp_dir: |
| tmp_file = path.join(tmp_dir, "aligned.zip") |
| zipalign(["-f", str(ZIP_ALIGN_BYTES), zip_target, tmp_file]) |
| # replace original zip target with our temp file. |
| os.rename(tmp_file, zip_target) |
| |
| |
| def make_jasmin(out_directory, jasmin_sources): |
| os.makedirs(out_directory, exist_ok=True) |
| jasmin(["-d", out_directory] + sorted(jasmin_sources)) |
| |
| |
| # Like regular javac but may include libcore on the bootclasspath. |
| def javac_with_bootclasspath(args): |
| flags = JAVAC_ARGS + ["-encoding", "utf8"] |
| if BUILD_MODE != "jvm": |
| flags.extend(["-bootclasspath", ART_TEST_RUN_TEST_BOOTCLASSPATH]) |
| javac(flags + args) |
| |
| |
| # Make a "dex" file given a directory of classes. This will be |
| # packaged in a jar file. |
| def make_dex(name): |
| d8_inputs = find(name, "*.class") |
| d8_output = name + ".jar" |
| dex_output = name + ".dex" |
| if USE_DESUGAR: |
| flags = ["--lib", ART_TEST_RUN_TEST_BOOTCLASSPATH] |
| else: |
| flags = ["--no-desugaring"] |
| assert d8_inputs |
| d8(D8_FLAGS + flags + ["--output", d8_output] + d8_inputs) |
| |
| # D8 outputs to JAR files today rather than DEX files as DX used |
| # to. To compensate, we extract the DEX from d8's output to meet the |
| # expectations of make_dex callers. |
| with tempfile.TemporaryDirectory(dir=".") as tmp_dir: |
| zipfile.ZipFile(d8_output, "r").extractall(tmp_dir) |
| os.rename(path.join(tmp_dir, "classes.dex"), dex_output) |
| |
| |
| # Merge all the dex files. |
| # Skip non-existing files, but at least 1 file must exist. |
| def make_dexmerge(*dex_files_to_merge): |
| # Dex file that acts as the destination. |
| dst_file = dex_files_to_merge[0] |
| |
| # Skip any non-existing files. |
| dex_files_to_merge = list(filter(path.exists, dex_files_to_merge)) |
| |
| # NB: We merge even if there is just single input. |
| # It is useful to normalize non-deterministic smali output. |
| |
| with tempfile.TemporaryDirectory(dir=".") as tmp_dir: |
| d8(["--min-api", API_LEVEL, "--output", tmp_dir] + dex_files_to_merge) |
| assert not path.exists(path.join(tmp_dir, "classes2.dex")) |
| for input_dex in dex_files_to_merge: |
| os.remove(input_dex) |
| os.rename(path.join(tmp_dir, "classes.dex"), dst_file) |
| |
| |
| def make_hiddenapi(*dex_files): |
| args = ["encode"] |
| for dex_file in dex_files: |
| args.extend(["--input-dex=" + dex_file, "--output-dex=" + dex_file]) |
| args.append("--api-flags=hiddenapi-flags.csv") |
| args.append("--no-force-assign-all") |
| hiddenapi(args) |
| |
| |
| if path.exists("classes.dex"): |
| zip(TEST_NAME + ".jar", "classes.dex") |
| os.sys.exit(0) |
| |
| |
| def has_multidex(): |
| return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX |
| |
| |
| def add_to_cp_args(old_cp_args, path): |
| if len(old_cp_args) == 0: |
| return ["-cp", path] |
| else: |
| return ["-cp", old_cp_args[1] + ":" + path] |
| |
| |
| src_tmp_all = [] |
| |
| if HAS_JASMIN: |
| make_jasmin("jasmin_classes", find("jasmin", "*.j")) |
| src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes") |
| |
| if HAS_JASMIN_MULTIDEX: |
| make_jasmin("jasmin_classes2", find("jasmin-multidex", "*.j")) |
| src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes2") |
| |
| if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or |
| HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2): |
| # To allow circular references, compile src/, src-multidex/, src-aotex/, |
| # src-bcpex/, src-ex/ together and pass the output as class path argument. |
| # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols |
| # used by the other src-* sources we compile here but everything needed to |
| # compile the other src-* sources should be present in src/ (and jasmin*/). |
| os.makedirs("classes-tmp-all") |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes-tmp-all"] + |
| find("src", "*.java") + |
| find("src-multidex", "*.java") + |
| find("src-aotex", "*.java") + |
| find("src-bcpex", "*.java") + |
| find("src-ex", "*.java")) |
| src_tmp_all = add_to_cp_args(src_tmp_all, "classes-tmp-all") |
| |
| if HAS_SRC_AOTEX: |
| os.makedirs("classes-aotex") |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes-aotex"] + |
| find("src-aotex", "*.java")) |
| if NEED_DEX: |
| make_dex("classes-aotex") |
| # rename it so it shows up as "classes.dex" in the zip file. |
| os.rename("classes-aotex.dex", "classes.dex") |
| zip(TEST_NAME + "-aotex.jar", "classes.dex") |
| |
| if HAS_SRC_BCPEX: |
| os.makedirs("classes-bcpex") |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes-bcpex"] + |
| find("src-bcpex", "*.java")) |
| if NEED_DEX: |
| make_dex("classes-bcpex") |
| # rename it so it shows up as "classes.dex" in the zip file. |
| os.rename("classes-bcpex.dex", "classes.dex") |
| zip(TEST_NAME + "-bcpex.jar", "classes.dex") |
| |
| if HAS_SRC: |
| os.makedirs("classes", exist_ok=True) |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes"] + find("src", "*.java")) |
| |
| if HAS_SRC_ART: |
| os.makedirs("classes", exist_ok=True) |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes"] + find("src-art", "*.java")) |
| |
| if HAS_SRC_MULTIDEX: |
| os.makedirs("classes2") |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes2"] + |
| find("src-multidex", "*.java")) |
| if NEED_DEX: |
| make_dex("classes2") |
| |
| if HAS_SRC2: |
| os.makedirs("classes", exist_ok=True) |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes"] + |
| find("src2", "*.java")) |
| |
| # If the classes directory is not-empty, package classes in a DEX file. |
| # NB: some tests provide classes rather than java files. |
| if find("classes", "*"): |
| if NEED_DEX: |
| make_dex("classes") |
| |
| if HAS_JASMIN: |
| # Compile Jasmin classes as if they were part of the classes.dex file. |
| if NEED_DEX: |
| make_dex("jasmin_classes") |
| make_dexmerge("classes.dex", "jasmin_classes.dex") |
| else: |
| # Move jasmin classes into classes directory so that they are picked up |
| # with -cp classes. |
| os.makedirs("classes", exist_ok=True) |
| shutil.copytree("jasmin_classes", "classes", dirs_exist_ok=True) |
| |
| if HAS_SMALI and NEED_DEX: |
| # Compile Smali classes |
| smali(["-JXmx512m", "assemble"] + SMALI_ARGS + |
| ["--output", "smali_classes.dex"] + find("smali", "*.smali")) |
| assert path.exists("smali_classes.dex") |
| # Merge smali files into classes.dex, |
| # this takes priority over any jasmin files. |
| make_dexmerge("classes.dex", "smali_classes.dex") |
| |
| # Compile Jasmin classes in jasmin-multidex as if they were part of |
| # the classes2.jar |
| if HAS_JASMIN_MULTIDEX: |
| if NEED_DEX: |
| make_dex("jasmin_classes2") |
| make_dexmerge("classes2.dex", "jasmin_classes2.dex") |
| else: |
| # Move jasmin classes into classes2 directory so that |
| # they are picked up with -cp classes2. |
| os.makedirs("classes2", exist_ok=True) |
| shutil.copytree("jasmin_classes2", "classes2", dirs_exist_ok=True) |
| shutil.rmtree("jasmin_classes2") |
| |
| if HAS_SMALI_MULTIDEX and NEED_DEX: |
| # Compile Smali classes |
| smali(["-JXmx512m", "assemble"] + SMALI_ARGS + |
| ["--output", "smali_classes2.dex"] + find("smali-multidex", "*.smali")) |
| |
| # Merge smali_classes2.dex into classes2.dex |
| make_dexmerge("classes2.dex", "smali_classes2.dex") |
| |
| if HAS_SRC_EX: |
| os.makedirs("classes-ex", exist_ok=True) |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes-ex"] + find("src-ex", "*.java")) |
| |
| if HAS_SRC_EX2: |
| os.makedirs("classes-ex", exist_ok=True) |
| javac_with_bootclasspath(["-implicit:none"] + src_tmp_all + |
| ["-d", "classes-ex"] + find("src-ex2", "*.java")) |
| |
| if path.exists("classes-ex") and NEED_DEX: |
| make_dex("classes-ex") |
| |
| if HAS_SMALI_EX and NEED_DEX: |
| # Compile Smali classes |
| smali(["-JXmx512m", "assemble"] + SMALI_ARGS + |
| ["--output", "smali_classes-ex.dex"] + find("smali-ex", "*.smali")) |
| assert path.exists("smali_classes-ex.dex") |
| # Merge smali files into classes-ex.dex. |
| make_dexmerge("classes-ex.dex", "smali_classes-ex.dex") |
| |
| if path.exists("classes-ex.dex"): |
| # Apply hiddenapi on the dex files if the test has API list file(s). |
| if USE_HIDDENAPI and HAS_HIDDENAPI_SPEC: |
| make_hiddenapi("classes-ex.dex") |
| |
| # quick shuffle so that the stored name is "classes.dex" |
| os.rename("classes.dex", "classes-1.dex") |
| os.rename("classes-ex.dex", "classes.dex") |
| zip(TEST_NAME + "-ex.jar", "classes.dex") |
| os.rename("classes.dex", "classes-ex.dex") |
| os.rename("classes-1.dex", "classes.dex") |
| |
| # Apply hiddenapi on the dex files if the test has API list file(s). |
| if NEED_DEX and USE_HIDDENAPI and HAS_HIDDENAPI_SPEC: |
| if has_multidex(): |
| make_hiddenapi("classes.dex", "classes2.dex") |
| else: |
| make_hiddenapi("classes.dex") |
| |
| # Create a single dex jar with two dex files for multidex. |
| if NEED_DEX: |
| if path.exists("classes2.dex"): |
| zip(TEST_NAME + ".jar", "classes.dex", "classes2.dex") |
| else: |
| zip(TEST_NAME + ".jar", "classes.dex") |