1#!/usr/bin/env python 2# 3# Copyright (C) 2023 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"""apexd_host simulates apexd on host. 17 18Basically the tool scans .apex/.capex files from given directories 19(e.g --system_path) and extracts them under the output directory (--apex_path) 20using the deapexer tool. It also generates apex-info-list.xml file because 21some tools need that file as well to know the partitions of APEX files. 22 23This can be used when handling APEX files on host at buildtime or with 24target-files. For example, check_target_files_vintf tool invokes checkvintf with 25target-files, which, in turn, needs to read VINTF fragment files in APEX files. 26Hence, check_target_files_vintf can use apexd_host before checkvintf. 27 28Example: 29 $ apexd_host --apex_path /path/to/apex --system_path /path/to/system 30""" 31from __future__ import print_function 32 33import argparse 34import glob 35import os 36import subprocess 37import sys 38from xml.dom import minidom 39 40import apex_manifest 41 42 43# This should be in sync with kApexPackageBuiltinDirs in 44# system/apex/apexd/apex_constants.h 45PARTITIONS = ['system', 'system_ext', 'product', 'vendor', 'odm'] 46 47 48def DirectoryType(path): 49 if not os.path.exists(path): 50 return None 51 if not os.path.isdir(path): 52 raise argparse.ArgumentTypeError(f'{path} is not a directory') 53 return os.path.realpath(path) 54 55 56def ExistentDirectoryType(path): 57 if not os.path.exists(path): 58 raise argparse.ArgumentTypeError(f'{path} is not found') 59 return DirectoryType(path) 60 61 62def ParseArgs(): 63 parser = argparse.ArgumentParser() 64 parser.add_argument('--tool_path', help='Tools are searched in TOOL_PATH/bin') 65 parser.add_argument( 66 '--apex_path', 67 required=True, 68 type=ExistentDirectoryType, 69 help='Path to the directory where to activate APEXes', 70 ) 71 for part in PARTITIONS: 72 parser.add_argument( 73 f'--{part}_path', 74 help=f'Path to the directory corresponding /{part} on device', 75 type=DirectoryType, 76 ) 77 return parser.parse_args() 78 79 80class ApexFile(object): 81 """Represents an APEX file.""" 82 83 def __init__(self, path_on_host, path_on_device): 84 self._path_on_host = path_on_host 85 self._path_on_device = path_on_device 86 self._manifest = apex_manifest.fromApex(path_on_host) 87 88 @property 89 def name(self): 90 return self._manifest.name 91 92 @property 93 def path_on_host(self): 94 return self._path_on_host 95 96 @property 97 def path_on_device(self): 98 return self._path_on_device 99 100 # Helper to create apex-info element 101 @property 102 def attrs(self): 103 return { 104 'moduleName': self.name, 105 'modulePath': self.path_on_device, 106 'preinstalledModulePath': self.path_on_device, 107 'versionCode': str(self._manifest.version), 108 'versionName': self._manifest.versionName, 109 'isFactory': 'true', 110 'isActive': 'true', 111 'provideSharedApexLibs': ( 112 'true' if self._manifest.provideSharedApexLibs else 'false' 113 ), 114 } 115 116 117def InitTools(tool_path): 118 if tool_path is None: 119 exec_path = os.path.realpath(sys.argv[0]) 120 if exec_path.endswith('.py'): 121 script_name = os.path.basename(exec_path)[:-3] 122 sys.exit( 123 f'Do not invoke {exec_path} directly. Instead, use {script_name}' 124 ) 125 tool_path = os.path.dirname(os.path.dirname(exec_path)) 126 127 def ToolPath(name): 128 path = os.path.join(tool_path, 'bin', name) 129 if not os.path.exists(path): 130 sys.exit(f'Required tool({name}) not found in {tool_path}') 131 return path 132 133 return { 134 tool: ToolPath(tool) 135 for tool in [ 136 'deapexer', 137 'debugfs_static', 138 'fsck.erofs', 139 ] 140 } 141 142 143def ScanApexes(partition, real_path) -> list[ApexFile]: 144 apexes = [] 145 for path_on_host in glob.glob( 146 os.path.join(real_path, 'apex/*.apex') 147 ) + glob.glob(os.path.join(real_path, 'apex/*.capex')): 148 path_on_device = f'/{partition}/apex/' + os.path.basename(path_on_host) 149 apexes.append(ApexFile(path_on_host, path_on_device)) 150 # sort list for stability 151 return sorted(apexes, key=lambda apex: apex.path_on_device) 152 153 154def ActivateApexes(partitions, apex_dir, tools): 155 # Emit apex-info-list.xml 156 impl = minidom.getDOMImplementation() 157 doc = impl.createDocument(None, 'apex-info-list', None) 158 apex_info_list = doc.documentElement 159 160 # Scan each partition for apexes and activate them 161 for partition, real_path in partitions.items(): 162 apexes = ScanApexes(partition, real_path) 163 164 # Activate each apex with deapexer 165 for apex_file in apexes: 166 # Multi-apex is ignored 167 if os.path.exists(os.path.join(apex_dir, apex_file.name)): 168 continue 169 170 cmd = [tools['deapexer']] 171 cmd += ['--debugfs_path', tools['debugfs_static']] 172 cmd += ['--fsckerofs_path', tools['fsck.erofs']] 173 cmd += [ 174 'extract', 175 apex_file.path_on_host, 176 os.path.join(apex_dir, apex_file.name), 177 ] 178 subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT) 179 180 # Add activated apex_info to apex_info_list 181 apex_info = doc.createElement('apex-info') 182 for name, value in apex_file.attrs.items(): 183 apex_info.setAttribute(name, value) 184 apex_info_list.appendChild(apex_info) 185 186 apex_info_list_file = os.path.join(apex_dir, 'apex-info-list.xml') 187 with open(apex_info_list_file, 'wt', encoding='utf-8') as f: 188 doc.writexml(f, encoding='utf-8', addindent=' ', newl='\n') 189 190 191def main(): 192 args = ParseArgs() 193 194 partitions = {} 195 for part in PARTITIONS: 196 if vars(args).get(f'{part}_path'): 197 partitions[part] = vars(args).get(f'{part}_path') 198 199 tools = InitTools(args.tool_path) 200 ActivateApexes(partitions, args.apex_path, tools) 201 202 203if __name__ == '__main__': 204 main() 205