| #!/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()) |