#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2019 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import argparse import fnmatch import logging import os import os.path import shutil import subprocess import sys import zipfile logging.basicConfig(format='%(message)s') # Flavors of ART APEX package. FLAVOR_RELEASE = 'release' FLAVOR_DEBUG = 'debug' FLAVOR_TESTING = 'testing' FLAVOR_AUTO = 'auto' FLAVORS_ALL = [FLAVOR_RELEASE, FLAVOR_DEBUG, FLAVOR_TESTING, FLAVOR_AUTO] # Bitness options for APEX package BITNESS_32 = '32' BITNESS_64 = '64' BITNESS_MULTILIB = 'multilib' BITNESS_AUTO = 'auto' BITNESS_ALL = [BITNESS_32, BITNESS_64, BITNESS_MULTILIB, BITNESS_AUTO] # Architectures supported by APEX packages. ARCHS_32 = ['arm', 'x86'] ARCHS_64 = ['arm64', 'riscv64', 'x86_64'] # Multilib options MULTILIB_32 = '32' MULTILIB_64 = '64' MULTILIB_BOTH = 'both' MULTILIB_FIRST = 'first' # Directory containing ART tests within an ART APEX (if the package includes # any). ART test executables are installed in `bin/art/`. Segregating # tests by architecture is useful on devices supporting more than one # architecture, as it permits testing all of them using a single ART APEX # package. ART_TEST_DIR = 'bin/art' # Test if a given variable is set to a string "true". def isEnvTrue(var): return var in os.environ and os.environ[var] == 'true' def extract_apex(apex_path, deapexer_path, debugfs_path, fsckerofs_path, tmpdir): _, apex_name = os.path.split(apex_path) extract_path = os.path.join(tmpdir, apex_name) if os.path.exists(extract_path): shutil.rmtree(extract_path) subprocess.check_call([deapexer_path, '--debugfs', debugfs_path, '--fsckerofs', fsckerofs_path, 'extract', apex_path, extract_path], stdout=subprocess.DEVNULL) return extract_path class FSObject: def __init__(self, name, is_dir, is_exec, is_symlink, size): self.name = name self.is_dir = is_dir self.is_exec = is_exec self.is_symlink = is_symlink self.size = size def __str__(self): return '%s(dir=%r,exec=%r,symlink=%r,size=%d)' \ % (self.name, self.is_dir, self.is_exec, self.is_symlink, self.size) def type_descr(self): if self.is_dir: return 'directory' if self.is_symlink: return 'symlink' return 'file' class TargetApexProvider: def __init__(self, apex): self._folder_cache = {} self._apex = apex def get(self, path): apex_dir, name = os.path.split(path) if not apex_dir: apex_dir = '.' apex_map = self.read_dir(apex_dir) return apex_map[name] if name in apex_map else None def read_dir(self, apex_dir): if apex_dir in self._folder_cache: return self._folder_cache[apex_dir] apex_map = {} dirname = os.path.join(self._apex, apex_dir) if os.path.exists(dirname): for basename in os.listdir(dirname): filepath = os.path.join(dirname, basename) is_dir = os.path.isdir(filepath) is_exec = os.access(filepath, os.X_OK) is_symlink = os.path.islink(filepath) if is_symlink: # Report the length of the symlink's target's path as file size, like `ls`. size = len(os.readlink(filepath)) else: size = os.path.getsize(filepath) apex_map[basename] = FSObject(basename, is_dir, is_exec, is_symlink, size) self._folder_cache[apex_dir] = apex_map return apex_map class HostApexProvider: def __init__(self, apex, tmpdir): self._tmpdir = tmpdir self._folder_cache = {} self._payload = os.path.join(self._tmpdir, 'apex_payload.zip') # Extract payload to tmpdir. apex_zip = zipfile.ZipFile(apex) apex_zip.extract('apex_payload.zip', tmpdir) def __del__(self): # Delete temps. if os.path.exists(self._payload): os.remove(self._payload) def get(self, path): apex_dir, name = os.path.split(path) if not apex_dir: apex_dir = '' apex_map = self.read_dir(apex_dir) return apex_map[name] if name in apex_map else None def read_dir(self, apex_dir): if apex_dir in self._folder_cache: return self._folder_cache[apex_dir] if not self._folder_cache: self.parse_zip() if apex_dir in self._folder_cache: return self._folder_cache[apex_dir] return {} def parse_zip(self): apex_zip = zipfile.ZipFile(self._payload) infos = apex_zip.infolist() for zipinfo in infos: path = zipinfo.filename # Assume no empty file is stored. assert path def get_octal(val, index): return (val >> (index * 3)) & 0x7 def bits_is_exec(val): # TODO: Enforce group/other, too? return get_octal(val, 2) & 1 == 1 is_zipinfo = True while path: apex_dir, base = os.path.split(path) # TODO: If directories are stored, base will be empty. if apex_dir not in self._folder_cache: self._folder_cache[apex_dir] = {} dir_map = self._folder_cache[apex_dir] if base not in dir_map: if is_zipinfo: bits = (zipinfo.external_attr >> 16) & 0xFFFF is_dir = get_octal(bits, 4) == 4 is_symlink = get_octal(bits, 4) == 2 is_exec = bits_is_exec(bits) size = zipinfo.file_size else: is_exec = False # Seems we can't get this easily? is_symlink = False is_dir = True # Use a negative value as an indicator of undefined/unknown size. size = -1 dir_map[base] = FSObject(base, is_dir, is_exec, is_symlink, size) is_zipinfo = False path = apex_dir # DO NOT USE DIRECTLY! This is an "abstract" base class. class Checker: def __init__(self, provider): self._provider = provider self._errors = 0 self._expected_file_globs = set() def fail(self, msg, *fail_args): self._errors += 1 logging.error(msg, *fail_args) def error_count(self): return self._errors def reset_errors(self): self._errors = 0 def is_file(self, path): fs_object = self._provider.get(path) if fs_object is None: return False, 'Could not find %s' if fs_object.is_dir: return False, '%s is a directory' if fs_object.is_symlink: return False, '%s is a symlink' return True, '' def is_dir(self, path): fs_object = self._provider.get(path) if fs_object is None: return False, 'Could not find %s' if not fs_object.is_dir: return False, '%s is not a directory' return True, '' def check_file(self, path): ok, msg = self.is_file(path) if not ok: self.fail(msg, path) self._expected_file_globs.add(path) return ok def check_dir(self, path): ok, msg = self.is_dir(path) if not ok: self.fail(msg, path) self._expected_file_globs.add(path) return ok def check_optional_file(self, path): if not self._provider.get(path): return True return self.check_file(path) def check_executable(self, filename): path = 'bin/%s' % filename if not self.check_file(path): return if not self._provider.get(path).is_exec: self.fail('%s is not executable', path) def check_executable_symlink(self, filename): path = 'bin/%s' % filename fs_object = self._provider.get(path) if fs_object is None: self.fail('Could not find %s', path) return if fs_object.is_dir: self.fail('%s is a directory', path) return if not fs_object.is_symlink: self.fail('%s is not a symlink', path) self._expected_file_globs.add(path) def arch_dirs_for_path(self, path, multilib=None): # Look for target-specific subdirectories for the given directory path. # This is needed because the list of build targets is not propagated # to this script. # # TODO(b/123602136): Pass build target information to this script and fix # all places where this function in used (or similar workarounds). dirs = [] for archs_per_bitness in self.possible_archs_per_bitness(multilib): found_dir = False for arch in archs_per_bitness: dir = '%s/%s' % (path, arch) found, _ = self.is_dir(dir) if found: found_dir = True dirs.append(dir) # At least one arch directory per bitness must exist. if not found_dir: self.fail('Arch directories missing in %s - expected at least one of %s', path, ', '.join(archs_per_bitness)) return dirs def check_art_test_executable(self, filename, multilib=None): for dir in self.arch_dirs_for_path(ART_TEST_DIR, multilib): test_path = '%s/%s' % (dir, filename) self._expected_file_globs.add(test_path) file_obj = self._provider.get(test_path) if not file_obj: self.fail('ART test binary missing: %s', test_path) elif not file_obj.is_exec: self.fail('%s is not executable', test_path) def check_art_test_data(self, filename): for dir in self.arch_dirs_for_path(ART_TEST_DIR): if not self.check_file('%s/%s' % (dir, filename)): return def check_single_library(self, filename): lib_path = 'lib/%s' % filename lib64_path = 'lib64/%s' % filename lib_is_file, _ = self.is_file(lib_path) if lib_is_file: self._expected_file_globs.add(lib_path) lib64_is_file, _ = self.is_file(lib64_path) if lib64_is_file: self._expected_file_globs.add(lib64_path) if not lib_is_file and not lib64_is_file: self.fail('Library missing: %s', filename) def check_java_library(self, basename): return self.check_file('javalib/%s.jar' % basename) def ignore_path(self, path_glob): self._expected_file_globs.add(path_glob) def check_optional_art_test_executable(self, filename): for archs_per_bitness in self.possible_archs_per_bitness(): for arch in archs_per_bitness: self.ignore_path('%s/%s/%s' % (ART_TEST_DIR, arch, filename)) def check_no_superfluous_files(self): def recurse(dir_path): paths = [] for name, fsobj in sorted(self._provider.read_dir(dir_path).items(), key=lambda p: p[0]): if name in ('.', '..'): continue path = os.path.join(dir_path, name) paths.append(path) if fsobj.is_dir: recurse(path) for path_glob in self._expected_file_globs: paths = [path for path in paths if not fnmatch.fnmatch(path, path_glob)] for unexpected_path in paths: fs_object = self._provider.get(unexpected_path) self.fail('Unexpected %s: %s', fs_object.type_descr(), unexpected_path) recurse('') # Just here for docs purposes, even if it isn't good Python style. def check_symlinked_multilib_executable(self, filename): """Check bin/filename32, and/or bin/filename64, with symlink bin/filename.""" raise NotImplementedError def check_symlinked_first_executable(self, filename): """Check bin/filename32, and/or bin/filename64, with symlink bin/filename.""" raise NotImplementedError def check_native_library(self, basename): """Check lib/basename.so, and/or lib64/basename.so.""" raise NotImplementedError def check_optional_native_library(self, basename_glob): """Allow lib/basename.so and/or lib64/basename.so to exist.""" raise NotImplementedError def check_prefer64_library(self, basename): """Check lib64/basename.so, or lib/basename.so on 32 bit only.""" raise NotImplementedError def possible_archs_per_bitness(self, multilib=None): """Returns a list of lists of possible architectures per bitness.""" raise NotImplementedError class Arch32Checker(Checker): def __init__(self, provider): super().__init__(provider) self.lib_dirs = ['lib'] def check_symlinked_multilib_executable(self, filename): self.check_executable('%s32' % filename) self.check_executable_symlink(filename) def check_symlinked_first_executable(self, filename): self.check_executable('%s32' % filename) self.check_executable_symlink(filename) def check_native_library(self, basename): # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve # the precision of this test? self.check_file('lib/%s.so' % basename) def check_optional_native_library(self, basename_glob): self.ignore_path('lib/%s.so' % basename_glob) def check_prefer64_library(self, basename): self.check_native_library(basename) def possible_archs_per_bitness(self, multilib=None): return [ARCHS_32] class Arch64Checker(Checker): def __init__(self, provider): super().__init__(provider) self.lib_dirs = ['lib64'] def check_symlinked_multilib_executable(self, filename): self.check_executable('%s64' % filename) self.check_executable_symlink(filename) def check_symlinked_first_executable(self, filename): self.check_executable('%s64' % filename) self.check_executable_symlink(filename) def check_native_library(self, basename): # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve # the precision of this test? self.check_file('lib64/%s.so' % basename) def check_optional_native_library(self, basename_glob): self.ignore_path('lib64/%s.so' % basename_glob) def check_prefer64_library(self, basename): self.check_native_library(basename) def possible_archs_per_bitness(self, multilib=None): return [ARCHS_64] class MultilibChecker(Checker): def __init__(self, provider): super().__init__(provider) self.lib_dirs = ['lib', 'lib64'] def check_symlinked_multilib_executable(self, filename): self.check_executable('%s32' % filename) self.check_executable('%s64' % filename) self.check_executable_symlink(filename) def check_symlinked_first_executable(self, filename): self.check_executable('%s64' % filename) self.check_executable_symlink(filename) def check_native_library(self, basename): # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve # the precision of this test? self.check_file('lib/%s.so' % basename) self.check_file('lib64/%s.so' % basename) def check_optional_native_library(self, basename_glob): self.ignore_path('lib/%s.so' % basename_glob) self.ignore_path('lib64/%s.so' % basename_glob) def check_prefer64_library(self, basename): self.check_file('lib64/%s.so' % basename) def possible_archs_per_bitness(self, multilib=None): if multilib is None or multilib == MULTILIB_BOTH: return [ARCHS_32, ARCHS_64] if multilib == MULTILIB_FIRST or multilib == MULTILIB_64: return [ARCHS_64] elif multilib == MULTILIB_32: return [ARCHS_32] self.fail('Unrecognized multilib option "%s"', multilib) class ReleaseChecker: def __init__(self, checker): self._checker = checker def __str__(self): return 'Release Checker' def run(self): # Check the root directory. self._checker.check_dir('bin') self._checker.check_dir('etc') self._checker.check_dir('javalib') for lib_dir in self._checker.lib_dirs: self._checker.check_dir(lib_dir) self._checker.check_file('apex_manifest.pb') # Check etc. self._checker.check_file('etc/boot-image.prof') self._checker.check_dir('etc/classpaths') self._checker.check_file('etc/classpaths/bootclasspath.pb') self._checker.check_file('etc/classpaths/systemserverclasspath.pb') self._checker.check_dir('etc/compatconfig') self._checker.check_file('etc/compatconfig/libcore-platform-compat-config.xml') self._checker.check_file('etc/init.rc') self._checker.check_file('etc/linker.config.pb') self._checker.check_file('etc/sdkinfo.pb') # Check flagging files that don't get added in builds on master-art. # TODO(b/345713436): Make flags work on master-art. self._checker.check_optional_file('etc/aconfig_flags.pb') self._checker.check_optional_file('etc/flag.map') self._checker.check_optional_file('etc/flag.val') self._checker.check_optional_file('etc/package.map') # Check binaries for ART. self._checker.check_executable('dexdump') self._checker.check_executable('dexlist') self._checker.check_executable('dexoptanalyzer') self._checker.check_executable('profman') self._checker.check_symlinked_multilib_executable('dalvikvm') # Check exported libraries for ART. self._checker.check_native_library('libdexfile') self._checker.check_native_library('libnativebridge') self._checker.check_native_library('libnativehelper') self._checker.check_native_library('libnativeloader') # Check internal libraries for ART. self._checker.check_native_library('libadbconnection') self._checker.check_native_library('libart') self._checker.check_native_library('libart-disassembler') self._checker.check_native_library('libartbase') self._checker.check_native_library('libartpalette') self._checker.check_native_library('libarttools') self._checker.check_native_library('libdt_fd_forward') self._checker.check_native_library('libopenjdkjvm') self._checker.check_native_library('libopenjdkjvmti') self._checker.check_native_library('libprofile') self._checker.check_native_library('libsigchain') # Check Java libraries for Managed Core Library. self._checker.check_java_library('apache-xml') self._checker.check_java_library('bouncycastle') self._checker.check_java_library('core-libart') self._checker.check_java_library('core-oj') self._checker.check_java_library('okhttp') if isEnvTrue('EMMA_INSTRUMENT_FRAMEWORK'): # In coverage builds jacoco is added to the list of ART apex jars. self._checker.check_java_library('jacocoagent') # Check internal native libraries for Managed Core Library. self._checker.check_native_library('libjavacore') self._checker.check_native_library('libopenjdk') # Check internal native library dependencies. # # Any internal dependency not listed here will cause a failure in # NoSuperfluousFilesChecker. Internal dependencies are generally just # implementation details, but in the release package we want to track them # because a) they add to the package size and the RAM usage (in particular # if the library is also present in /system or another APEX and hence might # get loaded twice through linker namespace separation), and b) we need to # catch invalid dependencies on /system or other APEXes that should go # through an exported library with stubs (b/128708192 tracks implementing a # better approach for that). self._checker.check_native_library('libbase') self._checker.check_native_library('libc++') self._checker.check_native_library('libdt_socket') self._checker.check_native_library('libjdwp') self._checker.check_native_library('liblz4') self._checker.check_native_library('liblzma') self._checker.check_native_library('libnpt') self._checker.check_native_library('libunwindstack') # Allow extra dependencies that appear in ASAN builds. self._checker.check_optional_native_library('libclang_rt.asan*') self._checker.check_optional_native_library('libclang_rt.hwasan*') self._checker.check_optional_native_library('libclang_rt.ubsan*') class ReleaseTargetChecker: def __init__(self, checker): self._checker = checker def __str__(self): return 'Release (Target) Checker' def run(self): # We don't check for the presence of the JSON APEX manifest (file # `apex_manifest.json`, only present in target APEXes), as it is only # included for compatibility reasons with Android Q and will likely be # removed in Android R. # Check binaries for ART. self._checker.check_executable('art_boot') self._checker.check_executable('art_exec') self._checker.check_executable('artd') self._checker.check_executable('dexopt_chroot_setup') self._checker.check_executable('oatdump') self._checker.check_executable('odrefresh') self._checker.check_symlinked_multilib_executable('dex2oat') # Check internal libraries for ART. self._checker.check_native_library('libartservice') self._checker.check_native_library('libperfetto_hprof') # Check internal Java libraries self._checker.check_java_library('service-art') self._checker.check_file('javalib/service-art.jar.prof') # Check exported native libraries for Managed Core Library. self._checker.check_native_library('libandroidio') # Check internal native library dependencies. self._checker.check_native_library('libexpat') class ReleaseHostChecker: def __init__(self, checker): self._checker = checker def __str__(self): return 'Release (Host) Checker' def run(self): # Check binaries for ART. self._checker.check_executable('hprof-conv') self._checker.check_symlinked_first_executable('dex2oatd') self._checker.check_symlinked_first_executable('dex2oat') # Check exported native libraries for Managed Core Library. self._checker.check_native_library('libicu') self._checker.check_native_library('libandroidio') # Check internal libraries for Managed Core Library. self._checker.check_native_library('libexpat-host') self._checker.check_native_library('libz-host') class DebugChecker: def __init__(self, checker): self._checker = checker def __str__(self): return 'Debug Checker' def run(self): # Check binaries for ART. self._checker.check_executable('dexanalyze') self._checker.check_symlinked_multilib_executable('imgdiag') # Check debug binaries for ART. self._checker.check_executable('dexoptanalyzerd') self._checker.check_symlinked_multilib_executable('imgdiagd') self._checker.check_executable('profmand') # Check exported libraries for ART. self._checker.check_native_library('libdexfiled') # Check internal libraries for ART. self._checker.check_native_library('libadbconnectiond') self._checker.check_native_library('libartbased') self._checker.check_native_library('libartd') self._checker.check_native_library('libartd-disassembler') self._checker.check_native_library('libopenjdkjvmd') self._checker.check_native_library('libopenjdkjvmtid') self._checker.check_native_library('libprofiled') # Check internal libraries for Managed Core Library. self._checker.check_native_library('libopenjdkd') class DebugTargetChecker: def __init__(self, checker): self._checker = checker def __str__(self): return 'Debug (Target) Checker' def run(self): # Check ART debug binaries. self._checker.check_executable('oatdumpd') self._checker.check_symlinked_multilib_executable('dex2oatd') # Check ART internal libraries. self._checker.check_native_library('libartserviced') self._checker.check_native_library('libperfetto_hprofd') # Check internal native library dependencies. # # Like in the release package, we check that we don't get other dependencies # besides those listed here. In this case the concern is not bloat, but # rather that we don't get behavioural differences between user (release) # and userdebug/eng builds, which could happen if the debug package has # duplicate library instances where releases don't. In other words, it's # uncontroversial to add debug-only dependencies, as long as they don't make # assumptions on having a single global state (ideally they should have # double_loadable:true, cf. go/double_loadable). Also, like in the release # package we need to look out for dependencies that should go through # exported library stubs (until b/128708192 is fixed). # # (There are currently no debug-only native libraries.) class TestingTargetChecker: def __init__(self, checker): self._checker = checker def __str__(self): return 'Testing (Target) Checker' def run(self): # Check test directories. self._checker.check_dir(ART_TEST_DIR) for arch_dir in self._checker.arch_dirs_for_path(ART_TEST_DIR): self._checker.check_dir(arch_dir) # Check ART test binaries. self._checker.check_art_test_executable('art_artd_tests') self._checker.check_art_test_executable('art_cmdline_tests') self._checker.check_art_test_executable('art_compiler_tests') self._checker.check_art_test_executable('art_dex2oat_tests') self._checker.check_art_test_executable('art_dexanalyze_tests') self._checker.check_art_test_executable('art_dexdump_tests') self._checker.check_art_test_executable('art_dexlist_tests') self._checker.check_art_test_executable('art_dexoptanalyzer_tests') self._checker.check_art_test_executable('art_disassembler_tests') self._checker.check_art_test_executable('art_imgdiag_tests') self._checker.check_art_test_executable('art_libartbase_tests') self._checker.check_art_test_executable('art_libartpalette_tests') self._checker.check_art_test_executable('art_libartservice_tests') self._checker.check_art_test_executable('art_libarttools_tests') self._checker.check_art_test_executable('art_libdexfile_support_tests') self._checker.check_art_test_executable('art_libdexfile_tests') self._checker.check_art_test_executable('art_libprofile_tests') self._checker.check_art_test_executable('art_oatdump_tests') self._checker.check_art_test_executable('art_odrefresh_tests') self._checker.check_art_test_executable('art_profman_tests') self._checker.check_art_test_executable('art_runtime_tests') self._checker.check_art_test_executable('art_sigchain_tests') # Check ART test tools. self._checker.check_executable('signal_dumper') # Check ART jar files which are needed for gtests. self._checker.check_art_test_data('art-gtest-jars-AbstractMethod.jar') self._checker.check_art_test_data('art-gtest-jars-ArrayClassWithUnresolvedComponent.dex') self._checker.check_art_test_data('art-gtest-jars-MyClassNatives.jar') self._checker.check_art_test_data('art-gtest-jars-Main.jar') self._checker.check_art_test_data('art-gtest-jars-ProtoCompare.jar') self._checker.check_art_test_data('art-gtest-jars-Transaction.jar') self._checker.check_art_test_data('art-gtest-jars-VerifierDepsMulti.dex') self._checker.check_art_test_data('art-gtest-jars-Nested.jar') self._checker.check_art_test_data('art-gtest-jars-MyClass.jar') self._checker.check_art_test_data('art-gtest-jars-ManyMethods.jar') self._checker.check_art_test_data('art-gtest-jars-GetMethodSignature.jar') self._checker.check_art_test_data('art-gtest-jars-Lookup.jar') self._checker.check_art_test_data('art-gtest-jars-Instrumentation.jar') self._checker.check_art_test_data('art-gtest-jars-MainUncompressedAligned.jar') self._checker.check_art_test_data('art-gtest-jars-ForClassLoaderD.jar') self._checker.check_art_test_data('art-gtest-jars-ForClassLoaderC.jar') self._checker.check_art_test_data('art-gtest-jars-ErroneousA.jar') self._checker.check_art_test_data('art-gtest-jars-HiddenApiSignatures.jar') self._checker.check_art_test_data('art-gtest-jars-ForClassLoaderB.jar') self._checker.check_art_test_data('art-gtest-jars-LinkageTest.dex') self._checker.check_art_test_data('art-gtest-jars-MethodTypes.jar') self._checker.check_art_test_data('art-gtest-jars-ErroneousInit.jar') self._checker.check_art_test_data('art-gtest-jars-VerifierDeps.dex') self._checker.check_art_test_data('art-gtest-jars-StringLiterals.jar') self._checker.check_art_test_data('art-gtest-jars-XandY.jar') self._checker.check_art_test_data('art-gtest-jars-ExceptionHandle.jar') self._checker.check_art_test_data('art-gtest-jars-ImageLayoutB.jar') self._checker.check_art_test_data('art-gtest-jars-Interfaces.jar') self._checker.check_art_test_data('art-gtest-jars-IMTB.jar') self._checker.check_art_test_data('art-gtest-jars-Extension2.jar') self._checker.check_art_test_data('art-gtest-jars-Extension1.jar') self._checker.check_art_test_data('art-gtest-jars-MainEmptyUncompressedAligned.jar') self._checker.check_art_test_data('art-gtest-jars-ErroneousB.jar') self._checker.check_art_test_data('art-gtest-jars-MultiDexModifiedSecondary.jar') self._checker.check_art_test_data('art-gtest-jars-NonStaticLeafMethods.jar') self._checker.check_art_test_data('art-gtest-jars-DefaultMethods.jar') self._checker.check_art_test_data('art-gtest-jars-MultiDexUncompressedAligned.jar') self._checker.check_art_test_data('art-gtest-jars-StaticsFromCode.jar') self._checker.check_art_test_data('art-gtest-jars-ProfileTestMultiDex.jar') self._checker.check_art_test_data('art-gtest-jars-VerifySoftFailDuringClinit.dex') self._checker.check_art_test_data('art-gtest-jars-MainStripped.jar') self._checker.check_art_test_data('art-gtest-jars-ForClassLoaderA.jar') self._checker.check_art_test_data('art-gtest-jars-StaticLeafMethods.jar') self._checker.check_art_test_data('art-gtest-jars-MultiDex.jar') self._checker.check_art_test_data('art-gtest-jars-Packages.jar') self._checker.check_art_test_data('art-gtest-jars-ProtoCompare2.jar') self._checker.check_art_test_data('art-gtest-jars-Statics.jar') self._checker.check_art_test_data('art-gtest-jars-AllFields.jar') self._checker.check_art_test_data('art-gtest-jars-IMTA.jar') self._checker.check_art_test_data('art-gtest-jars-ImageLayoutA.jar') self._checker.check_art_test_data('art-gtest-jars-MainEmptyUncompressed.jar') self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexTestDex.jar') self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexPublicSdkDex.dex') self._checker.check_art_test_data('art-gtest-jars-SuperWithAccessChecks.dex') # Fuzzer cases self._checker.check_art_test_data('fuzzer_corpus.zip') class NoSuperfluousFilesChecker: def __init__(self, checker): self._checker = checker def __str__(self): return 'No superfluous files checker' def run(self): self._checker.check_no_superfluous_files() class List: def __init__(self, provider, print_size=False): self._provider = provider self._print_size = print_size def print_list(self): def print_list_rec(path): apex_map = self._provider.read_dir(path) if apex_map is None: return apex_map = dict(apex_map) if '.' in apex_map: del apex_map['.'] if '..' in apex_map: del apex_map['..'] for (_, val) in sorted(apex_map.items()): val_path = os.path.join(path, val.name) if self._print_size: if val.size < 0: print('[ n/a ] %s' % val_path) else: print('[%11d] %s' % (val.size, val_path)) else: print(val_path) if val.is_dir: print_list_rec(val_path) print_list_rec('') class Tree: def __init__(self, provider, title, print_size=False): print('%s' % title) self._provider = provider self._has_next_list = [] self._print_size = print_size @staticmethod def get_vertical(has_next_list): string = '' for v in has_next_list: string += '%s ' % ('│' if v else ' ') return string @staticmethod def get_last_vertical(last): return '└── ' if last else '├── ' def print_tree(self): def print_tree_rec(path): apex_map = self._provider.read_dir(path) if apex_map is None: return apex_map = dict(apex_map) if '.' in apex_map: del apex_map['.'] if '..' in apex_map: del apex_map['..'] key_list = list(sorted(apex_map.keys())) for i, key in enumerate(key_list): prev = self.get_vertical(self._has_next_list) last = self.get_last_vertical(i == len(key_list) - 1) val = apex_map[key] if self._print_size: if val.size < 0: print('%s%s[ n/a ] %s' % (prev, last, val.name)) else: print('%s%s[%11d] %s' % (prev, last, val.size, val.name)) else: print('%s%s%s' % (prev, last, val.name)) if val.is_dir: self._has_next_list.append(i < len(key_list) - 1) val_path = os.path.join(path, val.name) print_tree_rec(val_path) self._has_next_list.pop() print_tree_rec('') # Note: do not sys.exit early, for __del__ cleanup. def art_apex_test_main(test_args): if test_args.host and test_args.flattened: logging.error('Both of --host and --flattened set') return 1 if test_args.list and test_args.tree: logging.error('Both of --list and --tree set') return 1 if test_args.size and not (test_args.list or test_args.tree): logging.error('--size set but neither --list nor --tree set') return 1 if not test_args.flattened and not test_args.tmpdir: logging.error('Need a tmpdir.') return 1 if not test_args.flattened and not test_args.host: if not test_args.deapexer: logging.error('Need deapexer.') return 1 if not test_args.debugfs: logging.error('Need debugfs.') return 1 if not test_args.fsckerofs: logging.error('Need fsck.erofs.') return 1 if test_args.host: # Host APEX. if test_args.flavor not in [FLAVOR_DEBUG, FLAVOR_AUTO]: logging.error('Using option --host with non-Debug APEX') return 1 # Host APEX is always a debug flavor (for now). test_args.flavor = FLAVOR_DEBUG else: # Device APEX. if test_args.flavor == FLAVOR_AUTO: logging.warning('--flavor=auto, trying to autodetect. This may be incorrect!') # The order of flavors in the list below matters, as the release tag (empty string) will # match any package name. for flavor in [ FLAVOR_DEBUG, FLAVOR_TESTING, FLAVOR_RELEASE ]: flavor_tag = flavor # Special handling for the release flavor, whose name is no longer part of the Release ART # APEX file name (`com.android.art.capex` / `com.android.art`). if flavor == FLAVOR_RELEASE: flavor_tag = '' flavor_pattern = '*.%s*' % flavor_tag if fnmatch.fnmatch(test_args.apex, flavor_pattern): test_args.flavor = flavor logging.warning(' Detected %s flavor', flavor) break if test_args.flavor == FLAVOR_AUTO: logging.error(' Could not detect APEX flavor, neither %s, %s nor %s for \'%s\'', FLAVOR_RELEASE, FLAVOR_DEBUG, FLAVOR_TESTING, test_args.apex) return 1 try: if test_args.host: apex_provider = HostApexProvider(test_args.apex, test_args.tmpdir) else: apex_dir = test_args.apex if not test_args.flattened: # Extract the apex. It would be nice to use the output from "deapexer list" # to avoid this work, but it doesn't provide info about executable bits. apex_dir = extract_apex(test_args.apex, test_args.deapexer, test_args.debugfs, test_args.fsckerofs, test_args.tmpdir) apex_provider = TargetApexProvider(apex_dir) except (zipfile.BadZipFile, zipfile.LargeZipFile) as e: logging.error('Failed to create provider: %s', e) return 1 if test_args.tree: Tree(apex_provider, test_args.apex, test_args.size).print_tree() return 0 if test_args.list: List(apex_provider, test_args.size).print_list() return 0 checkers = [] if test_args.bitness == BITNESS_AUTO: logging.warning('--bitness=auto, trying to autodetect. This may be incorrect!') has_32 = apex_provider.get('lib') is not None has_64 = apex_provider.get('lib64') is not None if has_32 and has_64: logging.warning(' Detected multilib') test_args.bitness = BITNESS_MULTILIB elif has_32: logging.warning(' Detected 32-only') test_args.bitness = BITNESS_32 elif has_64: logging.warning(' Detected 64-only') test_args.bitness = BITNESS_64 else: logging.error(' Could not detect bitness, neither lib nor lib64 contained.') List(apex_provider).print_list() return 1 if test_args.bitness == BITNESS_32: base_checker = Arch32Checker(apex_provider) elif test_args.bitness == BITNESS_64: base_checker = Arch64Checker(apex_provider) else: assert test_args.bitness == BITNESS_MULTILIB base_checker = MultilibChecker(apex_provider) checkers.append(ReleaseChecker(base_checker)) if test_args.host: checkers.append(ReleaseHostChecker(base_checker)) else: checkers.append(ReleaseTargetChecker(base_checker)) if test_args.flavor == FLAVOR_DEBUG or test_args.flavor == FLAVOR_TESTING: checkers.append(DebugChecker(base_checker)) if not test_args.host: checkers.append(DebugTargetChecker(base_checker)) if test_args.flavor == FLAVOR_TESTING: checkers.append(TestingTargetChecker(base_checker)) # This checker must be last. checkers.append(NoSuperfluousFilesChecker(base_checker)) failed = False for checker in checkers: logging.info('%s...', checker) checker.run() if base_checker.error_count() > 0: logging.error('%s FAILED', checker) failed = True else: logging.info('%s SUCCEEDED', checker) base_checker.reset_errors() return 1 if failed else 0 def art_apex_test_default(test_parser): if 'ANDROID_PRODUCT_OUT' not in os.environ: logging.error('No-argument use requires ANDROID_PRODUCT_OUT') sys.exit(1) product_out = os.environ['ANDROID_PRODUCT_OUT'] if 'ANDROID_HOST_OUT' not in os.environ: logging.error('No-argument use requires ANDROID_HOST_OUT') sys.exit(1) host_out = os.environ['ANDROID_HOST_OUT'] test_args = test_parser.parse_args(['unused']) # For consistency. test_args.debugfs = '%s/bin/debugfs' % host_out test_args.fsckerofs = '%s/bin/fsck.erofs' % host_out test_args.tmpdir = '.' test_args.tree = False test_args.list = False test_args.bitness = BITNESS_AUTO failed = False if not os.path.exists(test_args.debugfs): logging.error('Cannot find debugfs (default path %s). Please build it, e.g., m debugfs', test_args.debugfs) sys.exit(1) # TODO: Add host support. # TODO: Add support for flattened APEX packages. configs = [ {'name': 'com.android.art.capex', 'flavor': FLAVOR_RELEASE, 'host': False}, {'name': 'com.android.art.debug.capex', 'flavor': FLAVOR_DEBUG, 'host': False}, # Note: The Testing ART APEX is not a Compressed APEX. {'name': 'com.android.art.testing.apex', 'flavor': FLAVOR_TESTING, 'host': False}, ] for config in configs: logging.info(config['name']) # TODO: Host will need different path. test_args.apex = '%s/system/apex/%s' % (product_out, config['name']) if not os.path.exists(test_args.apex): failed = True logging.error('Cannot find APEX %s. Please build it first.', test_args.apex) continue test_args.flavor = config['flavor'] test_args.host = config['host'] failed = art_apex_test_main(test_args) != 0 if failed: sys.exit(1) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Check integrity of an ART APEX.') parser.add_argument('apex', help='APEX file input') parser.add_argument('--host', help='Check as host APEX', action='store_true') parser.add_argument('--flattened', help='Check as flattened (target) APEX', action='store_true') parser.add_argument('--flavor', help='Check as FLAVOR APEX', choices=FLAVORS_ALL, default=FLAVOR_AUTO) parser.add_argument('--list', help='List all files', action='store_true') parser.add_argument('--tree', help='Print directory tree', action='store_true') parser.add_argument('--size', help='Print file sizes', action='store_true') parser.add_argument('--tmpdir', help='Directory for temp files') parser.add_argument('--deapexer', help='Path to deapexer') parser.add_argument('--debugfs', help='Path to debugfs') parser.add_argument('--fsckerofs', help='Path to fsck.erofs') parser.add_argument('--bitness', help='Bitness to check', choices=BITNESS_ALL, default=BITNESS_AUTO) if len(sys.argv) == 1: art_apex_test_default(parser) else: args = parser.parse_args() if args is None: sys.exit(1) exit_code = art_apex_test_main(args) sys.exit(exit_code)