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