#!/usr/bin/env python3 # # Copyright (C) 2017 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 collections import difflib import os import subprocess import sys import tempfile """Test vndk vtable dumper""" NDK_VERSION = 'r11' API_LEVEL = 'android-24' SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) VNDK_VTABLE_DUMPER = 'vndk-vtable-dumper' def get_dirnames(path, n): """Get directory, n directories before path""" for i in range(n): path = os.path.dirname(path) return path def get_prebuilts_host(): """Get the host dir for prebuilts""" if sys.platform.startswith('linux'): return 'linux-x86' if sys.platform.startswith('darwin'): return 'darwin-x86' raise NotImplementedError('unknown platform') def get_prebuilts_gcc(android_build_top, arch, gcc_version): """Get the path to gcc for the current platform""" return os.path.join(android_build_top, 'prebuilts', 'gcc', get_prebuilts_host(), arch, gcc_version) def get_prebuilts_clang(android_build_top): """Get the path to prebuilt gcc for the current platform""" return os.path.join(android_build_top, 'prebuilts', 'clang', 'host', get_prebuilts_host(), 'clang-stable') def get_prebuilts_ndk(android_build_top, subdirs): """Get the path to prebuilt ndk for the current platform and API level""" return os.path.join(android_build_top, 'prebuilts', 'ndk', NDK_VERSION, 'platforms', API_LEVEL, *subdirs) def run_cmd(cmd, verbose=False): """Run the command given and print the command if verbose is True""" if verbose: print('RUN:', ' '.join(cmd), file=sys.stderr) subprocess.check_call(cmd) def run_output(cmd, verbose=False): """Run the command given and print output of the command""" if verbose: print('RUN:', ' '.join(cmd), file=sys.stderr) return subprocess.check_output(cmd, universal_newlines=True) def run_vtable_dump(path, verbose=False): """Run vndk vtable dumper""" return run_output([VNDK_VTABLE_DUMPER, path], verbose) class Target(object): """Class representing a target: for eg: x86, arm64 etc""" def __init__(self, name, triple, cflags, ldflags, gcc_toolchain_dir, clang_dir, ndk_include, ndk_lib): """Parameterized Constructor""" self.name = name self.target_triple = triple self.target_cflags = cflags self.target_ldflags = ldflags self.gcc_toolchain_dir = gcc_toolchain_dir self.clang_dir = clang_dir self.ndk_include = ndk_include self.ndk_lib = ndk_lib def compile(self, obj_file, src_file, cflags, verbose=False): """Compiles the given source files and produces a .o at obj_file""" clangpp = os.path.join(self.clang_dir, 'bin', 'clang++') cmd = [clangpp, '-o', obj_file, '-c', src_file] cmd.extend(['-fPIE', '-fPIC', '-fno-rtti', '-std=c++11']) cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir]) cmd.extend(['-target', self.target_triple]) cmd.extend(['-isystem', self.ndk_include]) cmd.extend(cflags) cmd.extend(self.target_cflags) run_cmd(cmd, verbose) def link(self, out_file, obj_files, ldflags, verbose=False): """Link the given obj files to form a shared library""" crtbegin = os.path.join(self.ndk_lib, 'crtbegin_so.o') crtend = os.path.join(self.ndk_lib, 'crtend_so.o') clangpp = os.path.join(self.clang_dir, 'bin', 'clang++') cmd = [clangpp, '-o', out_file] cmd.extend(['-fPIE', '-fPIC', '-fno-rtti', '-Wl,--no-undefined', '-nostdlib']) cmd.append('-L' + self.ndk_lib) cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir]) cmd.extend(['-target', self.target_triple]) cmd.append(crtbegin) cmd.extend(obj_files) cmd.append(crtend) cmd.extend(ldflags) cmd.extend(self.target_ldflags) run_cmd(cmd, verbose) def create_targets(top): """Create multiple targets objects, one for each architecture supported""" return [ Target('arm', 'arm-linux-androideabi', [],[], get_prebuilts_gcc(top, 'arm', 'arm-linux-androideabi-4.9'), get_prebuilts_clang(top), get_prebuilts_ndk(top, ['arch-arm', 'usr', 'include']), get_prebuilts_ndk(top, ['arch-arm', 'usr', 'lib'])), Target('arm64', 'aarch64-linux-android', [], [], get_prebuilts_gcc(top, 'aarch64', 'aarch64-linux-android-4.9'), get_prebuilts_clang(top), get_prebuilts_ndk(top, ['arch-arm64', 'usr', 'include']), get_prebuilts_ndk(top, ['arch-arm64', 'usr', 'lib'])), Target('mips', 'mipsel-linux-android', [], [], get_prebuilts_gcc(top, 'mips', 'mips64el-linux-android-4.9'), get_prebuilts_clang(top), get_prebuilts_ndk(top, ['arch-mips', 'usr', 'include']), get_prebuilts_ndk(top, ['arch-mips', 'usr', 'lib'])), Target('mips64', 'mips64el-linux-android', ['-march=mips64el', '-mcpu=mips64r6'], ['-march=mips64el', '-mcpu=mips64r6'], get_prebuilts_gcc(top, 'mips', 'mips64el-linux-android-4.9'), get_prebuilts_clang(top), get_prebuilts_ndk(top, ['arch-mips64', 'usr', 'include']), get_prebuilts_ndk(top, ['arch-mips64', 'usr', 'lib64'])), Target('x86', 'x86_64-linux-android', ['-m32'], ['-m32'], get_prebuilts_gcc(top, 'x86', 'x86_64-linux-android-4.9'), get_prebuilts_clang(top), get_prebuilts_ndk(top, ['arch-x86', 'usr', 'include']), get_prebuilts_ndk(top, ['arch-x86', 'usr', 'lib'])), Target('x86_64', 'x86_64-linux-android', ['-m64'], ['-m64'], get_prebuilts_gcc(top, 'x86', 'x86_64-linux-android-4.9'), get_prebuilts_clang(top), get_prebuilts_ndk(top, ['arch-x86_64', 'usr', 'include']), get_prebuilts_ndk(top, ['arch-x86_64', 'usr', 'lib64'])), ] class TestRunner(object): """Class to run the test""" def __init__(self, expected_dir, test_dir, verbose): """Parameterized constructor""" self.expected_dir = expected_dir self.test_dir = test_dir self.verbose = verbose self.num_errors = 0 def check_output(self, expected_file_path, actual): """Compare the output of the test run and the expected output""" actual = actual.splitlines(True) with open(expected_file_path, 'r') as f: expected = f.readlines() if actual == expected: return for line in difflib.context_diff(expected, actual, fromfile=expected_file_path, tofile='actual'): sys.stderr.write(line) self.num_errors += 1 def run_test_for_target(self, target): """Run the test for a specific target""" print('Testing target', target.name, '...', file=sys.stderr) expected_dir = os.path.join(self.expected_dir, target.name) # Create test directory for this target. test_dir = os.path.join(self.test_dir, target.name) os.makedirs(test_dir, exist_ok=True) # Compile and test "libtest.so". src_file = os.path.join(SCRIPT_DIR, 'test1.cpp') obj_file = os.path.join(test_dir, 'test.o') target.compile(obj_file, src_file, [], self.verbose) out_file = os.path.join(test_dir, 'libtest.so') target.link(out_file, [obj_file], ['-shared', '-lc', '-lgcc', '-lstdc++'], self.verbose) self.check_output(os.path.join(expected_dir, 'libtest.so.txt'), run_vtable_dump(out_file, self.verbose)) def run_test(self, targets): """Run test fo all targets""" for target in targets: self.run_test_for_target(target) def main(): """ Set up and run test""" # Parse command line arguments. parser = argparse.ArgumentParser() parser.add_argument('--verbose', '-v', action='store_true') parser.add_argument('--android-build-top', help='path to android build top') parser.add_argument('--test-dir', help='directory for temporary files') parser.add_argument('--expected-dir', help='directory with expected output') args = parser.parse_args() # Find ${ANDROID_BUILD_TOP}. if args.android_build_top: android_build_top = args.android_build_top else: android_build_top = get_dirnames(SCRIPT_DIR, 5) # Find expected output directory. if args.expected_dir: expected_dir = args.expected_dir else: expected_dir = os.path.join(SCRIPT_DIR, 'expected') # Load compilation targets. targets = create_targets(android_build_top) # Run tests. if args.test_dir: os.makedirs(args.test_dir, exist_ok=True) runner = TestRunner(expected_dir, args.test_dir, args.verbose) runner.run_test(targets) else: with tempfile.TemporaryDirectory() as test_dir: runner = TestRunner(expected_dir, test_dir, args.verbose) runner.run_test(targets) if runner.num_errors: print('FAILED:', runner.num_errors, 'test(s) failed', file=sys.stderr) else: print('SUCCESS', file=sys.stderr) return 1 if runner.num_errors else 0 if __name__ == '__main__': sys.exit(main())