1#!/usr/bin/env python
2
3from __future__ import print_function
4
5import collections
6import os
7import re
8import subprocess
9import sys
10
11
12def detect_ndk_dir():
13    ndk_dir = os.getenv('NDK')
14    if not ndk_dir:
15        error_msg = '''error: NDK toolchain is required for this test case.
16error:
17error: Steps:
18error:   1. Download NDK from https://developer.android.com/ndk/downloads/
19error:   2. Unzip the archive (android-ndk-r15c-linux-x86_64.zip)
20error:   3. Set environment variable NDK to the extracted directory
21error:      (export NDK=android-ndk-r15c)
22error:'''
23        print(error_msg, file=sys.stderr)
24        raise ValueError('NDK toolchain not specified')
25
26    if not os.path.exists(ndk_dir):
27        raise ValueError('NDK toolchain not found')
28
29    return ndk_dir
30
31
32def detect_api_level(ndk_dir):
33    try:
34        apis = []
35        pattern = re.compile('android-(\\d+)')
36        for name in os.listdir(os.path.join(ndk_dir, 'platforms')):
37            match = pattern.match(name)
38            if match:
39                apis.append(int(match.group(1)))
40        if not apis:
41            raise ValueError('failed to find latest api')
42        return 'android-{}'.format(max(apis))
43    except IOError:
44        raise ValueError('failed to find latest api')
45
46
47def detect_host():
48    if sys.platform.startswith('linux'):
49        return 'linux-x86_64'
50    if sys.platform.startswith('darwin'):
51        return 'darwin-x86_64'
52    raise NotImplementedError('unknown host platform')
53
54
55def get_gcc_dir(ndk_dir, arch, host):
56    return os.path.join(ndk_dir, 'toolchains', arch, 'prebuilt', host)
57
58
59def get_clang_dir(ndk_dir, host):
60    return os.path.join(ndk_dir, 'toolchains', 'llvm', 'prebuilt', host)
61
62
63def get_platform_dir(ndk_dir, api, subdirs):
64    return os.path.join(ndk_dir, 'platforms', api, *subdirs)
65
66
67class Target(object):
68    def __init__(self, name, triple, cflags, ldflags, gcc_toolchain_dir,
69                 clang_dir, ndk_include, ndk_lib):
70        self.name = name
71        self.target_triple = triple
72        self.target_cflags = cflags
73        self.target_ldflags = ldflags
74
75        self.gcc_toolchain_dir = gcc_toolchain_dir
76        self.clang_dir = clang_dir
77        self.ndk_include = ndk_include
78        self.ndk_lib = ndk_lib
79
80
81    def check_paths(self):
82        def check_path(path):
83            if os.path.exists(path):
84                return True
85            print('error: File not found:', path, file=sys.stderr)
86            return False
87
88        ld_exeutable = os.path.join(
89            self.gcc_toolchain_dir, 'bin', self.target_triple + '-ld')
90
91        success = check_path(self.gcc_toolchain_dir)
92        success &= check_path(ld_exeutable)
93        success &= check_path(self.clang_dir)
94        success &= check_path(self.ndk_include)
95        success &= check_path(self.ndk_lib)
96        return success
97
98
99    def compile(self, obj_file, src_file, cflags):
100        clang = os.path.join(self.clang_dir, 'bin', 'clang')
101
102        cmd = [clang, '-o', obj_file, '-c', src_file]
103        cmd.extend(['-fPIE', '-fPIC'])
104        cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir])
105        cmd.extend(['-target', self.target_triple])
106        cmd.extend(['-isystem', self.ndk_include])
107        cmd.extend(cflags)
108        cmd.extend(self.target_cflags)
109        subprocess.check_call(cmd)
110
111
112    def link(self, out_file, obj_files, ldflags):
113        if '-shared' in ldflags:
114            crtbegin = os.path.join(self.ndk_lib, 'crtbegin_so.o')
115            crtend = os.path.join(self.ndk_lib, 'crtend_so.o')
116        else:
117            crtbegin = os.path.join(self.ndk_lib, 'crtbegin_static.o')
118            crtend = os.path.join(self.ndk_lib, 'crtend_android.o')
119
120        clang = os.path.join(self.clang_dir, 'bin', 'clang')
121
122        cmd = [clang, '-o', out_file]
123        cmd.extend(['-fPIE', '-fPIC', '-Wl,--no-undefined', '-nostdlib'])
124        cmd.append('-L' + self.ndk_lib)
125        cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir])
126        cmd.extend(['-target', self.target_triple])
127        cmd.append(crtbegin)
128        cmd.extend(obj_files)
129        cmd.append(crtend)
130        cmd.extend(ldflags)
131        cmd.extend(self.target_ldflags)
132        if '-shared' not in ldflags:
133            cmd.append('-Wl,-pie')
134        subprocess.check_call(cmd)
135
136
137def create_targets(ndk_dir=None, api=None, host=None):
138    if ndk_dir is None:
139        ndk_dir = detect_ndk_dir()
140    if api is None:
141        api = detect_api_level(ndk_dir)
142    if host is None:
143        host = detect_host()
144
145    targets = collections.OrderedDict()
146
147    targets['arm'] = Target(
148        'arm', 'arm-linux-androideabi', [], [],
149        get_gcc_dir(ndk_dir, 'arm-linux-androideabi-4.9', host),
150        get_clang_dir(ndk_dir, host),
151        get_platform_dir(ndk_dir, api, ['arch-arm', 'usr', 'include']),
152        get_platform_dir(ndk_dir, api, ['arch-arm', 'usr', 'lib']))
153
154    targets['arm64'] = Target(
155        'arm64', 'aarch64-linux-android', [], [],
156        get_gcc_dir(ndk_dir, 'aarch64-linux-android-4.9', host),
157        get_clang_dir(ndk_dir, host),
158        get_platform_dir(ndk_dir, api, ['arch-arm64', 'usr', 'include']),
159        get_platform_dir(ndk_dir, api, ['arch-arm64', 'usr', 'lib']))
160
161    targets['x86'] = Target(
162        'x86', 'i686-linux-android', ['-m32'], ['-m32'],
163        get_gcc_dir(ndk_dir, 'x86-4.9', host),
164        get_clang_dir(ndk_dir, host),
165        get_platform_dir(ndk_dir, api, ['arch-x86', 'usr', 'include']),
166        get_platform_dir(ndk_dir, api, ['arch-x86', 'usr', 'lib']))
167
168    targets['x86_64'] = Target(
169        'x86_64', 'x86_64-linux-android', ['-m64'], ['-m64'],
170        get_gcc_dir(ndk_dir, 'x86_64-4.9', host),
171        get_clang_dir(ndk_dir, host),
172        get_platform_dir(ndk_dir, api, ['arch-x86_64', 'usr', 'include']),
173        get_platform_dir(ndk_dir, api, ['arch-x86_64', 'usr', 'lib64']))
174
175    targets['mips'] = Target(
176        'mips', 'mipsel-linux-android', [], [],
177        get_gcc_dir(ndk_dir, 'mipsel-linux-android-4.9', host),
178        get_clang_dir(ndk_dir, host),
179        get_platform_dir(ndk_dir, api, ['arch-mips', 'usr', 'include']),
180        get_platform_dir(ndk_dir, api, ['arch-mips', 'usr', 'lib']))
181
182    targets['mips64'] = Target(
183        'mips64', 'mips64el-linux-android',
184        ['-march=mips64el', '-mcpu=mips64r6'],
185        ['-march=mips64el', '-mcpu=mips64r6'],
186        get_gcc_dir(ndk_dir, 'mips64el-linux-android-4.9', host),
187        get_clang_dir(ndk_dir, host),
188        get_platform_dir(ndk_dir, api, ['arch-mips64', 'usr', 'include']),
189        get_platform_dir(ndk_dir, api, ['arch-mips64', 'usr', 'lib64']))
190
191    return targets
192