1#!/usr/bin/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# 18# This script runs dex2oat on the host to compile a provided JAR or APK. 19# 20 21import argparse 22import itertools 23import shlex 24import subprocess 25import os 26import os.path 27 28def run_print(lst): 29 return " ".join(map(shlex.quote, lst)) 30 31 32def parse_args(): 33 parser = argparse.ArgumentParser( 34 description="compile dex or jar files", 35 epilog="Unrecognized options are passed on to dex2oat unmodified.") 36 parser.add_argument( 37 "--dex2oat", 38 action="store", 39 default=os.path.expandvars("$ANDROID_HOST_OUT/bin/dex2oatd64"), 40 help="selects the dex2oat to use.") 41 parser.add_argument( 42 "--debug", 43 action="store_true", 44 default=False, 45 help="launches dex2oatd with lldb-server g :5039. Connect using vscode or remote lldb" 46 ) 47 parser.add_argument( 48 "--profman", 49 action="store", 50 default=os.path.expandvars("$ANDROID_HOST_OUT/bin/profmand"), 51 help="selects the profman to use.") 52 parser.add_argument( 53 "--debug-profman", 54 action="store_true", 55 default=False, 56 help="launches profman with lldb-server g :5039. Connect using vscode or remote lldb" 57 ) 58 profs = parser.add_mutually_exclusive_group() 59 profs.add_argument( 60 "--profile-file", 61 action="store", 62 help="Use this profile file. Probably want to pass --compiler-filter=speed-profile with this." 63 ) 64 profs.add_argument( 65 "--profile-line", 66 action="append", 67 default=[], 68 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." 69 ) 70 parser.add_argument( 71 "--add-bcp", 72 action="append", 73 default=[], 74 nargs=2, 75 metavar=("BCP_FILE", "BCP_LOCATION"), 76 help="File and location to add to the boot-class-path. Note no deduplication is attempted." 77 ) 78 parser.add_argument( 79 "--arch", 80 action="store", 81 choices=["arm", "arm64", "x86", "x86_64", "host64", "host32"], 82 default="host64", 83 help="architecture to compile for. Defaults to host64") 84 parser.add_argument( 85 "--odex-file", 86 action="store", 87 help="odex file to write. File discarded if not set", 88 default=None) 89 parser.add_argument( 90 "--save-profile", 91 action="store", 92 type=argparse.FileType("w"), 93 default=None, 94 help="File path to store the profile to") 95 parser.add_argument( 96 "dex_files", help="dex/jar files", nargs="+", metavar="DEX") 97 return parser.parse_known_args() 98 99 100def get_bcp_runtime_args(additions, image, arch): 101 add_files = map(lambda a: a[0], additions) 102 add_locs = map(lambda a: a[1], additions) 103 if arch != "host32" and arch != "host64": 104 args = [ 105 "art/tools/host_bcp.sh", 106 os.path.expandvars( 107 "${{OUT}}/system/framework/oat/{}/services.odex".format(arch)), 108 "--use-first-dir" 109 ] 110 print("Running: {}".format(run_print(args))) 111 print("=START=======================================") 112 res = subprocess.run(args, capture_output=True, text=True) 113 print("=END=========================================") 114 if res.returncode != 0: 115 print("Falling back to com.android.art BCP") 116 args = [ 117 "art/tools/host_bcp.sh", 118 os.path.expandvars( 119 "${{OUT}}/apex/com.android.art.debug/javalib/{}/boot.oat".format(arch)), 120 "--use-first-dir" 121 ] 122 print("Running: {}".format(run_print(args))) 123 print("=START=======================================") 124 res = subprocess.run(args, capture_output=True, text=True) 125 print("=END=========================================") 126 res.check_returncode() 127 segments = res.stdout.split() 128 def extend_bcp(segment: str): 129 # TODO We should make the bcp have absolute paths. 130 if segment.startswith("-Xbootclasspath:"): 131 return ":".join(itertools.chain((segment,), add_files)) 132 elif segment.startswith("-Xbootclasspath-locations:"): 133 return ":".join(itertools.chain((segment,), add_locs)) 134 else: 135 return segment 136 return list(map(extend_bcp, segments)) 137 else: 138 # Host we just use the bcp locations for both. 139 res = open( 140 os.path.expandvars( 141 "$ANDROID_HOST_OUT/apex/art_boot_images/javalib/{}/boot.oat".format( 142 "x86" if arch == "host32" else "x86_64")), "rb").read() 143 bcp_tag = b"bootclasspath\0" 144 bcp_start = res.find(bcp_tag) + len(bcp_tag) 145 bcp = res[bcp_start:bcp_start + res[bcp_start:].find(b"\0")] 146 img_bcp = bcp.decode() 147 # TODO We should make the str_bcp have absolute paths. 148 str_bcp = ":".join(itertools.chain((img_bcp,), add_files)) 149 str_bcp_loc = ":".join(itertools.chain((img_bcp,), add_locs)) 150 return [ 151 "--runtime-arg", "-Xbootclasspath:{}".format(str_bcp), 152 "--runtime-arg", "-Xbootclasspath-locations:{}".format(str_bcp_loc) 153 ] 154 155 156def fdfile(fd): 157 return "/proc/{}/fd/{}".format(os.getpid(), fd) 158 159 160def get_profile_args(args, location_base): 161 """Handle all the profile file options.""" 162 if args.profile_file is None and len(args.profile_line) == 0: 163 return [] 164 if args.profile_file: 165 with open(args.profile_file, "rb") as prof: 166 prof_magic = prof.read(4) 167 if prof_magic == b'pro\0': 168 # Looks like the profile-file is a binary profile. Just use it directly 169 return ['--profile-file={}'.format(args.profile_file)] 170 if args.debug_profman: 171 profman_args = ["lldb-server", "g", ":5039", "--", args.profman] 172 else: 173 profman_args = [args.profman] 174 if args.save_profile: 175 prof_out_fd = args.save_profile.fileno() 176 os.set_inheritable(prof_out_fd, True) 177 else: 178 prof_out_fd = os.memfd_create("reference_prof", flags=0) 179 if args.debug_profman: 180 profman_args.append("--reference-profile-file={}".format( 181 fdfile(prof_out_fd))) 182 else: 183 profman_args.append("--reference-profile-file-fd={}".format(prof_out_fd)) 184 if args.profile_file: 185 profman_args.append("--create-profile-from={}".format(args.profile_file)) 186 else: 187 prof_in_fd = os.memfd_create("input_prof", flags=0) 188 # Why on earth does fdopen take control of the fd and not mention it in the docs. 189 with os.fdopen(os.dup(prof_in_fd), "w") as prof_in: 190 for l in args.profile_line: 191 print(l, file=prof_in) 192 profman_args.append("--create-profile-from={}".format(fdfile(prof_in_fd))) 193 for f in args.dex_files: 194 profman_args.append("--apk={}".format(f)) 195 profman_args.append("--dex-location={}".format( 196 os.path.join(location_base, os.path.basename(f)))) 197 print("Running: {}".format(run_print(profman_args))) 198 print("=START=======================================") 199 subprocess.run(profman_args, close_fds=False).check_returncode() 200 print("=END=========================================") 201 if args.debug: 202 return ["--profile-file={}".format(fdfile(prof_out_fd))] 203 else: 204 return ["--profile-file={}".format(fdfile(prof_out_fd))] 205 206 207def main(): 208 args, extra = parse_args() 209 if args.arch == "host32" or args.arch == "host64": 210 location_base = os.path.expandvars("${ANDROID_HOST_OUT}/framework/") 211 real_arch = "x86" if args.arch == "host32" else "x86_64" 212 boot_image = os.path.expandvars( 213 "$ANDROID_HOST_OUT/apex/art_boot_images/javalib/boot.art") 214 android_root = os.path.expandvars("$ANDROID_HOST_OUT") 215 for f in args.dex_files: 216 extra.append("--dex-location={}".format( 217 os.path.join(location_base, os.path.basename(f)))) 218 extra.append("--dex-file={}".format(f)) 219 else: 220 location_base = "/system/framework" 221 real_arch = args.arch 222 boot_image = os.path.expandvars(":".join([ 223 "${OUT}/apex/art_boot_images/javalib/boot.art", 224 "${OUT}/system/framework/boot-framework.art" 225 ])) 226 android_root = os.path.expandvars("$OUT/system") 227 for f in args.dex_files: 228 extra.append("--dex-location={}".format( 229 os.path.join(location_base, os.path.basename(f)))) 230 extra.append("--dex-file={}".format(f)) 231 extra += get_bcp_runtime_args(args.add_bcp, boot_image, args.arch) 232 extra += get_profile_args(args, location_base) 233 extra.append("--instruction-set={}".format(real_arch)) 234 extra.append("--boot-image={}".format(boot_image)) 235 extra.append("--android-root={}".format(android_root)) 236 extra += ["--runtime-arg", "-Xms64m", "--runtime-arg", "-Xmx512m"] 237 if args.odex_file is not None: 238 extra.append("--oat-file={}".format(args.odex_file)) 239 else: 240 if args.debug: 241 raise Exception("Debug requires a real output file. :(") 242 extra.append("--oat-fd={}".format(os.memfd_create("odex_fd", flags=0))) 243 extra.append("--oat-location={}".format("/tmp/odex_fd.odex")) 244 extra.append("--output-vdex-fd={}".format( 245 os.memfd_create("vdex_fd", flags=0))) 246 pre_args = [] 247 if args.debug: 248 pre_args = ["lldb-server", "g", ":5039", "--"] 249 pre_args.append(args.dex2oat) 250 print("Running: {}".format(run_print(pre_args + extra))) 251 print("=START=======================================") 252 subprocess.run(pre_args + extra, close_fds=False).check_returncode() 253 print("=END=========================================") 254 255 256if __name__ == "__main__": 257 main() 258