1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2019 The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19import argparse
20import fnmatch
21import logging
22import os
23import os.path
24import shutil
25import subprocess
26import sys
27import zipfile
28
29logging.basicConfig(format='%(message)s')
30
31# Flavors of ART APEX package.
32FLAVOR_RELEASE = 'release'
33FLAVOR_DEBUG = 'debug'
34FLAVOR_TESTING = 'testing'
35FLAVOR_AUTO = 'auto'
36FLAVORS_ALL = [FLAVOR_RELEASE, FLAVOR_DEBUG, FLAVOR_TESTING, FLAVOR_AUTO]
37
38# Bitness options for APEX package
39BITNESS_32 = '32'
40BITNESS_64 = '64'
41BITNESS_MULTILIB = 'multilib'
42BITNESS_AUTO = 'auto'
43BITNESS_ALL = [BITNESS_32, BITNESS_64, BITNESS_MULTILIB, BITNESS_AUTO]
44
45# Architectures supported by APEX packages.
46ARCHS_32 = ['arm', 'x86']
47ARCHS_64 = ['arm64', 'riscv64', 'x86_64']
48
49# Multilib options
50MULTILIB_32 = '32'
51MULTILIB_64 = '64'
52MULTILIB_BOTH = 'both'
53MULTILIB_FIRST = 'first'
54
55# Directory containing ART tests within an ART APEX (if the package includes
56# any). ART test executables are installed in `bin/art/<arch>`. Segregating
57# tests by architecture is useful on devices supporting more than one
58# architecture, as it permits testing all of them using a single ART APEX
59# package.
60ART_TEST_DIR = 'bin/art'
61
62
63# Test if a given variable is set to a string "true".
64def isEnvTrue(var):
65  return var in os.environ and os.environ[var] == 'true'
66
67
68def extract_apex(apex_path, deapexer_path, debugfs_path, fsckerofs_path,
69                 tmpdir):
70  _, apex_name = os.path.split(apex_path)
71  extract_path = os.path.join(tmpdir, apex_name)
72  if os.path.exists(extract_path):
73    shutil.rmtree(extract_path)
74  subprocess.check_call([deapexer_path, '--debugfs', debugfs_path,
75                         '--fsckerofs', fsckerofs_path,
76                         'extract', apex_path, extract_path],
77                        stdout=subprocess.DEVNULL)
78  return extract_path
79
80
81class FSObject:
82  def __init__(self, name, is_dir, is_exec, is_symlink, size):
83    self.name = name
84    self.is_dir = is_dir
85    self.is_exec = is_exec
86    self.is_symlink = is_symlink
87    self.size = size
88
89  def __str__(self):
90    return '%s(dir=%r,exec=%r,symlink=%r,size=%d)' \
91             % (self.name, self.is_dir, self.is_exec, self.is_symlink, self.size)
92
93  def type_descr(self):
94    if self.is_dir:
95      return 'directory'
96    if self.is_symlink:
97      return 'symlink'
98    return 'file'
99
100
101class TargetApexProvider:
102  def __init__(self, apex):
103    self._folder_cache = {}
104    self._apex = apex
105
106  def get(self, path):
107    apex_dir, name = os.path.split(path)
108    if not apex_dir:
109      apex_dir = '.'
110    apex_map = self.read_dir(apex_dir)
111    return apex_map[name] if name in apex_map else None
112
113  def read_dir(self, apex_dir):
114    if apex_dir in self._folder_cache:
115      return self._folder_cache[apex_dir]
116    apex_map = {}
117    dirname = os.path.join(self._apex, apex_dir)
118    if os.path.exists(dirname):
119      for basename in os.listdir(dirname):
120        filepath = os.path.join(dirname, basename)
121        is_dir = os.path.isdir(filepath)
122        is_exec = os.access(filepath, os.X_OK)
123        is_symlink = os.path.islink(filepath)
124        if is_symlink:
125          # Report the length of the symlink's target's path as file size, like `ls`.
126          size = len(os.readlink(filepath))
127        else:
128          size = os.path.getsize(filepath)
129        apex_map[basename] = FSObject(basename, is_dir, is_exec, is_symlink, size)
130    self._folder_cache[apex_dir] = apex_map
131    return apex_map
132
133
134class HostApexProvider:
135  def __init__(self, apex, tmpdir):
136    self._tmpdir = tmpdir
137    self._folder_cache = {}
138    self._payload = os.path.join(self._tmpdir, 'apex_payload.zip')
139    # Extract payload to tmpdir.
140    apex_zip = zipfile.ZipFile(apex)
141    apex_zip.extract('apex_payload.zip', tmpdir)
142
143  def __del__(self):
144    # Delete temps.
145    if os.path.exists(self._payload):
146      os.remove(self._payload)
147
148  def get(self, path):
149    apex_dir, name = os.path.split(path)
150    if not apex_dir:
151      apex_dir = ''
152    apex_map = self.read_dir(apex_dir)
153    return apex_map[name] if name in apex_map else None
154
155  def read_dir(self, apex_dir):
156    if apex_dir in self._folder_cache:
157      return self._folder_cache[apex_dir]
158    if not self._folder_cache:
159      self.parse_zip()
160    if apex_dir in self._folder_cache:
161      return self._folder_cache[apex_dir]
162    return {}
163
164  def parse_zip(self):
165    apex_zip = zipfile.ZipFile(self._payload)
166    infos = apex_zip.infolist()
167    for zipinfo in infos:
168      path = zipinfo.filename
169
170      # Assume no empty file is stored.
171      assert path
172
173      def get_octal(val, index):
174        return (val >> (index * 3)) & 0x7
175
176      def bits_is_exec(val):
177        # TODO: Enforce group/other, too?
178        return get_octal(val, 2) & 1 == 1
179
180      is_zipinfo = True
181      while path:
182        apex_dir, base = os.path.split(path)
183        # TODO: If directories are stored, base will be empty.
184
185        if apex_dir not in self._folder_cache:
186          self._folder_cache[apex_dir] = {}
187        dir_map = self._folder_cache[apex_dir]
188        if base not in dir_map:
189          if is_zipinfo:
190            bits = (zipinfo.external_attr >> 16) & 0xFFFF
191            is_dir = get_octal(bits, 4) == 4
192            is_symlink = get_octal(bits, 4) == 2
193            is_exec = bits_is_exec(bits)
194            size = zipinfo.file_size
195          else:
196            is_exec = False  # Seems we can't get this easily?
197            is_symlink = False
198            is_dir = True
199            # Use a negative value as an indicator of undefined/unknown size.
200            size = -1
201          dir_map[base] = FSObject(base, is_dir, is_exec, is_symlink, size)
202        is_zipinfo = False
203        path = apex_dir
204
205
206# DO NOT USE DIRECTLY! This is an "abstract" base class.
207class Checker:
208  def __init__(self, provider):
209    self._provider = provider
210    self._errors = 0
211    self._expected_file_globs = set()
212
213  def fail(self, msg, *fail_args):
214    self._errors += 1
215    logging.error(msg, *fail_args)
216
217  def error_count(self):
218    return self._errors
219
220  def reset_errors(self):
221    self._errors = 0
222
223  def is_file(self, path):
224    fs_object = self._provider.get(path)
225    if fs_object is None:
226      return False, 'Could not find %s'
227    if fs_object.is_dir:
228      return False, '%s is a directory'
229    if fs_object.is_symlink:
230      return False, '%s is a symlink'
231    return True, ''
232
233  def is_dir(self, path):
234    fs_object = self._provider.get(path)
235    if fs_object is None:
236      return False, 'Could not find %s'
237    if not fs_object.is_dir:
238      return False, '%s is not a directory'
239    return True, ''
240
241  def check_file(self, path):
242    ok, msg = self.is_file(path)
243    if not ok:
244      self.fail(msg, path)
245    self._expected_file_globs.add(path)
246    return ok
247
248  def check_dir(self, path):
249    ok, msg = self.is_dir(path)
250    if not ok:
251      self.fail(msg, path)
252    self._expected_file_globs.add(path)
253    return ok
254
255  def check_optional_file(self, path):
256    if not self._provider.get(path):
257      return True
258    return self.check_file(path)
259
260  def check_executable(self, filename):
261    path = 'bin/%s' % filename
262    if not self.check_file(path):
263      return
264    if not self._provider.get(path).is_exec:
265      self.fail('%s is not executable', path)
266
267  def check_executable_symlink(self, filename):
268    path = 'bin/%s' % filename
269    fs_object = self._provider.get(path)
270    if fs_object is None:
271      self.fail('Could not find %s', path)
272      return
273    if fs_object.is_dir:
274      self.fail('%s is a directory', path)
275      return
276    if not fs_object.is_symlink:
277      self.fail('%s is not a symlink', path)
278    self._expected_file_globs.add(path)
279
280  def arch_dirs_for_path(self, path, multilib=None):
281    # Look for target-specific subdirectories for the given directory path.
282    # This is needed because the list of build targets is not propagated
283    # to this script.
284    #
285    # TODO(b/123602136): Pass build target information to this script and fix
286    # all places where this function in used (or similar workarounds).
287    dirs = []
288    for archs_per_bitness in self.possible_archs_per_bitness(multilib):
289      found_dir = False
290      for arch in archs_per_bitness:
291        dir = '%s/%s' % (path, arch)
292        found, _ = self.is_dir(dir)
293        if found:
294          found_dir = True
295          dirs.append(dir)
296      # At least one arch directory per bitness must exist.
297      if not found_dir:
298        self.fail('Arch directories missing in %s - expected at least one of %s',
299                  path, ', '.join(archs_per_bitness))
300    return dirs
301
302  def check_art_test_executable(self, filename, multilib=None):
303    for dir in self.arch_dirs_for_path(ART_TEST_DIR, multilib):
304      test_path = '%s/%s' % (dir, filename)
305      self._expected_file_globs.add(test_path)
306      file_obj = self._provider.get(test_path)
307      if not file_obj:
308        self.fail('ART test binary missing: %s', test_path)
309      elif not file_obj.is_exec:
310        self.fail('%s is not executable', test_path)
311
312  def check_art_test_data(self, filename):
313    for dir in self.arch_dirs_for_path(ART_TEST_DIR):
314      if not self.check_file('%s/%s' % (dir, filename)):
315        return
316
317  def check_single_library(self, filename):
318    lib_path = 'lib/%s' % filename
319    lib64_path = 'lib64/%s' % filename
320    lib_is_file, _ = self.is_file(lib_path)
321    if lib_is_file:
322      self._expected_file_globs.add(lib_path)
323    lib64_is_file, _ = self.is_file(lib64_path)
324    if lib64_is_file:
325      self._expected_file_globs.add(lib64_path)
326    if not lib_is_file and not lib64_is_file:
327      self.fail('Library missing: %s', filename)
328
329  def check_java_library(self, basename):
330    return self.check_file('javalib/%s.jar' % basename)
331
332  def ignore_path(self, path_glob):
333    self._expected_file_globs.add(path_glob)
334
335  def check_optional_art_test_executable(self, filename):
336    for archs_per_bitness in self.possible_archs_per_bitness():
337      for arch in archs_per_bitness:
338        self.ignore_path('%s/%s/%s' % (ART_TEST_DIR, arch, filename))
339
340  def check_no_superfluous_files(self):
341    def recurse(dir_path):
342      paths = []
343      for name, fsobj in sorted(self._provider.read_dir(dir_path).items(), key=lambda p: p[0]):
344        if name in ('.', '..'):
345          continue
346        path = os.path.join(dir_path, name)
347        paths.append(path)
348        if fsobj.is_dir:
349          recurse(path)
350      for path_glob in self._expected_file_globs:
351        paths = [path for path in paths if not fnmatch.fnmatch(path, path_glob)]
352      for unexpected_path in paths:
353        fs_object = self._provider.get(unexpected_path)
354        self.fail('Unexpected %s: %s', fs_object.type_descr(), unexpected_path)
355    recurse('')
356
357  # Just here for docs purposes, even if it isn't good Python style.
358
359  def check_symlinked_multilib_executable(self, filename):
360    """Check bin/filename32, and/or bin/filename64, with symlink bin/filename."""
361    raise NotImplementedError
362
363  def check_symlinked_first_executable(self, filename):
364    """Check bin/filename32, and/or bin/filename64, with symlink bin/filename."""
365    raise NotImplementedError
366
367  def check_native_library(self, basename):
368    """Check lib/basename.so, and/or lib64/basename.so."""
369    raise NotImplementedError
370
371  def check_optional_native_library(self, basename_glob):
372    """Allow lib/basename.so and/or lib64/basename.so to exist."""
373    raise NotImplementedError
374
375  def check_prefer64_library(self, basename):
376    """Check lib64/basename.so, or lib/basename.so on 32 bit only."""
377    raise NotImplementedError
378
379  def possible_archs_per_bitness(self, multilib=None):
380    """Returns a list of lists of possible architectures per bitness."""
381    raise NotImplementedError
382
383class Arch32Checker(Checker):
384  def __init__(self, provider):
385    super().__init__(provider)
386    self.lib_dirs = ['lib']
387
388  def check_symlinked_multilib_executable(self, filename):
389    self.check_executable('%s32' % filename)
390    self.check_executable_symlink(filename)
391
392  def check_symlinked_first_executable(self, filename):
393    self.check_executable('%s32' % filename)
394    self.check_executable_symlink(filename)
395
396  def check_native_library(self, basename):
397    # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
398    # the precision of this test?
399    self.check_file('lib/%s.so' % basename)
400
401  def check_optional_native_library(self, basename_glob):
402    self.ignore_path('lib/%s.so' % basename_glob)
403
404  def check_prefer64_library(self, basename):
405    self.check_native_library(basename)
406
407  def possible_archs_per_bitness(self, multilib=None):
408    return [ARCHS_32]
409
410class Arch64Checker(Checker):
411  def __init__(self, provider):
412    super().__init__(provider)
413    self.lib_dirs = ['lib64']
414
415  def check_symlinked_multilib_executable(self, filename):
416    self.check_executable('%s64' % filename)
417    self.check_executable_symlink(filename)
418
419  def check_symlinked_first_executable(self, filename):
420    self.check_executable('%s64' % filename)
421    self.check_executable_symlink(filename)
422
423  def check_native_library(self, basename):
424    # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
425    # the precision of this test?
426    self.check_file('lib64/%s.so' % basename)
427
428  def check_optional_native_library(self, basename_glob):
429    self.ignore_path('lib64/%s.so' % basename_glob)
430
431  def check_prefer64_library(self, basename):
432    self.check_native_library(basename)
433
434  def possible_archs_per_bitness(self, multilib=None):
435    return [ARCHS_64]
436
437
438class MultilibChecker(Checker):
439  def __init__(self, provider):
440    super().__init__(provider)
441    self.lib_dirs = ['lib', 'lib64']
442
443  def check_symlinked_multilib_executable(self, filename):
444    self.check_executable('%s32' % filename)
445    self.check_executable('%s64' % filename)
446    self.check_executable_symlink(filename)
447
448  def check_symlinked_first_executable(self, filename):
449    self.check_executable('%s64' % filename)
450    self.check_executable_symlink(filename)
451
452  def check_native_library(self, basename):
453    # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
454    # the precision of this test?
455    self.check_file('lib/%s.so' % basename)
456    self.check_file('lib64/%s.so' % basename)
457
458  def check_optional_native_library(self, basename_glob):
459    self.ignore_path('lib/%s.so' % basename_glob)
460    self.ignore_path('lib64/%s.so' % basename_glob)
461
462  def check_prefer64_library(self, basename):
463    self.check_file('lib64/%s.so' % basename)
464
465  def possible_archs_per_bitness(self, multilib=None):
466    if multilib is None or multilib == MULTILIB_BOTH:
467      return [ARCHS_32, ARCHS_64]
468    if multilib == MULTILIB_FIRST or multilib == MULTILIB_64:
469      return [ARCHS_64]
470    elif multilib == MULTILIB_32:
471      return [ARCHS_32]
472    self.fail('Unrecognized multilib option "%s"', multilib)
473
474
475class ReleaseChecker:
476  def __init__(self, checker):
477    self._checker = checker
478
479  def __str__(self):
480    return 'Release Checker'
481
482  def run(self):
483    # Check the root directory.
484    self._checker.check_dir('bin')
485    self._checker.check_dir('etc')
486    self._checker.check_dir('javalib')
487    for lib_dir in self._checker.lib_dirs:
488      self._checker.check_dir(lib_dir)
489    self._checker.check_file('apex_manifest.pb')
490
491    # Check etc.
492    self._checker.check_file('etc/boot-image.prof')
493    self._checker.check_dir('etc/classpaths')
494    self._checker.check_file('etc/classpaths/bootclasspath.pb')
495    self._checker.check_file('etc/classpaths/systemserverclasspath.pb')
496    self._checker.check_dir('etc/compatconfig')
497    self._checker.check_file('etc/compatconfig/libcore-platform-compat-config.xml')
498    self._checker.check_file('etc/init.rc')
499    self._checker.check_file('etc/linker.config.pb')
500    self._checker.check_file('etc/sdkinfo.pb')
501
502    # Check flagging files that don't get added in builds on master-art.
503    # TODO(b/345713436): Make flags work on master-art.
504    self._checker.check_optional_file('etc/aconfig_flags.pb')
505    self._checker.check_optional_file('etc/flag.map')
506    self._checker.check_optional_file('etc/flag.val')
507    self._checker.check_optional_file('etc/package.map')
508
509    # Check binaries for ART.
510    self._checker.check_executable('dexdump')
511    self._checker.check_executable('dexlist')
512    self._checker.check_executable('dexoptanalyzer')
513    self._checker.check_executable('profman')
514    self._checker.check_symlinked_multilib_executable('dalvikvm')
515
516    # Check exported libraries for ART.
517    self._checker.check_native_library('libdexfile')
518    self._checker.check_native_library('libnativebridge')
519    self._checker.check_native_library('libnativehelper')
520    self._checker.check_native_library('libnativeloader')
521
522    # Check internal libraries for ART.
523    self._checker.check_native_library('libadbconnection')
524    self._checker.check_native_library('libart')
525    self._checker.check_native_library('libart-disassembler')
526    self._checker.check_native_library('libartbase')
527    self._checker.check_native_library('libartpalette')
528    self._checker.check_native_library('libarttools')
529    self._checker.check_native_library('libdt_fd_forward')
530    self._checker.check_native_library('libopenjdkjvm')
531    self._checker.check_native_library('libopenjdkjvmti')
532    self._checker.check_native_library('libprofile')
533    self._checker.check_native_library('libsigchain')
534
535    # Check Java libraries for Managed Core Library.
536    self._checker.check_java_library('apache-xml')
537    self._checker.check_java_library('bouncycastle')
538    self._checker.check_java_library('core-libart')
539    self._checker.check_java_library('core-oj')
540    self._checker.check_java_library('okhttp')
541    if isEnvTrue('EMMA_INSTRUMENT_FRAMEWORK'):
542      # In coverage builds jacoco is added to the list of ART apex jars.
543      self._checker.check_java_library('jacocoagent')
544
545    # Check internal native libraries for Managed Core Library.
546    self._checker.check_native_library('libjavacore')
547    self._checker.check_native_library('libopenjdk')
548
549    # Check internal native library dependencies.
550    #
551    # Any internal dependency not listed here will cause a failure in
552    # NoSuperfluousFilesChecker. Internal dependencies are generally just
553    # implementation details, but in the release package we want to track them
554    # because a) they add to the package size and the RAM usage (in particular
555    # if the library is also present in /system or another APEX and hence might
556    # get loaded twice through linker namespace separation), and b) we need to
557    # catch invalid dependencies on /system or other APEXes that should go
558    # through an exported library with stubs (b/128708192 tracks implementing a
559    # better approach for that).
560    self._checker.check_native_library('libbase')
561    self._checker.check_native_library('libc++')
562    self._checker.check_native_library('libdt_socket')
563    self._checker.check_native_library('libjdwp')
564    self._checker.check_native_library('liblz4')
565    self._checker.check_native_library('liblzma')
566    self._checker.check_native_library('libnpt')
567    self._checker.check_native_library('libunwindstack')
568
569    # Allow extra dependencies that appear in ASAN builds.
570    self._checker.check_optional_native_library('libclang_rt.asan*')
571    self._checker.check_optional_native_library('libclang_rt.hwasan*')
572    self._checker.check_optional_native_library('libclang_rt.ubsan*')
573
574
575class ReleaseTargetChecker:
576  def __init__(self, checker):
577    self._checker = checker
578
579  def __str__(self):
580    return 'Release (Target) Checker'
581
582  def run(self):
583    # We don't check for the presence of the JSON APEX manifest (file
584    # `apex_manifest.json`, only present in target APEXes), as it is only
585    # included for compatibility reasons with Android Q and will likely be
586    # removed in Android R.
587
588    # Check binaries for ART.
589    self._checker.check_executable('art_boot')
590    self._checker.check_executable('art_exec')
591    self._checker.check_executable('artd')
592    self._checker.check_executable('dexopt_chroot_setup')
593    self._checker.check_executable('oatdump')
594    self._checker.check_executable('odrefresh')
595    self._checker.check_symlinked_multilib_executable('dex2oat')
596
597    # Check internal libraries for ART.
598    self._checker.check_native_library('libartservice')
599    self._checker.check_native_library('libperfetto_hprof')
600
601    # Check internal Java libraries
602    self._checker.check_java_library('service-art')
603    self._checker.check_file('javalib/service-art.jar.prof')
604
605    # Check exported native libraries for Managed Core Library.
606    self._checker.check_native_library('libandroidio')
607
608    # Check internal native library dependencies.
609    self._checker.check_native_library('libexpat')
610
611
612class ReleaseHostChecker:
613  def __init__(self, checker):
614    self._checker = checker
615
616  def __str__(self):
617    return 'Release (Host) Checker'
618
619  def run(self):
620    # Check binaries for ART.
621    self._checker.check_executable('hprof-conv')
622    self._checker.check_symlinked_first_executable('dex2oatd')
623    self._checker.check_symlinked_first_executable('dex2oat')
624
625    # Check exported native libraries for Managed Core Library.
626    self._checker.check_native_library('libicu')
627    self._checker.check_native_library('libandroidio')
628
629    # Check internal libraries for Managed Core Library.
630    self._checker.check_native_library('libexpat-host')
631    self._checker.check_native_library('libz-host')
632
633
634class DebugChecker:
635  def __init__(self, checker):
636    self._checker = checker
637
638  def __str__(self):
639    return 'Debug Checker'
640
641  def run(self):
642    # Check binaries for ART.
643    self._checker.check_executable('dexanalyze')
644    self._checker.check_symlinked_multilib_executable('imgdiag')
645
646    # Check debug binaries for ART.
647    self._checker.check_executable('dexoptanalyzerd')
648    self._checker.check_symlinked_multilib_executable('imgdiagd')
649    self._checker.check_executable('profmand')
650
651    # Check exported libraries for ART.
652    self._checker.check_native_library('libdexfiled')
653
654    # Check internal libraries for ART.
655    self._checker.check_native_library('libadbconnectiond')
656    self._checker.check_native_library('libartbased')
657    self._checker.check_native_library('libartd')
658    self._checker.check_native_library('libartd-disassembler')
659    self._checker.check_native_library('libopenjdkjvmd')
660    self._checker.check_native_library('libopenjdkjvmtid')
661    self._checker.check_native_library('libprofiled')
662
663    # Check internal libraries for Managed Core Library.
664    self._checker.check_native_library('libopenjdkd')
665
666
667class DebugTargetChecker:
668  def __init__(self, checker):
669    self._checker = checker
670
671  def __str__(self):
672    return 'Debug (Target) Checker'
673
674  def run(self):
675    # Check ART debug binaries.
676    self._checker.check_executable('oatdumpd')
677    self._checker.check_symlinked_multilib_executable('dex2oatd')
678
679    # Check ART internal libraries.
680    self._checker.check_native_library('libartserviced')
681    self._checker.check_native_library('libperfetto_hprofd')
682
683    # Check internal native library dependencies.
684    #
685    # Like in the release package, we check that we don't get other dependencies
686    # besides those listed here. In this case the concern is not bloat, but
687    # rather that we don't get behavioural differences between user (release)
688    # and userdebug/eng builds, which could happen if the debug package has
689    # duplicate library instances where releases don't. In other words, it's
690    # uncontroversial to add debug-only dependencies, as long as they don't make
691    # assumptions on having a single global state (ideally they should have
692    # double_loadable:true, cf. go/double_loadable). Also, like in the release
693    # package we need to look out for dependencies that should go through
694    # exported library stubs (until b/128708192 is fixed).
695    #
696    # (There are currently no debug-only native libraries.)
697
698
699class TestingTargetChecker:
700  def __init__(self, checker):
701    self._checker = checker
702
703  def __str__(self):
704    return 'Testing (Target) Checker'
705
706  def run(self):
707    # Check test directories.
708    self._checker.check_dir(ART_TEST_DIR)
709    for arch_dir in self._checker.arch_dirs_for_path(ART_TEST_DIR):
710      self._checker.check_dir(arch_dir)
711
712    # Check ART test binaries.
713    self._checker.check_art_test_executable('art_artd_tests')
714    self._checker.check_art_test_executable('art_cmdline_tests')
715    self._checker.check_art_test_executable('art_compiler_tests')
716    self._checker.check_art_test_executable('art_dex2oat_tests')
717    self._checker.check_art_test_executable('art_dexanalyze_tests')
718    self._checker.check_art_test_executable('art_dexdump_tests')
719    self._checker.check_art_test_executable('art_dexlist_tests')
720    self._checker.check_art_test_executable('art_dexoptanalyzer_tests')
721    self._checker.check_art_test_executable('art_disassembler_tests')
722    self._checker.check_art_test_executable('art_imgdiag_tests')
723    self._checker.check_art_test_executable('art_libartbase_tests')
724    self._checker.check_art_test_executable('art_libartpalette_tests')
725    self._checker.check_art_test_executable('art_libartservice_tests')
726    self._checker.check_art_test_executable('art_libarttools_tests')
727    self._checker.check_art_test_executable('art_libdexfile_support_tests')
728    self._checker.check_art_test_executable('art_libdexfile_tests')
729    self._checker.check_art_test_executable('art_libprofile_tests')
730    self._checker.check_art_test_executable('art_oatdump_tests')
731    self._checker.check_art_test_executable('art_odrefresh_tests')
732    self._checker.check_art_test_executable('art_profman_tests')
733    self._checker.check_art_test_executable('art_runtime_tests')
734    self._checker.check_art_test_executable('art_sigchain_tests')
735
736    # Check ART test tools.
737    self._checker.check_executable('signal_dumper')
738
739    # Check ART jar files which are needed for gtests.
740    self._checker.check_art_test_data('art-gtest-jars-AbstractMethod.jar')
741    self._checker.check_art_test_data('art-gtest-jars-ArrayClassWithUnresolvedComponent.dex')
742    self._checker.check_art_test_data('art-gtest-jars-MyClassNatives.jar')
743    self._checker.check_art_test_data('art-gtest-jars-Main.jar')
744    self._checker.check_art_test_data('art-gtest-jars-ProtoCompare.jar')
745    self._checker.check_art_test_data('art-gtest-jars-Transaction.jar')
746    self._checker.check_art_test_data('art-gtest-jars-VerifierDepsMulti.dex')
747    self._checker.check_art_test_data('art-gtest-jars-Nested.jar')
748    self._checker.check_art_test_data('art-gtest-jars-MyClass.jar')
749    self._checker.check_art_test_data('art-gtest-jars-ManyMethods.jar')
750    self._checker.check_art_test_data('art-gtest-jars-GetMethodSignature.jar')
751    self._checker.check_art_test_data('art-gtest-jars-Lookup.jar')
752    self._checker.check_art_test_data('art-gtest-jars-Instrumentation.jar')
753    self._checker.check_art_test_data('art-gtest-jars-MainUncompressedAligned.jar')
754    self._checker.check_art_test_data('art-gtest-jars-ForClassLoaderD.jar')
755    self._checker.check_art_test_data('art-gtest-jars-ForClassLoaderC.jar')
756    self._checker.check_art_test_data('art-gtest-jars-ErroneousA.jar')
757    self._checker.check_art_test_data('art-gtest-jars-HiddenApiSignatures.jar')
758    self._checker.check_art_test_data('art-gtest-jars-ForClassLoaderB.jar')
759    self._checker.check_art_test_data('art-gtest-jars-LinkageTest.dex')
760    self._checker.check_art_test_data('art-gtest-jars-MethodTypes.jar')
761    self._checker.check_art_test_data('art-gtest-jars-ErroneousInit.jar')
762    self._checker.check_art_test_data('art-gtest-jars-VerifierDeps.dex')
763    self._checker.check_art_test_data('art-gtest-jars-StringLiterals.jar')
764    self._checker.check_art_test_data('art-gtest-jars-XandY.jar')
765    self._checker.check_art_test_data('art-gtest-jars-ExceptionHandle.jar')
766    self._checker.check_art_test_data('art-gtest-jars-ImageLayoutB.jar')
767    self._checker.check_art_test_data('art-gtest-jars-Interfaces.jar')
768    self._checker.check_art_test_data('art-gtest-jars-IMTB.jar')
769    self._checker.check_art_test_data('art-gtest-jars-Extension2.jar')
770    self._checker.check_art_test_data('art-gtest-jars-Extension1.jar')
771    self._checker.check_art_test_data('art-gtest-jars-MainEmptyUncompressedAligned.jar')
772    self._checker.check_art_test_data('art-gtest-jars-ErroneousB.jar')
773    self._checker.check_art_test_data('art-gtest-jars-MultiDexModifiedSecondary.jar')
774    self._checker.check_art_test_data('art-gtest-jars-NonStaticLeafMethods.jar')
775    self._checker.check_art_test_data('art-gtest-jars-DefaultMethods.jar')
776    self._checker.check_art_test_data('art-gtest-jars-MultiDexUncompressedAligned.jar')
777    self._checker.check_art_test_data('art-gtest-jars-StaticsFromCode.jar')
778    self._checker.check_art_test_data('art-gtest-jars-ProfileTestMultiDex.jar')
779    self._checker.check_art_test_data('art-gtest-jars-VerifySoftFailDuringClinit.dex')
780    self._checker.check_art_test_data('art-gtest-jars-MainStripped.jar')
781    self._checker.check_art_test_data('art-gtest-jars-ForClassLoaderA.jar')
782    self._checker.check_art_test_data('art-gtest-jars-StaticLeafMethods.jar')
783    self._checker.check_art_test_data('art-gtest-jars-MultiDex.jar')
784    self._checker.check_art_test_data('art-gtest-jars-Packages.jar')
785    self._checker.check_art_test_data('art-gtest-jars-ProtoCompare2.jar')
786    self._checker.check_art_test_data('art-gtest-jars-Statics.jar')
787    self._checker.check_art_test_data('art-gtest-jars-AllFields.jar')
788    self._checker.check_art_test_data('art-gtest-jars-IMTA.jar')
789    self._checker.check_art_test_data('art-gtest-jars-ImageLayoutA.jar')
790    self._checker.check_art_test_data('art-gtest-jars-MainEmptyUncompressed.jar')
791    self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexTestDex.jar')
792    self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexPublicSdkDex.dex')
793    self._checker.check_art_test_data('art-gtest-jars-SuperWithAccessChecks.dex')
794
795    # Fuzzer cases
796    self._checker.check_art_test_data('fuzzer_corpus.zip')
797
798
799class NoSuperfluousFilesChecker:
800  def __init__(self, checker):
801    self._checker = checker
802
803  def __str__(self):
804    return 'No superfluous files checker'
805
806  def run(self):
807    self._checker.check_no_superfluous_files()
808
809
810class List:
811  def __init__(self, provider, print_size=False):
812    self._provider = provider
813    self._print_size = print_size
814
815  def print_list(self):
816
817    def print_list_rec(path):
818      apex_map = self._provider.read_dir(path)
819      if apex_map is None:
820        return
821      apex_map = dict(apex_map)
822      if '.' in apex_map:
823        del apex_map['.']
824      if '..' in apex_map:
825        del apex_map['..']
826      for (_, val) in sorted(apex_map.items()):
827        val_path = os.path.join(path, val.name)
828        if self._print_size:
829          if val.size < 0:
830            print('[    n/a    ]  %s' % val_path)
831          else:
832            print('[%11d]  %s' % (val.size, val_path))
833        else:
834          print(val_path)
835        if val.is_dir:
836          print_list_rec(val_path)
837
838    print_list_rec('')
839
840
841class Tree:
842  def __init__(self, provider, title, print_size=False):
843    print('%s' % title)
844    self._provider = provider
845    self._has_next_list = []
846    self._print_size = print_size
847
848  @staticmethod
849  def get_vertical(has_next_list):
850    string = ''
851    for v in has_next_list:
852      string += '%s   ' % ('│' if v else ' ')
853    return string
854
855  @staticmethod
856  def get_last_vertical(last):
857    return '└── ' if last else '├── '
858
859  def print_tree(self):
860
861    def print_tree_rec(path):
862      apex_map = self._provider.read_dir(path)
863      if apex_map is None:
864        return
865      apex_map = dict(apex_map)
866      if '.' in apex_map:
867        del apex_map['.']
868      if '..' in apex_map:
869        del apex_map['..']
870      key_list = list(sorted(apex_map.keys()))
871      for i, key in enumerate(key_list):
872        prev = self.get_vertical(self._has_next_list)
873        last = self.get_last_vertical(i == len(key_list) - 1)
874        val = apex_map[key]
875        if self._print_size:
876          if val.size < 0:
877            print('%s%s[    n/a    ]  %s' % (prev, last, val.name))
878          else:
879            print('%s%s[%11d]  %s' % (prev, last, val.size, val.name))
880        else:
881          print('%s%s%s' % (prev, last, val.name))
882        if val.is_dir:
883          self._has_next_list.append(i < len(key_list) - 1)
884          val_path = os.path.join(path, val.name)
885          print_tree_rec(val_path)
886          self._has_next_list.pop()
887
888    print_tree_rec('')
889
890
891# Note: do not sys.exit early, for __del__ cleanup.
892def art_apex_test_main(test_args):
893  if test_args.host and test_args.flattened:
894    logging.error('Both of --host and --flattened set')
895    return 1
896  if test_args.list and test_args.tree:
897    logging.error('Both of --list and --tree set')
898    return 1
899  if test_args.size and not (test_args.list or test_args.tree):
900    logging.error('--size set but neither --list nor --tree set')
901    return 1
902  if not test_args.flattened and not test_args.tmpdir:
903    logging.error('Need a tmpdir.')
904    return 1
905  if not test_args.flattened and not test_args.host:
906    if not test_args.deapexer:
907      logging.error('Need deapexer.')
908      return 1
909    if not test_args.debugfs:
910      logging.error('Need debugfs.')
911      return 1
912    if not test_args.fsckerofs:
913      logging.error('Need fsck.erofs.')
914      return 1
915
916  if test_args.host:
917    # Host APEX.
918    if test_args.flavor not in [FLAVOR_DEBUG, FLAVOR_AUTO]:
919      logging.error('Using option --host with non-Debug APEX')
920      return 1
921    # Host APEX is always a debug flavor (for now).
922    test_args.flavor = FLAVOR_DEBUG
923  else:
924    # Device APEX.
925    if test_args.flavor == FLAVOR_AUTO:
926      logging.warning('--flavor=auto, trying to autodetect. This may be incorrect!')
927      # The order of flavors in the list below matters, as the release tag (empty string) will
928      # match any package name.
929      for flavor in [ FLAVOR_DEBUG, FLAVOR_TESTING, FLAVOR_RELEASE ]:
930        flavor_tag = flavor
931        # Special handling for the release flavor, whose name is no longer part of the Release ART
932        # APEX file name (`com.android.art.capex` / `com.android.art`).
933        if flavor == FLAVOR_RELEASE:
934          flavor_tag = ''
935        flavor_pattern = '*.%s*' % flavor_tag
936        if fnmatch.fnmatch(test_args.apex, flavor_pattern):
937          test_args.flavor = flavor
938          logging.warning('  Detected %s flavor', flavor)
939          break
940      if test_args.flavor == FLAVOR_AUTO:
941        logging.error('  Could not detect APEX flavor, neither %s, %s nor %s for \'%s\'',
942                    FLAVOR_RELEASE, FLAVOR_DEBUG, FLAVOR_TESTING, test_args.apex)
943        return 1
944
945  try:
946    if test_args.host:
947      apex_provider = HostApexProvider(test_args.apex, test_args.tmpdir)
948    else:
949      apex_dir = test_args.apex
950      if not test_args.flattened:
951        # Extract the apex. It would be nice to use the output from "deapexer list"
952        # to avoid this work, but it doesn't provide info about executable bits.
953        apex_dir = extract_apex(test_args.apex, test_args.deapexer, test_args.debugfs,
954                                test_args.fsckerofs, test_args.tmpdir)
955      apex_provider = TargetApexProvider(apex_dir)
956  except (zipfile.BadZipFile, zipfile.LargeZipFile) as e:
957    logging.error('Failed to create provider: %s', e)
958    return 1
959
960  if test_args.tree:
961    Tree(apex_provider, test_args.apex, test_args.size).print_tree()
962    return 0
963  if test_args.list:
964    List(apex_provider, test_args.size).print_list()
965    return 0
966
967  checkers = []
968  if test_args.bitness == BITNESS_AUTO:
969    logging.warning('--bitness=auto, trying to autodetect. This may be incorrect!')
970    has_32 = apex_provider.get('lib') is not None
971    has_64 = apex_provider.get('lib64') is not None
972    if has_32 and has_64:
973      logging.warning('  Detected multilib')
974      test_args.bitness = BITNESS_MULTILIB
975    elif has_32:
976      logging.warning('  Detected 32-only')
977      test_args.bitness = BITNESS_32
978    elif has_64:
979      logging.warning('  Detected 64-only')
980      test_args.bitness = BITNESS_64
981    else:
982      logging.error('  Could not detect bitness, neither lib nor lib64 contained.')
983      List(apex_provider).print_list()
984      return 1
985
986  if test_args.bitness == BITNESS_32:
987    base_checker = Arch32Checker(apex_provider)
988  elif test_args.bitness == BITNESS_64:
989    base_checker = Arch64Checker(apex_provider)
990  else:
991    assert test_args.bitness == BITNESS_MULTILIB
992    base_checker = MultilibChecker(apex_provider)
993
994  checkers.append(ReleaseChecker(base_checker))
995  if test_args.host:
996    checkers.append(ReleaseHostChecker(base_checker))
997  else:
998    checkers.append(ReleaseTargetChecker(base_checker))
999  if test_args.flavor == FLAVOR_DEBUG or test_args.flavor == FLAVOR_TESTING:
1000    checkers.append(DebugChecker(base_checker))
1001    if not test_args.host:
1002      checkers.append(DebugTargetChecker(base_checker))
1003  if test_args.flavor == FLAVOR_TESTING:
1004    checkers.append(TestingTargetChecker(base_checker))
1005
1006  # This checker must be last.
1007  checkers.append(NoSuperfluousFilesChecker(base_checker))
1008
1009  failed = False
1010  for checker in checkers:
1011    logging.info('%s...', checker)
1012    checker.run()
1013    if base_checker.error_count() > 0:
1014      logging.error('%s FAILED', checker)
1015      failed = True
1016    else:
1017      logging.info('%s SUCCEEDED', checker)
1018    base_checker.reset_errors()
1019
1020  return 1 if failed else 0
1021
1022
1023def art_apex_test_default(test_parser):
1024  if 'ANDROID_PRODUCT_OUT' not in os.environ:
1025    logging.error('No-argument use requires ANDROID_PRODUCT_OUT')
1026    sys.exit(1)
1027  product_out = os.environ['ANDROID_PRODUCT_OUT']
1028  if 'ANDROID_HOST_OUT' not in os.environ:
1029    logging.error('No-argument use requires ANDROID_HOST_OUT')
1030    sys.exit(1)
1031  host_out = os.environ['ANDROID_HOST_OUT']
1032
1033  test_args = test_parser.parse_args(['unused'])  # For consistency.
1034  test_args.debugfs = '%s/bin/debugfs' % host_out
1035  test_args.fsckerofs = '%s/bin/fsck.erofs' % host_out
1036  test_args.tmpdir = '.'
1037  test_args.tree = False
1038  test_args.list = False
1039  test_args.bitness = BITNESS_AUTO
1040  failed = False
1041
1042  if not os.path.exists(test_args.debugfs):
1043    logging.error('Cannot find debugfs (default path %s). Please build it, e.g., m debugfs',
1044                  test_args.debugfs)
1045    sys.exit(1)
1046
1047  # TODO: Add host support.
1048  # TODO: Add support for flattened APEX packages.
1049  configs = [
1050    {'name': 'com.android.art.capex',         'flavor': FLAVOR_RELEASE, 'host': False},
1051    {'name': 'com.android.art.debug.capex',   'flavor': FLAVOR_DEBUG,   'host': False},
1052    # Note: The Testing ART APEX is not a Compressed APEX.
1053    {'name': 'com.android.art.testing.apex',  'flavor': FLAVOR_TESTING, 'host': False},
1054  ]
1055
1056  for config in configs:
1057    logging.info(config['name'])
1058    # TODO: Host will need different path.
1059    test_args.apex = '%s/system/apex/%s' % (product_out, config['name'])
1060    if not os.path.exists(test_args.apex):
1061      failed = True
1062      logging.error('Cannot find APEX %s. Please build it first.', test_args.apex)
1063      continue
1064    test_args.flavor = config['flavor']
1065    test_args.host = config['host']
1066    failed = art_apex_test_main(test_args) != 0
1067
1068  if failed:
1069    sys.exit(1)
1070
1071
1072if __name__ == '__main__':
1073  parser = argparse.ArgumentParser(description='Check integrity of an ART APEX.')
1074
1075  parser.add_argument('apex', help='APEX file input')
1076
1077  parser.add_argument('--host', help='Check as host APEX', action='store_true')
1078
1079  parser.add_argument('--flattened', help='Check as flattened (target) APEX', action='store_true')
1080
1081  parser.add_argument('--flavor', help='Check as FLAVOR APEX', choices=FLAVORS_ALL,
1082                      default=FLAVOR_AUTO)
1083
1084  parser.add_argument('--list', help='List all files', action='store_true')
1085  parser.add_argument('--tree', help='Print directory tree', action='store_true')
1086  parser.add_argument('--size', help='Print file sizes', action='store_true')
1087
1088  parser.add_argument('--tmpdir', help='Directory for temp files')
1089  parser.add_argument('--deapexer', help='Path to deapexer')
1090  parser.add_argument('--debugfs', help='Path to debugfs')
1091  parser.add_argument('--fsckerofs', help='Path to fsck.erofs')
1092
1093  parser.add_argument('--bitness', help='Bitness to check', choices=BITNESS_ALL,
1094                      default=BITNESS_AUTO)
1095
1096  if len(sys.argv) == 1:
1097    art_apex_test_default(parser)
1098  else:
1099    args = parser.parse_args()
1100
1101    if args is None:
1102      sys.exit(1)
1103
1104    exit_code = art_apex_test_main(args)
1105    sys.exit(exit_code)
1106