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
18import argparse
19import fnmatch
20import logging
21import os
22import os.path
23import subprocess
24import sys
25import zipfile
26
27logging.basicConfig(format='%(message)s')
28
29
30class 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
41class 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
115class 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.
185class 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
295class 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
315class 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
335class 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
359class 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
438class 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
489class 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
512class 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
542class 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
574class 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
585class 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
600class 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
621class 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.
664def 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
757def 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
802if __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