#!/usr/bin/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 script runs dex2oat on the host to compile a provided JAR or APK. # import argparse import itertools import shlex import subprocess import os import os.path def run_print(lst): return " ".join(map(shlex.quote, lst)) def parse_args(): parser = argparse.ArgumentParser( description="compile dex or jar files", epilog="Unrecognized options are passed on to dex2oat unmodified.") parser.add_argument( "--dex2oat", action="store", default=os.path.expandvars("$ANDROID_HOST_OUT/bin/dex2oatd64"), help="selects the dex2oat to use.") parser.add_argument( "--debug", action="store_true", default=False, help="launches dex2oatd with lldb-server g :5039. Connect using vscode or remote lldb" ) parser.add_argument( "--profman", action="store", default=os.path.expandvars("$ANDROID_HOST_OUT/bin/profmand"), help="selects the profman to use.") parser.add_argument( "--debug-profman", action="store_true", default=False, help="launches profman with lldb-server g :5039. Connect using vscode or remote lldb" ) profs = parser.add_mutually_exclusive_group() profs.add_argument( "--profile-file", action="store", help="Use this profile file. Probably want to pass --compiler-filter=speed-profile with this." ) profs.add_argument( "--profile-line", action="append", default=[], help="functions to add to a profile. Probably want to pass --compiler-filter=speed-profile with this. All functions are marked as 'hot'. Use --profile-file for more control." ) parser.add_argument( "--add-bcp", action="append", default=[], nargs=2, metavar=("BCP_FILE", "BCP_LOCATION"), help="File and location to add to the boot-class-path. Note no deduplication is attempted." ) parser.add_argument( "--arch", action="store", choices=["arm", "arm64", "x86", "x86_64", "host64", "host32"], default="host64", help="architecture to compile for. Defaults to host64") parser.add_argument( "--odex-file", action="store", help="odex file to write. File discarded if not set", default=None) parser.add_argument( "--save-profile", action="store", type=argparse.FileType("w"), default=None, help="File path to store the profile to") parser.add_argument( "dex_files", help="dex/jar files", nargs="+", metavar="DEX") return parser.parse_known_args() def get_bcp_runtime_args(additions, image, arch): add_files = map(lambda a: a[0], additions) add_locs = map(lambda a: a[1], additions) if arch != "host32" and arch != "host64": args = [ "art/tools/host_bcp.sh", os.path.expandvars( "${{OUT}}/system/framework/oat/{}/services.odex".format(arch)), "--use-first-dir" ] print("Running: {}".format(run_print(args))) print("=START=======================================") res = subprocess.run(args, capture_output=True, text=True) print("=END=========================================") if res.returncode != 0: print("Falling back to com.android.art BCP") args = [ "art/tools/host_bcp.sh", os.path.expandvars( "${{OUT}}/apex/com.android.art.debug/javalib/{}/boot.oat".format(arch)), "--use-first-dir" ] print("Running: {}".format(run_print(args))) print("=START=======================================") res = subprocess.run(args, capture_output=True, text=True) print("=END=========================================") res.check_returncode() segments = res.stdout.split() def extend_bcp(segment: str): # TODO We should make the bcp have absolute paths. if segment.startswith("-Xbootclasspath:"): return ":".join(itertools.chain((segment,), add_files)) elif segment.startswith("-Xbootclasspath-locations:"): return ":".join(itertools.chain((segment,), add_locs)) else: return segment return list(map(extend_bcp, segments)) else: # Host we just use the bcp locations for both. res = open( os.path.expandvars( "$ANDROID_HOST_OUT/apex/art_boot_images/javalib/{}/boot.oat".format( "x86" if arch == "host32" else "x86_64")), "rb").read() bcp_tag = b"bootclasspath\0" bcp_start = res.find(bcp_tag) + len(bcp_tag) bcp = res[bcp_start:bcp_start + res[bcp_start:].find(b"\0")] img_bcp = bcp.decode() # TODO We should make the str_bcp have absolute paths. str_bcp = ":".join(itertools.chain((img_bcp,), add_files)) str_bcp_loc = ":".join(itertools.chain((img_bcp,), add_locs)) return [ "--runtime-arg", "-Xbootclasspath:{}".format(str_bcp), "--runtime-arg", "-Xbootclasspath-locations:{}".format(str_bcp_loc) ] def fdfile(fd): return "/proc/{}/fd/{}".format(os.getpid(), fd) def get_profile_args(args, location_base): """Handle all the profile file options.""" if args.profile_file is None and len(args.profile_line) == 0: return [] if args.profile_file: with open(args.profile_file, "rb") as prof: prof_magic = prof.read(4) if prof_magic == b'pro\0': # Looks like the profile-file is a binary profile. Just use it directly return ['--profile-file={}'.format(args.profile_file)] if args.debug_profman: profman_args = ["lldb-server", "g", ":5039", "--", args.profman] else: profman_args = [args.profman] if args.save_profile: prof_out_fd = args.save_profile.fileno() os.set_inheritable(prof_out_fd, True) else: prof_out_fd = os.memfd_create("reference_prof", flags=0) if args.debug_profman: profman_args.append("--reference-profile-file={}".format( fdfile(prof_out_fd))) else: profman_args.append("--reference-profile-file-fd={}".format(prof_out_fd)) if args.profile_file: profman_args.append("--create-profile-from={}".format(args.profile_file)) else: prof_in_fd = os.memfd_create("input_prof", flags=0) # Why on earth does fdopen take control of the fd and not mention it in the docs. with os.fdopen(os.dup(prof_in_fd), "w") as prof_in: for l in args.profile_line: print(l, file=prof_in) profman_args.append("--create-profile-from={}".format(fdfile(prof_in_fd))) for f in args.dex_files: profman_args.append("--apk={}".format(f)) profman_args.append("--dex-location={}".format( os.path.join(location_base, os.path.basename(f)))) print("Running: {}".format(run_print(profman_args))) print("=START=======================================") subprocess.run(profman_args, close_fds=False).check_returncode() print("=END=========================================") if args.debug: return ["--profile-file={}".format(fdfile(prof_out_fd))] else: return ["--profile-file={}".format(fdfile(prof_out_fd))] def main(): args, extra = parse_args() if args.arch == "host32" or args.arch == "host64": location_base = os.path.expandvars("${ANDROID_HOST_OUT}/framework/") real_arch = "x86" if args.arch == "host32" else "x86_64" boot_image = os.path.expandvars( "$ANDROID_HOST_OUT/apex/art_boot_images/javalib/boot.art") android_root = os.path.expandvars("$ANDROID_HOST_OUT") for f in args.dex_files: extra.append("--dex-location={}".format( os.path.join(location_base, os.path.basename(f)))) extra.append("--dex-file={}".format(f)) else: location_base = "/system/framework" real_arch = args.arch boot_image = os.path.expandvars(":".join([ "${OUT}/apex/art_boot_images/javalib/boot.art", "${OUT}/system/framework/boot-framework.art" ])) android_root = os.path.expandvars("$OUT/system") for f in args.dex_files: extra.append("--dex-location={}".format( os.path.join(location_base, os.path.basename(f)))) extra.append("--dex-file={}".format(f)) extra += get_bcp_runtime_args(args.add_bcp, boot_image, args.arch) extra += get_profile_args(args, location_base) extra.append("--instruction-set={}".format(real_arch)) extra.append("--boot-image={}".format(boot_image)) extra.append("--android-root={}".format(android_root)) extra += ["--runtime-arg", "-Xms64m", "--runtime-arg", "-Xmx512m"] if args.odex_file is not None: extra.append("--oat-file={}".format(args.odex_file)) else: if args.debug: raise Exception("Debug requires a real output file. :(") extra.append("--oat-fd={}".format(os.memfd_create("odex_fd", flags=0))) extra.append("--oat-location={}".format("/tmp/odex_fd.odex")) extra.append("--output-vdex-fd={}".format( os.memfd_create("vdex_fd", flags=0))) pre_args = [] if args.debug: pre_args = ["lldb-server", "g", ":5039", "--"] pre_args.append(args.dex2oat) print("Running: {}".format(run_print(pre_args + extra))) print("=START=======================================") subprocess.run(pre_args + extra, close_fds=False).check_returncode() print("=END=========================================") if __name__ == "__main__": main()