Home | History | Annotate | Download | only in apex
      1 #!/usr/bin/env python3
      2 
      3 # Copyright (C) 2019 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 import argparse
     19 import fnmatch
     20 import logging
     21 import os
     22 import os.path
     23 import subprocess
     24 import sys
     25 import zipfile
     26 
     27 logging.basicConfig(format='%(message)s')
     28 
     29 
     30 class FSObject:
     31   def __init__(self, name, is_dir, is_exec, is_symlink):
     32     self.name = name
     33     self.is_dir = is_dir
     34     self.is_exec = is_exec
     35     self.is_symlink = is_symlink
     36 
     37   def __str__(self):
     38     return '%s(dir=%r,exec=%r,symlink=%r)' % (self.name, self.is_dir, self.is_exec, self.is_symlink)
     39 
     40 
     41 class TargetApexProvider:
     42   def __init__(self, apex, tmpdir, debugfs):
     43     self._tmpdir = tmpdir
     44     self._debugfs = debugfs
     45     self._folder_cache = {}
     46     self._payload = os.path.join(self._tmpdir, 'apex_payload.img')
     47     # Extract payload to tmpdir.
     48     apex_zip = zipfile.ZipFile(apex)
     49     apex_zip.extract('apex_payload.img', tmpdir)
     50 
     51   def __del__(self):
     52     # Delete temps.
     53     if os.path.exists(self._payload):
     54       os.remove(self._payload)
     55 
     56   def get(self, path):
     57     apex_dir, name = os.path.split(path)
     58     if not apex_dir:
     59       apex_dir = '.'
     60     apex_map = self.read_dir(apex_dir)
     61     return apex_map[name] if name in apex_map else None
     62 
     63   def read_dir(self, apex_dir):
     64     if apex_dir in self._folder_cache:
     65       return self._folder_cache[apex_dir]
     66     # Cannot use check_output as it will annoy with stderr.
     67     process = subprocess.Popen([self._debugfs, '-R', 'ls -l -p %s' % apex_dir, self._payload],
     68                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,
     69                                universal_newlines=True)
     70     stdout, _ = process.communicate()
     71     res = str(stdout)
     72     apex_map = {}
     73     # Debugfs output looks like this:
     74     #   debugfs 1.44.4 (18-Aug-2018)
     75     #   /12/040755/0/2000/.//
     76     #   /2/040755/1000/1000/..//
     77     #   /13/100755/0/2000/dalvikvm32/28456/
     78     #   /14/100755/0/2000/dexoptanalyzer/20396/
     79     #   /15/100755/0/2000/linker/1152724/
     80     #   /16/100755/0/2000/dex2oat/563508/
     81     #   /17/100755/0/2000/linker64/1605424/
     82     #   /18/100755/0/2000/profman/85304/
     83     #   /19/100755/0/2000/dalvikvm64/28576/
     84     #    |     |   |   |       |        |
     85     #    |     |   |   #- gid  #- name  #- size
     86     #    |     |   #- uid
     87     #    |     #- type and permission bits
     88     #    #- inode nr (?)
     89     #
     90     # Note: could break just on '/' to avoid names with newlines.
     91     for line in res.split("\n"):
     92       if not line:
     93         continue
     94       comps = line.split('/')
     95       if len(comps) != 8:
     96         logging.warning('Could not break and parse line \'%s\'', line)
     97         continue
     98       bits = comps[2]
     99       name = comps[5]
    100       if len(bits) != 6:
    101         logging.warning('Dont understand bits \'%s\'', bits)
    102         continue
    103       is_dir = bits[1] == '4'
    104 
    105       def is_exec_bit(ch):
    106         return int(ch) & 1 == 1
    107 
    108       is_exec = is_exec_bit(bits[3]) and is_exec_bit(bits[4]) and is_exec_bit(bits[5])
    109       is_symlink = bits[1] == '2'
    110       apex_map[name] = FSObject(name, is_dir, is_exec, is_symlink)
    111     self._folder_cache[apex_dir] = apex_map
    112     return apex_map
    113 
    114 
    115 class HostApexProvider:
    116   def __init__(self, apex, tmpdir):
    117     self._tmpdir = tmpdir
    118     self.folder_cache = {}
    119     self._payload = os.path.join(self._tmpdir, 'apex_payload.zip')
    120     # Extract payload to tmpdir.
    121     apex_zip = zipfile.ZipFile(apex)
    122     apex_zip.extract('apex_payload.zip', tmpdir)
    123 
    124   def __del__(self):
    125     # Delete temps.
    126     if os.path.exists(self._payload):
    127       os.remove(self._payload)
    128 
    129   def get(self, path):
    130     apex_dir, name = os.path.split(path)
    131     if not apex_dir:
    132       apex_dir = ''
    133     apex_map = self.read_dir(apex_dir)
    134     return apex_map[name] if name in apex_map else None
    135 
    136   def read_dir(self, apex_dir):
    137     if apex_dir in self.folder_cache:
    138       return self.folder_cache[apex_dir]
    139     if not self.folder_cache:
    140       self.parse_zip()
    141     if apex_dir in self.folder_cache:
    142       return self.folder_cache[apex_dir]
    143     return {}
    144 
    145   def parse_zip(self):
    146     apex_zip = zipfile.ZipFile(self._payload)
    147     infos = apex_zip.infolist()
    148     for zipinfo in infos:
    149       path = zipinfo.filename
    150 
    151       # Assume no empty file is stored.
    152       assert path
    153 
    154       def get_octal(val, index):
    155         return (val >> (index * 3)) & 0x7
    156 
    157       def bits_is_exec(val):
    158         # TODO: Enforce group/other, too?
    159         return get_octal(val, 2) & 1 == 1
    160 
    161       is_zipinfo = True
    162       while path:
    163         apex_dir, base = os.path.split(path)
    164         # TODO: If directories are stored, base will be empty.
    165 
    166         if apex_dir not in self.folder_cache:
    167           self.folder_cache[apex_dir] = {}
    168         dir_map = self.folder_cache[apex_dir]
    169         if base not in dir_map:
    170           if is_zipinfo:
    171             bits = (zipinfo.external_attr >> 16) & 0xFFFF
    172             is_dir = get_octal(bits, 4) == 4
    173             is_symlink = get_octal(bits, 4) == 2
    174             is_exec = bits_is_exec(bits)
    175           else:
    176             is_exec = False  # Seems we can't get this easily?
    177             is_symlink = False
    178             is_dir = True
    179           dir_map[base] = FSObject(base, is_dir, is_exec, is_symlink)
    180         is_zipinfo = False
    181         path = apex_dir
    182 
    183 
    184 # DO NOT USE DIRECTLY! This is an "abstract" base class.
    185 class Checker:
    186   def __init__(self, provider):
    187     self._provider = provider
    188     self._errors = 0
    189     self._expected_file_globs = set()
    190 
    191   def fail(self, msg, *fail_args):
    192     self._errors += 1
    193     logging.error(msg, *fail_args)
    194 
    195   def error_count(self):
    196     return self._errors
    197 
    198   def reset_errors(self):
    199     self._errors = 0
    200 
    201   def is_file(self, path):
    202     fs_object = self._provider.get(path)
    203     if fs_object is None:
    204       return False, 'Could not find %s'
    205     if fs_object.is_dir:
    206       return False, '%s is a directory'
    207     return True, ''
    208 
    209   def check_file(self, path):
    210     ok, msg = self.is_file(path)
    211     if not ok:
    212       self.fail(msg, path)
    213     self._expected_file_globs.add(path)
    214     return ok
    215 
    216   def check_executable(self, filename):
    217     path = 'bin/%s' % filename
    218     if not self.check_file(path):
    219       return
    220     if not self._provider.get(path).is_exec:
    221       self.fail('%s is not executable', path)
    222 
    223   def check_executable_symlink(self, filename):
    224     path = 'bin/%s' % filename
    225     fs_object = self._provider.get(path)
    226     if fs_object is None:
    227       self.fail('Could not find %s', path)
    228       return
    229     if fs_object.is_dir:
    230       self.fail('%s is a directory', path)
    231       return
    232     if not fs_object.is_symlink:
    233       self.fail('%s is not a symlink', path)
    234     self._expected_file_globs.add(path)
    235 
    236   def check_single_library(self, filename):
    237     lib_path = 'lib/%s' % filename
    238     lib64_path = 'lib64/%s' % filename
    239     lib_is_file, _ = self.is_file(lib_path)
    240     if lib_is_file:
    241       self._expected_file_globs.add(lib_path)
    242     lib64_is_file, _ = self.is_file(lib64_path)
    243     if lib64_is_file:
    244       self._expected_file_globs.add(lib64_path)
    245     if not lib_is_file and not lib64_is_file:
    246       self.fail('Library missing: %s', filename)
    247 
    248   def check_java_library(self, basename):
    249     return self.check_file('javalib/%s.jar' % basename)
    250 
    251   def ignore_path(self, path_glob):
    252     self._expected_file_globs.add(path_glob)
    253 
    254   def check_no_superfluous_files(self, dir_path):
    255     paths = []
    256     for name in sorted(self._provider.read_dir(dir_path).keys()):
    257       if name not in ('.', '..'):
    258         paths.append(os.path.join(dir_path, name))
    259     expected_paths = set()
    260     dir_prefix = dir_path + '/'
    261     for path_glob in self._expected_file_globs:
    262       expected_paths |= set(fnmatch.filter(paths, path_glob))
    263       # If there are globs in subdirectories of dir_path we want to match their
    264       # path segments at this directory level.
    265       if path_glob.startswith(dir_prefix):
    266         subpath = path_glob[len(dir_prefix):]
    267         subpath_first_segment, _, _ = subpath.partition('/')
    268         expected_paths |= set(fnmatch.filter(paths, dir_prefix + subpath_first_segment))
    269     for unexpected_path in set(paths) - expected_paths:
    270       self.fail('Unexpected file \'%s\'', unexpected_path)
    271 
    272   # Just here for docs purposes, even if it isn't good Python style.
    273 
    274   def check_symlinked_multilib_executable(self, filename):
    275     """Check bin/filename32, and/or bin/filename64, with symlink bin/filename."""
    276     raise NotImplementedError
    277 
    278   def check_multilib_executable(self, filename):
    279     """Check bin/filename for 32 bit, and/or bin/filename64."""
    280     raise NotImplementedError
    281 
    282   def check_native_library(self, basename):
    283     """Check lib/basename.so, and/or lib64/basename.so."""
    284     raise NotImplementedError
    285 
    286   def check_optional_native_library(self, basename_glob):
    287     """Allow lib/basename.so and/or lib64/basename.so to exist."""
    288     raise NotImplementedError
    289 
    290   def check_prefer64_library(self, basename):
    291     """Check lib64/basename.so, or lib/basename.so on 32 bit only."""
    292     raise NotImplementedError
    293 
    294 
    295 class Arch32Checker(Checker):
    296   def check_symlinked_multilib_executable(self, filename):
    297     self.check_executable('%s32' % filename)
    298     self.check_executable_symlink(filename)
    299 
    300   def check_multilib_executable(self, filename):
    301     self.check_executable(filename)
    302 
    303   def check_native_library(self, basename):
    304     # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
    305     # the precision of this test?
    306     self.check_file('lib/%s.so' % basename)
    307 
    308   def check_optional_native_library(self, basename_glob):
    309     self.ignore_path('lib/%s.so' % basename_glob)
    310 
    311   def check_prefer64_library(self, basename):
    312     self.check_native_library(basename)
    313 
    314 
    315 class Arch64Checker(Checker):
    316   def check_symlinked_multilib_executable(self, filename):
    317     self.check_executable('%s64' % filename)
    318     self.check_executable_symlink(filename)
    319 
    320   def check_multilib_executable(self, filename):
    321     self.check_executable('%s64' % filename)
    322 
    323   def check_native_library(self, basename):
    324     # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
    325     # the precision of this test?
    326     self.check_file('lib64/%s.so' % basename)
    327 
    328   def check_optional_native_library(self, basename_glob):
    329     self.ignore_path('lib64/%s.so' % basename_glob)
    330 
    331   def check_prefer64_library(self, basename):
    332     self.check_native_library(basename)
    333 
    334 
    335 class MultilibChecker(Checker):
    336   def check_symlinked_multilib_executable(self, filename):
    337     self.check_executable('%s32' % filename)
    338     self.check_executable('%s64' % filename)
    339     self.check_executable_symlink(filename)
    340 
    341   def check_multilib_executable(self, filename):
    342     self.check_executable('%s64' % filename)
    343     self.check_executable(filename)
    344 
    345   def check_native_library(self, basename):
    346     # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
    347     # the precision of this test?
    348     self.check_file('lib/%s.so' % basename)
    349     self.check_file('lib64/%s.so' % basename)
    350 
    351   def check_optional_native_library(self, basename_glob):
    352     self.ignore_path('lib/%s.so' % basename_glob)
    353     self.ignore_path('lib64/%s.so' % basename_glob)
    354 
    355   def check_prefer64_library(self, basename):
    356     self.check_file('lib64/%s.so' % basename)
    357 
    358 
    359 class ReleaseChecker:
    360   def __init__(self, checker):
    361     self._checker = checker
    362 
    363   def __str__(self):
    364     return 'Release Checker'
    365 
    366   def run(self):
    367     # Check the APEX manifest.
    368     self._checker.check_file('apex_manifest.json')
    369 
    370     # Check binaries for ART.
    371     self._checker.check_executable('dex2oat')
    372     self._checker.check_executable('dexdump')
    373     self._checker.check_executable('dexlist')
    374     self._checker.check_executable('dexoptanalyzer')
    375     self._checker.check_executable('profman')
    376     self._checker.check_symlinked_multilib_executable('dalvikvm')
    377 
    378     # Check exported libraries for ART.
    379     self._checker.check_native_library('libdexfile_external')
    380     self._checker.check_native_library('libnativebridge')
    381     self._checker.check_native_library('libnativehelper')
    382     self._checker.check_native_library('libnativeloader')
    383 
    384     # Check internal libraries for ART.
    385     self._checker.check_native_library('libadbconnection')
    386     self._checker.check_native_library('libart')
    387     self._checker.check_native_library('libart-compiler')
    388     self._checker.check_native_library('libart-dexlayout')
    389     self._checker.check_native_library('libartbase')
    390     self._checker.check_native_library('libartpalette')
    391     self._checker.check_native_library('libdexfile')
    392     self._checker.check_native_library('libdexfile_support')
    393     self._checker.check_native_library('libopenjdkjvm')
    394     self._checker.check_native_library('libopenjdkjvmti')
    395     self._checker.check_native_library('libprofile')
    396     self._checker.check_native_library('libsigchain')
    397 
    398     # Check java libraries for Managed Core Library.
    399     self._checker.check_java_library('apache-xml')
    400     self._checker.check_java_library('bouncycastle')
    401     self._checker.check_java_library('core-libart')
    402     self._checker.check_java_library('core-oj')
    403     self._checker.check_java_library('okhttp')
    404 
    405     # Check internal native libraries for Managed Core Library.
    406     self._checker.check_native_library('libjavacore')
    407     self._checker.check_native_library('libopenjdk')
    408 
    409     # Check internal native library dependencies.
    410     #
    411     # Any internal dependency not listed here will cause a failure in
    412     # NoSuperfluousLibrariesChecker. Internal dependencies are generally just
    413     # implementation details, but in the release package we want to track them
    414     # because a) they add to the package size and the RAM usage (in particular
    415     # if the library is also present in /system or another APEX and hence might
    416     # get loaded twice through linker namespace separation), and b) we need to
    417     # catch invalid dependencies on /system or other APEXes that should go
    418     # through an exported library with stubs (b/128708192 tracks implementing a
    419     # better approach for that).
    420     self._checker.check_native_library('libbacktrace')
    421     self._checker.check_native_library('libbase')
    422     self._checker.check_native_library('libc++')
    423     self._checker.check_native_library('libdt_fd_forward')
    424     self._checker.check_native_library('libdt_socket')
    425     self._checker.check_native_library('libjdwp')
    426     self._checker.check_native_library('liblzma')
    427     self._checker.check_native_library('libnpt')
    428     self._checker.check_native_library('libunwindstack')
    429     self._checker.check_native_library('libziparchive')
    430     self._checker.check_optional_native_library('libvixl')  # Only on ARM/ARM64
    431 
    432     # Allow extra dependencies that appear in ASAN builds.
    433     self._checker.check_optional_native_library('libclang_rt.asan*')
    434     self._checker.check_optional_native_library('libclang_rt.hwasan*')
    435     self._checker.check_optional_native_library('libclang_rt.ubsan*')
    436 
    437 
    438 class ReleaseTargetChecker:
    439   def __init__(self, checker):
    440     self._checker = checker
    441 
    442   def __str__(self):
    443     return 'Release (Target) Checker'
    444 
    445   def run(self):
    446     # Check the APEX package scripts.
    447     self._checker.check_executable('art_postinstall_hook')
    448     self._checker.check_executable('art_preinstall_hook')
    449     self._checker.check_executable('art_preinstall_hook_boot')
    450     self._checker.check_executable('art_preinstall_hook_system_server')
    451     self._checker.check_executable('art_prepostinstall_utils')
    452 
    453     # Check binaries for ART.
    454     self._checker.check_executable('oatdump')
    455 
    456     # Check internal libraries for ART.
    457     self._checker.check_prefer64_library('libart-disassembler')
    458 
    459     # Check binaries for Bionic.
    460     self._checker.check_multilib_executable('linker')
    461     self._checker.check_multilib_executable('linker_asan')
    462 
    463     # Check libraries for Bionic.
    464     self._checker.check_native_library('bionic/libc')
    465     self._checker.check_native_library('bionic/libdl')
    466     self._checker.check_native_library('bionic/libm')
    467     # ... and its internal dependencies
    468     self._checker.check_native_library('libc_malloc_hooks')
    469     self._checker.check_native_library('libc_malloc_debug')
    470 
    471     # Check exported native libraries for Managed Core Library.
    472     self._checker.check_native_library('libandroidicu')
    473     self._checker.check_native_library('libandroidio')
    474 
    475     # Check internal native library dependencies.
    476     self._checker.check_native_library('libcrypto')
    477     self._checker.check_native_library('libexpat')
    478     self._checker.check_native_library('libicui18n')
    479     self._checker.check_native_library('libicuuc')
    480     self._checker.check_native_library('libpac')
    481     self._checker.check_native_library('libz')
    482 
    483     # TODO(b/124293228): Cuttlefish puts ARM libs in a lib/arm subdirectory.
    484     # Check that properly on that arch, but for now just ignore the directory.
    485     self._checker.ignore_path('lib/arm')
    486     self._checker.ignore_path('lib/arm64')
    487 
    488 
    489 class ReleaseHostChecker:
    490   def __init__(self, checker):
    491     self._checker = checker
    492 
    493   def __str__(self):
    494     return 'Release (Host) Checker'
    495 
    496   def run(self):
    497     # Check binaries for ART.
    498     self._checker.check_executable('hprof-conv')
    499     self._checker.check_symlinked_multilib_executable('dex2oatd')
    500 
    501     # Check exported native libraries for Managed Core Library.
    502     self._checker.check_native_library('libandroidicu-host')
    503     self._checker.check_native_library('libandroidio')
    504 
    505     # Check internal libraries for Managed Core Library.
    506     self._checker.check_native_library('libexpat-host')
    507     self._checker.check_native_library('libicui18n-host')
    508     self._checker.check_native_library('libicuuc-host')
    509     self._checker.check_native_library('libz-host')
    510 
    511 
    512 class DebugChecker:
    513   def __init__(self, checker):
    514     self._checker = checker
    515 
    516   def __str__(self):
    517     return 'Debug Checker'
    518 
    519   def run(self):
    520     # Check binaries for ART.
    521     self._checker.check_executable('dexdiag')
    522 
    523     # Check debug binaries for ART.
    524     self._checker.check_executable('dexoptanalyzerd')
    525     self._checker.check_executable('profmand')
    526 
    527     # Check internal libraries for ART.
    528     self._checker.check_native_library('libadbconnectiond')
    529     self._checker.check_native_library('libartbased')
    530     self._checker.check_native_library('libartd')
    531     self._checker.check_native_library('libartd-compiler')
    532     self._checker.check_native_library('libartd-dexlayout')
    533     self._checker.check_native_library('libdexfiled')
    534     self._checker.check_native_library('libopenjdkjvmd')
    535     self._checker.check_native_library('libopenjdkjvmtid')
    536     self._checker.check_native_library('libprofiled')
    537 
    538     # Check internal libraries for Managed Core Library.
    539     self._checker.check_native_library('libopenjdkd')
    540 
    541 
    542 class DebugTargetChecker:
    543   def __init__(self, checker):
    544     self._checker = checker
    545 
    546   def __str__(self):
    547     return 'Debug (Target) Checker'
    548 
    549   def run(self):
    550     # Check ART debug binaries.
    551     self._checker.check_executable('dex2oatd')
    552     self._checker.check_executable('oatdumpd')
    553 
    554     # Check ART internal libraries.
    555     self._checker.check_prefer64_library('libartd-disassembler')
    556 
    557     # Check internal native library dependencies.
    558     #
    559     # Like in the release package, we check that we don't get other dependencies
    560     # besides those listed here. In this case the concern is not bloat, but
    561     # rather that we don't get behavioural differences between user (release)
    562     # and userdebug/eng builds, which could happen if the debug package has
    563     # duplicate library instances where releases don't. In other words, it's
    564     # uncontroversial to add debug-only dependencies, as long as they don't make
    565     # assumptions on having a single global state (ideally they should have
    566     # double_loadable:true, cf. go/double_loadable). Also, like in the release
    567     # package we need to look out for dependencies that should go through
    568     # exported library stubs (until b/128708192 is fixed).
    569     self._checker.check_optional_native_library('libvixld')  # Only on ARM/ARM64
    570     self._checker.check_prefer64_library('libmeminfo')
    571     self._checker.check_prefer64_library('libprocinfo')
    572 
    573 
    574 class NoSuperfluousBinariesChecker:
    575   def __init__(self, checker):
    576     self._checker = checker
    577 
    578   def __str__(self):
    579     return 'No superfluous binaries checker'
    580 
    581   def run(self):
    582     self._checker.check_no_superfluous_files('bin')
    583 
    584 
    585 class NoSuperfluousLibrariesChecker:
    586   def __init__(self, checker):
    587     self._checker = checker
    588 
    589   def __str__(self):
    590     return 'No superfluous libraries checker'
    591 
    592   def run(self):
    593     self._checker.check_no_superfluous_files('javalib')
    594     self._checker.check_no_superfluous_files('lib')
    595     self._checker.check_no_superfluous_files('lib/bionic')
    596     self._checker.check_no_superfluous_files('lib64')
    597     self._checker.check_no_superfluous_files('lib64/bionic')
    598 
    599 
    600 class List:
    601   def __init__(self, provider):
    602     self._provider = provider
    603     self._path = ''
    604 
    605   def print_list(self):
    606     apex_map = self._provider.read_dir(self._path)
    607     if apex_map is None:
    608       return
    609     apex_map = dict(apex_map)
    610     if '.' in apex_map:
    611       del apex_map['.']
    612     if '..' in apex_map:
    613       del apex_map['..']
    614     for (_, val) in sorted(apex_map.items()):
    615       self._path = os.path.join(self._path, val.name)
    616       print(self._path)
    617       if val.is_dir:
    618         self.print_list()
    619 
    620 
    621 class Tree:
    622   def __init__(self, provider, title):
    623     print('%s' % title)
    624     self._provider = provider
    625     self._path = ''
    626     self._has_next_list = []
    627 
    628   @staticmethod
    629   def get_vertical(has_next_list):
    630     string = ''
    631     for v in has_next_list:
    632       string += '%s   ' % ('' if v else ' ')
    633     return string
    634 
    635   @staticmethod
    636   def get_last_vertical(last):
    637     return ' ' if last else ' '
    638 
    639   def print_tree(self):
    640     apex_map = self._provider.read_dir(self._path)
    641     if apex_map is None:
    642       return
    643     apex_map = dict(apex_map)
    644     if '.' in apex_map:
    645       del apex_map['.']
    646     if '..' in apex_map:
    647       del apex_map['..']
    648     key_list = list(sorted(apex_map.keys()))
    649     for i, key in enumerate(key_list):
    650       prev = self.get_vertical(self._has_next_list)
    651       last = self.get_last_vertical(i == len(key_list) - 1)
    652       val = apex_map[key]
    653       print('%s%s%s' % (prev, last, val.name))
    654       if val.is_dir:
    655         self._has_next_list.append(i < len(key_list) - 1)
    656         saved_dir = self._path
    657         self._path = os.path.join(self._path, val.name)
    658         self.print_tree()
    659         self._path = saved_dir
    660         self._has_next_list.pop()
    661 
    662 
    663 # Note: do not sys.exit early, for __del__ cleanup.
    664 def art_apex_test_main(test_args):
    665   if test_args.tree and test_args.debug:
    666     logging.error("Both of --tree and --debug set")
    667     return 1
    668   if test_args.list and test_args.debug:
    669     logging.error("Both of --list and --debug set")
    670     return 1
    671   if test_args.list and test_args.tree:
    672     logging.error("Both of --list and --tree set")
    673     return 1
    674   if not test_args.tmpdir:
    675     logging.error("Need a tmpdir.")
    676     return 1
    677   if not test_args.host and not test_args.debugfs:
    678     logging.error("Need debugfs.")
    679     return 1
    680   if test_args.bitness not in ['32', '64', 'multilib', 'auto']:
    681     logging.error('--bitness needs to be one of 32|64|multilib|auto')
    682 
    683   try:
    684     if test_args.host:
    685       apex_provider = HostApexProvider(test_args.apex, test_args.tmpdir)
    686     else:
    687       apex_provider = TargetApexProvider(test_args.apex, test_args.tmpdir, test_args.debugfs)
    688   except (zipfile.BadZipFile, zipfile.LargeZipFile) as e:
    689     logging.error('Failed to create provider: %s', e)
    690     return 1
    691 
    692   if test_args.tree:
    693     Tree(apex_provider, test_args.apex).print_tree()
    694     return 0
    695   if test_args.list:
    696     List(apex_provider).print_list()
    697     return 0
    698 
    699   checkers = []
    700   if test_args.bitness == 'auto':
    701     logging.warning('--bitness=auto, trying to autodetect. This may be incorrect!')
    702     has_32 = apex_provider.get('lib') is not None
    703     has_64 = apex_provider.get('lib64') is not None
    704     if has_32 and has_64:
    705       logging.warning('  Detected multilib')
    706       test_args.bitness = 'multilib'
    707     elif has_32:
    708       logging.warning('  Detected 32-only')
    709       test_args.bitness = '32'
    710     elif has_64:
    711       logging.warning('  Detected 64-only')
    712       test_args.bitness = '64'
    713     else:
    714       logging.error('  Could not detect bitness, neither lib nor lib64 contained.')
    715       print('%s' % apex_provider.folder_cache)
    716       return 1
    717 
    718   if test_args.bitness == '32':
    719     base_checker = Arch32Checker(apex_provider)
    720   elif test_args.bitness == '64':
    721     base_checker = Arch64Checker(apex_provider)
    722   else:
    723     assert test_args.bitness == 'multilib'
    724     base_checker = MultilibChecker(apex_provider)
    725 
    726   checkers.append(ReleaseChecker(base_checker))
    727   if test_args.host:
    728     checkers.append(ReleaseHostChecker(base_checker))
    729   else:
    730     checkers.append(ReleaseTargetChecker(base_checker))
    731   if test_args.debug:
    732     checkers.append(DebugChecker(base_checker))
    733   if test_args.debug and not test_args.host:
    734     checkers.append(DebugTargetChecker(base_checker))
    735 
    736   # These checkers must be last.
    737   checkers.append(NoSuperfluousBinariesChecker(base_checker))
    738   if not test_args.host:
    739     # We only care about superfluous libraries on target, where their absence
    740     # can be vital to ensure they get picked up from the right package.
    741     checkers.append(NoSuperfluousLibrariesChecker(base_checker))
    742 
    743   failed = False
    744   for checker in checkers:
    745     logging.info('%s...', checker)
    746     checker.run()
    747     if base_checker.error_count() > 0:
    748       logging.error('%s FAILED', checker)
    749       failed = True
    750     else:
    751       logging.info('%s SUCCEEDED', checker)
    752     base_checker.reset_errors()
    753 
    754   return 1 if failed else 0
    755 
    756 
    757 def art_apex_test_default(test_parser):
    758   if 'ANDROID_PRODUCT_OUT' not in os.environ:
    759     logging.error('No-argument use requires ANDROID_PRODUCT_OUT')
    760     sys.exit(1)
    761   product_out = os.environ['ANDROID_PRODUCT_OUT']
    762   if 'ANDROID_HOST_OUT' not in os.environ:
    763     logging.error('No-argument use requires ANDROID_HOST_OUT')
    764     sys.exit(1)
    765   host_out = os.environ['ANDROID_HOST_OUT']
    766 
    767   test_args = test_parser.parse_args(['dummy'])  # For consistency.
    768   test_args.debugfs = '%s/bin/debugfs' % host_out
    769   test_args.tmpdir = '.'
    770   test_args.tree = False
    771   test_args.list = False
    772   test_args.bitness = 'auto'
    773   failed = False
    774 
    775   if not os.path.exists(test_args.debugfs):
    776     logging.error("Cannot find debugfs (default path %s). Please build it, e.g., m debugfs",
    777                   test_args.debugfs)
    778     sys.exit(1)
    779 
    780   # TODO: Add host support
    781   configs = [
    782     {'name': 'com.android.runtime.release', 'debug': False, 'host': False},
    783     {'name': 'com.android.runtime.debug', 'debug': True, 'host': False},
    784   ]
    785 
    786   for config in configs:
    787     logging.info(config['name'])
    788     # TODO: Host will need different path.
    789     test_args.apex = '%s/system/apex/%s.apex' % (product_out, config['name'])
    790     if not os.path.exists(test_args.apex):
    791       failed = True
    792       logging.error("Cannot find APEX %s. Please build it first.", test_args.apex)
    793       continue
    794     test_args.debug = config['debug']
    795     test_args.host = config['host']
    796     failed = art_apex_test_main(test_args) != 0
    797 
    798   if failed:
    799     sys.exit(1)
    800 
    801 
    802 if __name__ == "__main__":
    803   parser = argparse.ArgumentParser(description='Check integrity of a Runtime APEX.')
    804 
    805   parser.add_argument('apex', help='apex file input')
    806 
    807   parser.add_argument('--host', help='Check as host apex', action='store_true')
    808 
    809   parser.add_argument('--debug', help='Check as debug apex', action='store_true')
    810 
    811   parser.add_argument('--list', help='List all files', action='store_true')
    812   parser.add_argument('--tree', help='Print directory tree', action='store_true')
    813 
    814   parser.add_argument('--tmpdir', help='Directory for temp files')
    815   parser.add_argument('--debugfs', help='Path to debugfs')
    816 
    817   parser.add_argument('--bitness', help='Bitness to check, 32|64|multilib|auto', default='auto')
    818 
    819   if len(sys.argv) == 1:
    820     art_apex_test_default(parser)
    821   else:
    822     args = parser.parse_args()
    823 
    824     if args is None:
    825       sys.exit(1)
    826 
    827     exit_code = art_apex_test_main(args)
    828     sys.exit(exit_code)
    829