1#!/usr/bin/env python 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 17import argparse 18import os 19import shutil 20import subprocess 21import sys 22import tempfile 23 24def _create_apex(args, work_dir): 25 26 image_apex_dir = "image.apex" 27 28 # Used for creating canned_fs_config, since every file and dir in the APEX are represented 29 # by an entry in the fs_config. 30 apex_subdirs = [] 31 apex_filepaths = [] 32 33 input_dir = os.path.join(work_dir, image_apex_dir) 34 os.makedirs(input_dir, exist_ok=True) 35 bazel_apexer_wrapper_manifest = open(args.bazel_apexer_wrapper_manifest, 'r') 36 file_lines = bazel_apexer_wrapper_manifest.readlines() 37 for line in file_lines: 38 line = line.strip() 39 if (len(line) == 0): 40 continue 41 apex_dirname, apex_filename, bazel_input_file = line.split(",") 42 full_apex_dirname = "/".join([input_dir, apex_dirname]) 43 os.makedirs(full_apex_dirname, exist_ok=True) 44 45 apex_filepath = "/".join([apex_dirname, apex_filename]) 46 apex_filepaths.append(apex_filepath) 47 apex_subdirs.append(apex_dirname) 48 49 full_apex_filepath = "/".join([input_dir, apex_filepath]) 50 # Because Bazel execution root is a symlink forest, all the input files are symlinks, these 51 # include the dependency files declared in the BUILD files as well as the files declared 52 # and created in the bzl files. For sandbox runs the former are two or more level symlinks and 53 # latter are one level symlinks. For non-sandbox runs, the former are one level symlinks 54 # and the latter are actual files. Here are some examples: 55 # 56 # Two level symlinks: 57 # system/timezone/output_data/version/tz_version -> 58 # /usr/local/google/home/...out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/ 59 # execroot/__main__/system/timezone/output_data/version/tz_version -> 60 # /usr/local/google/home/.../system/timezone/output_data/version/tz_version 61 # 62 # Three level symlinks: 63 # bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/libcrypto.so -> 64 # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/ 65 # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/libcrypto.so -> 66 # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/ 67 # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/ 68 # liblibcrypto_stripped.so -> 69 # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/ 70 # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/ 71 # liblibcrypto_unstripped.so 72 # 73 # One level symlinks: 74 # bazel-out/android_target-fastbuild/bin/system/timezone/apex/apex_manifest.pb -> 75 # /usr/local/google/home/.../out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/ 76 # execroot/__main__/bazel-out/android_target-fastbuild/bin/system/timezone/apex/ 77 # apex_manifest.pb 78 79 if os.path.islink(bazel_input_file): 80 bazel_input_file = os.readlink(bazel_input_file) 81 82 # For sandbox run these are the 2nd level symlinks and we need to resolve 83 while os.path.islink(bazel_input_file) and 'execroot/__main__' in bazel_input_file: 84 bazel_input_file = os.readlink(bazel_input_file) 85 86 shutil.copyfile(bazel_input_file, full_apex_filepath, follow_symlinks=False) 87 88 # Make sure subdirs are unique 89 apex_subdirs_set = set() 90 for d in apex_subdirs: 91 apex_subdirs_set.add(d) 92 93 # Make sure all the parent dirs of the current subdir are in the set, too 94 dirs = d.split("/") 95 for i in range(0, len(dirs)): 96 apex_subdirs_set.add("/".join(dirs[:i])) 97 98 canned_fs_config = _generate_canned_fs_config(work_dir, apex_subdirs_set, apex_filepaths) 99 100 # Construct the main apexer command. 101 cmd = [args.apexer_path] 102 cmd.append('--verbose') 103 cmd.append('--force') 104 cmd.append('--include_build_info') 105 cmd.extend(['--file_contexts', args.file_contexts]) 106 cmd.extend(['--canned_fs_config', canned_fs_config]) 107 cmd.extend(['--key', args.key]) 108 cmd.extend(['--payload_type', 'image']) 109 cmd.extend(['--target_sdk_version', '10000']) 110 cmd.extend(['--payload_fs_type', 'ext4']) 111 cmd.extend(['--apexer_tool_path', args.apexer_tool_paths]) 112 113 if args.android_manifest != None: 114 cmd.extend(['--android_manifest', args.android_manifest]) 115 116 if args.pubkey != None: 117 cmd.extend(['--pubkey', args.pubkey]) 118 119 if args.manifest != None: 120 cmd.extend(['--manifest', args.manifest]) 121 122 if args.min_sdk_version != None: 123 cmd.extend(['--min_sdk_version', args.min_sdk_version]) 124 125 if args.android_jar_path != None: 126 cmd.extend(['--android_jar_path', args.android_jar_path]) 127 128 cmd.append(input_dir) 129 cmd.append(args.apex_output_file) 130 131 popen = subprocess.Popen(cmd) 132 popen.wait() 133 134 return True 135 136# Generate filesystem config. This encodes the filemode, uid, and gid of each 137# file in the APEX, including apex_manifest.json and apex_manifest.pb. 138# 139# NOTE: every file must have an entry. 140def _generate_canned_fs_config(work_dir, dirs, filepaths): 141 with tempfile.NamedTemporaryFile(mode = 'w+', dir=work_dir, delete=False) as canned_fs_config: 142 config_lines = [] 143 config_lines += ["/ 1000 1000 0755"] 144 config_lines += ["/apex_manifest.json 1000 1000 0644"] 145 config_lines += ["/apex_manifest.pb 1000 1000 0644"] 146 config_lines += ["/" + filepath + " 1000 1000 0644" for filepath in filepaths] 147 config_lines += ["/" + d + " 0 2000 0755" for d in dirs] 148 canned_fs_config.write("\n".join(config_lines)) 149 150 return canned_fs_config.name 151 152def _parse_args(argv): 153 parser = argparse.ArgumentParser(description='Build an APEX file') 154 155 parser.add_argument( 156 '--manifest', 157 help='path to the APEX manifest file (.pb)') 158 parser.add_argument( 159 '--apex_output_file', 160 required=True, 161 help='path to the APEX image file') 162 parser.add_argument( 163 '--bazel_apexer_wrapper_manifest', 164 required=True, 165 help='path to the manifest file that stores the info about the files to be packaged by apexer') 166 parser.add_argument( 167 '--android_manifest', 168 help='path to the AndroidManifest file. If omitted, a default one is created and used') 169 parser.add_argument( 170 '--file_contexts', 171 required=True, 172 help='selinux file contexts file.') 173 parser.add_argument( 174 '--key', 175 required=True, 176 help='path to the private key file.') 177 parser.add_argument( 178 '--pubkey', 179 help='path to the public key file. Used to bundle the public key in APEX for testing.') 180 parser.add_argument( 181 '--apexer_path', 182 required=True, 183 help='Path to the apexer binary.') 184 parser.add_argument( 185 '--apexer_tool_paths', 186 required=True, 187 help='Directories containing all the tools used by apexer, separated by ":" character.') 188 parser.add_argument( 189 '--min_sdk_version', 190 help='Default Min SDK version to use for AndroidManifest.xml') 191 parser.add_argument( 192 '--android_jar_path', 193 help='path to use as the source of the android API.') 194 195 return parser.parse_args(argv) 196 197def main(argv): 198 args = _parse_args(argv) 199 200 with tempfile.TemporaryDirectory() as work_dir: 201 success = _create_apex(args, work_dir) 202 203 if not success: 204 sys.exit(1) 205 206if __name__ == '__main__': 207 main(sys.argv[1:]) 208