• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, partition):
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    self._partition = partition
88
89  @property
90  def name(self):
91    return self._manifest.name
92
93  @property
94  def path_on_host(self):
95    return self._path_on_host
96
97  @property
98  def path_on_device(self):
99    return self._path_on_device
100
101  @property
102  def partition(self):
103    return self._partition
104
105  # Helper to create apex-info element
106  @property
107  def attrs(self):
108    return {
109        'moduleName': self.name,
110        'modulePath': self.path_on_device,
111        'partition': self.partition.upper(),
112        'preinstalledModulePath': self.path_on_device,
113        'versionCode': str(self._manifest.version),
114        'versionName': self._manifest.versionName,
115        'isFactory': 'true',
116        'isActive': 'true',
117        'provideSharedApexLibs': (
118            'true' if self._manifest.provideSharedApexLibs else 'false'
119        ),
120    }
121
122
123def InitTools(tool_path):
124  if tool_path is None:
125    exec_path = os.path.realpath(sys.argv[0])
126    if exec_path.endswith('.py'):
127      script_name = os.path.basename(exec_path)[:-3]
128      sys.exit(
129          f'Do not invoke {exec_path} directly. Instead, use {script_name}'
130      )
131    tool_path = os.path.dirname(os.path.dirname(exec_path))
132
133  def ToolPath(name, candidates):
134    for candidate in candidates:
135      path = os.path.join(tool_path, 'bin', candidate)
136      if os.path.exists(path):
137        return path
138    sys.exit(f'Required tool({name}) not found in {tool_path}')
139
140  tools = {
141    'deapexer': ['deapexer'],
142    'debugfs': ['debugfs', 'debugfs_static'],
143    'fsckerofs': ['fsck.erofs'],
144  }
145  return {
146      tool: ToolPath(tool, candidates)
147      for tool, candidates in tools.items()
148  }
149
150
151def ScanApexes(partition, real_path) -> list[ApexFile]:
152  apexes = []
153  for path_on_host in glob.glob(
154      os.path.join(real_path, 'apex/*.apex')
155  ) + glob.glob(os.path.join(real_path, 'apex/*.capex')):
156    path_on_device = f'/{partition}/apex/' + os.path.basename(path_on_host)
157    apexes.append(ApexFile(path_on_host, path_on_device, partition))
158  # sort list for stability
159  return sorted(apexes, key=lambda apex: apex.path_on_device)
160
161
162def ActivateApexes(partitions, apex_dir, tools):
163  # Emit apex-info-list.xml
164  impl = minidom.getDOMImplementation()
165  doc = impl.createDocument(None, 'apex-info-list', None)
166  apex_info_list = doc.documentElement
167
168  # Scan each partition for apexes and activate them
169  for partition, real_path in partitions.items():
170    apexes = ScanApexes(partition, real_path)
171
172    # Activate each apex with deapexer
173    for apex_file in apexes:
174      # Multi-apex is ignored
175      if os.path.exists(os.path.join(apex_dir, apex_file.name)):
176        continue
177
178      cmd = [tools['deapexer']]
179      cmd += ['--debugfs_path', tools['debugfs']]
180      cmd += ['--fsckerofs_path', tools['fsckerofs']]
181      cmd += [
182          'extract',
183          apex_file.path_on_host,
184          os.path.join(apex_dir, apex_file.name),
185      ]
186      subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT)
187
188      # Add activated apex_info to apex_info_list
189      apex_info = doc.createElement('apex-info')
190      for name, value in apex_file.attrs.items():
191        apex_info.setAttribute(name, value)
192      apex_info_list.appendChild(apex_info)
193
194  apex_info_list_file = os.path.join(apex_dir, 'apex-info-list.xml')
195  with open(apex_info_list_file, 'wt', encoding='utf-8') as f:
196    doc.writexml(f, encoding='utf-8', addindent='  ', newl='\n')
197
198
199def main():
200  args = ParseArgs()
201
202  partitions = {}
203  for part in PARTITIONS:
204    if vars(args).get(f'{part}_path'):
205      partitions[part] = vars(args).get(f'{part}_path')
206
207  tools = InitTools(args.tool_path)
208  ActivateApexes(partitions, args.apex_path, tools)
209
210
211if __name__ == '__main__':
212  main()
213