Alex Light | 4fa4835 | 2020-12-29 13:45:04 -0800 | [diff] [blame^] | 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 | |
| 21 | import argparse |
| 22 | import shlex |
| 23 | import subprocess |
| 24 | import os |
| 25 | import os.path |
| 26 | |
| 27 | |
| 28 | def run_print(lst): |
| 29 | return " ".join(map(shlex.quote, lst)) |
| 30 | |
| 31 | |
| 32 | def 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 | "--arch", |
| 72 | action="store", |
| 73 | choices=["arm", "arm64", "x86", "x86_64", "host64", "host32"], |
| 74 | default="host64", |
| 75 | help="architecture to compile for. Defaults to host64") |
| 76 | parser.add_argument( |
| 77 | "--odex-file", |
| 78 | action="store", |
| 79 | help="odex file to write. File discarded if not set", |
| 80 | default=None) |
| 81 | parser.add_argument( |
| 82 | "--save-profile", |
| 83 | action="store", |
| 84 | type=argparse.FileType("w"), |
| 85 | default=None, |
| 86 | help="File path to store the profile to") |
| 87 | parser.add_argument( |
| 88 | "dex_files", help="dex/jar files", nargs="+", metavar="DEX") |
| 89 | return parser.parse_known_args() |
| 90 | |
| 91 | |
| 92 | def get_bcp_runtime_args(image, arch): |
| 93 | if arch != "host32" and arch != "host64": |
| 94 | args = [ |
| 95 | "art/tools/host_bcp.sh", |
| 96 | os.path.expandvars( |
| 97 | "${{OUT}}/system/framework/oat/{}/services.odex".format(arch)), |
| 98 | "--use-first-dir" |
| 99 | ] |
| 100 | print("Running: {}".format(run_print(args))) |
| 101 | print("=START=======================================") |
| 102 | res = subprocess.run(args, capture_output=True, text=True) |
| 103 | print("=END=========================================") |
| 104 | res.check_returncode() |
| 105 | return res.stdout.split() |
| 106 | else: |
| 107 | # Host we just use the bcp locations for both. |
| 108 | res = open( |
| 109 | os.path.expandvars( |
| 110 | "$ANDROID_HOST_OUT/apex/art_boot_images/javalib/{}/boot.oat".format( |
| 111 | "x86" if arch == "host32" else "x86_64")), "rb").read() |
| 112 | bcp_tag = b"bootclasspath\0" |
| 113 | bcp_start = res.find(bcp_tag) + len(bcp_tag) |
| 114 | bcp = res[bcp_start:bcp_start + res[bcp_start:].find(b"\0")] |
| 115 | str_bcp = bcp.decode() |
| 116 | return [ |
| 117 | "--runtime-arg", "-Xbootclasspath:{}".format(str_bcp), "--runtime-arg", |
| 118 | "-Xbootclasspath-locations:{}".format(str_bcp) |
| 119 | ] |
| 120 | |
| 121 | |
| 122 | def fdfile(fd): |
| 123 | return "/proc/{}/fd/{}".format(os.getpid(), fd) |
| 124 | |
| 125 | |
| 126 | def get_profile_args(args, location_base): |
| 127 | """Handle all the profile file options.""" |
| 128 | if args.profile_file is None and len(args.profile_line) == 0: |
| 129 | return [] |
| 130 | if args.debug_profman: |
| 131 | profman_args = ["lldb-server", "g", ":5039", "--", args.profman] |
| 132 | else: |
| 133 | profman_args = [args.profman] |
| 134 | if args.save_profile: |
| 135 | prof_out_fd = args.save_profile.fileno() |
| 136 | os.set_inheritable(prof_out_fd, True) |
| 137 | else: |
| 138 | prof_out_fd = os.memfd_create("reference_prof", flags=0) |
| 139 | if args.debug_profman: |
| 140 | profman_args.append("--reference-profile-file={}".format( |
| 141 | fdfile(prof_out_fd))) |
| 142 | else: |
| 143 | profman_args.append("--reference-profile-file-fd={}".format(prof_out_fd)) |
| 144 | if args.profile_file: |
| 145 | profman_args.append("--create-profile-from={}".format(args.profile_file)) |
| 146 | else: |
| 147 | prof_in_fd = os.memfd_create("input_prof", flags=0) |
| 148 | # Why on earth does fdopen take control of the fd and not mention it in the docs. |
| 149 | with os.fdopen(os.dup(prof_in_fd), "w") as prof_in: |
| 150 | for l in args.profile_line: |
| 151 | print(l, file=prof_in) |
| 152 | profman_args.append("--create-profile-from={}".format(fdfile(prof_in_fd))) |
| 153 | for f in args.dex_files: |
| 154 | profman_args.append("--apk={}".format(f)) |
| 155 | profman_args.append("--dex-location={}".format( |
| 156 | os.path.join(location_base, os.path.basename(f)))) |
| 157 | print("Running: {}".format(run_print(profman_args))) |
| 158 | print("=START=======================================") |
| 159 | subprocess.run(profman_args, close_fds=False).check_returncode() |
| 160 | print("=END=========================================") |
| 161 | if args.debug: |
| 162 | return ["--profile-file={}".format(fdfile(prof_out_fd))] |
| 163 | else: |
| 164 | return ["--profile-file={}".format(fdfile(prof_out_fd))] |
| 165 | |
| 166 | |
| 167 | def main(): |
| 168 | args, extra = parse_args() |
| 169 | if args.arch == "host32" or args.arch == "host64": |
| 170 | location_base = os.path.expandvars("${ANDROID_HOST_OUT}/framework/") |
| 171 | real_arch = "x86" if args.arch == "host32" else "x86_64" |
| 172 | boot_image = os.path.expandvars( |
| 173 | "$ANDROID_HOST_OUT/apex/art_boot_images/javalib/boot.art") |
| 174 | android_root = os.path.expandvars("$ANDROID_HOST_OUT") |
| 175 | for f in args.dex_files: |
| 176 | extra.append("--dex-location={}".format( |
| 177 | os.path.join(location_base, os.path.basename(f)))) |
| 178 | extra.append("--dex-file={}".format(f)) |
| 179 | else: |
| 180 | location_base = "/system/framework" |
| 181 | real_arch = args.arch |
| 182 | boot_image = os.path.expandvars(":".join([ |
| 183 | "${OUT}/apex/com.android.art.debug/javalib/boot.art", |
| 184 | "${OUT}/system/framework/boot-framework.art" |
| 185 | ])) |
| 186 | android_root = os.path.expandvars("$OUT/system") |
| 187 | for f in args.dex_files: |
| 188 | extra.append("--dex-location={}".format( |
| 189 | os.path.join(location_base, os.path.basename(f)))) |
| 190 | extra.append("--dex-file={}".format(f)) |
| 191 | extra += get_bcp_runtime_args(boot_image, args.arch) |
| 192 | extra += get_profile_args(args, location_base) |
| 193 | extra.append("--instruction-set={}".format(real_arch)) |
| 194 | extra.append("--boot-image={}".format(boot_image)) |
| 195 | extra.append("--android-root={}".format(android_root)) |
| 196 | extra += ["--runtime-arg", "-Xms64m", "--runtime-arg", "-Xmx512m"] |
| 197 | if args.odex_file is not None: |
| 198 | extra.append("--oat-file={}".format(args.odex_file)) |
| 199 | else: |
| 200 | if args.debug: |
| 201 | raise Exception("Debug requires a real output file. :(") |
| 202 | extra.append("--oat-fd={}".format(os.memfd_create("odex_fd", flags=0))) |
| 203 | extra.append("--oat-location={}".format("/tmp/odex_fd.odex")) |
| 204 | extra.append("--output-vdex-fd={}".format( |
| 205 | os.memfd_create("vdex_fd", flags=0))) |
| 206 | pre_args = [] |
| 207 | if args.debug: |
| 208 | pre_args = ["lldb-server", "g", ":5039", "--"] |
| 209 | pre_args.append(args.dex2oat) |
| 210 | print("Running: {}".format(run_print(pre_args + extra))) |
| 211 | print("=START=======================================") |
| 212 | subprocess.run(pre_args + extra, close_fds=False).check_returncode() |
| 213 | print("=END=========================================") |
| 214 | |
| 215 | |
| 216 | if __name__ == "__main__": |
| 217 | main() |