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