1#
2# Copyright (C) 2015 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16import argparse
17import multiprocessing
18import os
19import shutil
20import subprocess
21import sys
22import tempfile
23import zipfile
24
25
26THIS_DIR = os.path.realpath(os.path.dirname(__file__))
27
28
29# TODO: Make the x86 toolchain names just be the triple.
30ALL_TOOLCHAINS = (
31    'arm-linux-androideabi',
32    'aarch64-linux-android',
33    'mipsel-linux-android',
34    'mips64el-linux-android',
35    'x86',
36    'x86_64',
37)
38
39
40ALL_TRIPLES = (
41    'arm-linux-androideabi',
42    'aarch64-linux-android',
43    'mipsel-linux-android',
44    'mips64el-linux-android',
45    'i686-linux-android',
46    'x86_64-linux-android',
47)
48
49
50ALL_ARCHITECTURES = (
51    'arm',
52    'arm64',
53    'mips',
54    'mips64',
55    'x86',
56    'x86_64',
57)
58
59
60ALL_ABIS = (
61    'armeabi',
62    'armeabi-v7a',
63    'armeabi-v7a-hard',
64    'arm64-v8a',
65    'mips',
66    'mips64',
67    'x86',
68    'x86_64',
69)
70
71
72def arch_to_toolchain(arch):
73    return dict(zip(ALL_ARCHITECTURES, ALL_TOOLCHAINS))[arch]
74
75
76def arch_to_triple(arch):
77    return dict(zip(ALL_ARCHITECTURES, ALL_TRIPLES))[arch]
78
79
80def toolchain_to_arch(toolchain):
81    return dict(zip(ALL_TOOLCHAINS, ALL_ARCHITECTURES))[toolchain]
82
83
84def arch_to_abis(arch):
85    return {
86        'arm': ['armeabi', 'armeabi-v7a', 'armeabi-v7a-hard'],
87        'arm64': ['arm64-v8a'],
88        'mips': ['mips'],
89        'mips64': ['mips64'],
90        'x86': ['x86'],
91        'x86_64': ['x86_64'],
92    }[arch]
93
94
95def abi_to_arch(arch):
96    return {
97        'armeabi': 'arm',
98        'armeabi-v7a': 'arm',
99        'armeabi-v7a-hard': 'arm',
100        'arm64-v8a': 'arm64',
101        'mips': 'mips',
102        'mips64': 'mips64',
103        'x86': 'x86',
104        'x86_64': 'x86_64',
105    }[arch]
106
107
108def android_path(*args):
109    top = os.path.realpath(os.path.join(THIS_DIR, '../../..'))
110    return os.path.normpath(os.path.join(top, *args))
111
112
113def sysroot_path(toolchain):
114    arch = toolchain_to_arch(toolchain)
115    version = default_api_level(arch)
116
117    prebuilt_ndk = 'prebuilts/ndk/current'
118    sysroot_subpath = 'platforms/android-{}/arch-{}'.format(version, arch)
119    return android_path(prebuilt_ndk, sysroot_subpath)
120
121
122def ndk_path(*args):
123    return android_path('ndk', *args)
124
125
126def toolchain_path(*args):
127    return android_path('toolchain', *args)
128
129
130def default_api_level(arch):
131    if '64' in arch:
132        return 21
133    else:
134        return 9
135
136
137def jobs_arg():
138    return '-j{}'.format(multiprocessing.cpu_count() * 2)
139
140
141def build(cmd, args, intermediate_package=False):
142    package_dir = args.out_dir if intermediate_package else args.dist_dir
143    common_args = [
144        '--verbose',
145        '--package-dir={}'.format(package_dir),
146    ]
147
148    build_env = dict(os.environ)
149    build_env['NDK_BUILDTOOLS_PATH'] = android_path('ndk/build/tools')
150    build_env['ANDROID_NDK_ROOT'] = ndk_path()
151    subprocess.check_call(cmd + common_args, env=build_env)
152
153
154def _get_dir_from_env(default, env_var):
155    path = os.path.realpath(os.getenv(env_var, default))
156    if not os.path.isdir(path):
157        os.makedirs(path)
158    return path
159
160
161def get_out_dir():
162    return _get_dir_from_env(android_path('out'), 'OUT_DIR')
163
164
165def get_dist_dir(out_dir):
166    return _get_dir_from_env(os.path.join(out_dir, 'dist'), 'DIST_DIR')
167
168
169def get_default_host():
170    if sys.platform in ('linux', 'linux2'):
171        return 'linux'
172    elif sys.platform == 'darwin':
173        return 'darwin'
174    else:
175        raise RuntimeError('Unsupported host: {}'.format(sys.platform))
176
177
178def host_to_tag(host):
179    if host in ['darwin', 'linux']:
180        return host + '-x86_64'
181    elif host == 'windows':
182        return 'windows'
183    elif host == 'windows64':
184        return 'windows-x86_64'
185    else:
186        raise RuntimeError('Unsupported host: {}'.format(host))
187
188
189def make_repo_prop(out_dir):
190    file_name = 'repo.prop'
191
192    dist_dir = os.environ.get('DIST_DIR')
193    if dist_dir is not None:
194        dist_repo_prop = os.path.join(dist_dir, file_name)
195        shutil.copy(dist_repo_prop, out_dir)
196    else:
197        out_file = os.path.join(out_dir, file_name)
198        with open(out_file, 'w') as prop_file:
199            cmd = [
200                'repo', 'forall', '-c',
201                'echo $REPO_PROJECT $(git rev-parse HEAD)',
202            ]
203            subprocess.check_call(cmd, stdout=prop_file)
204
205
206def make_package(name, directory, out_dir):
207    """Pacakges an NDK module for release.
208
209    Makes a zipfile of the single NDK module that can be released in the SDK
210    manager. This will handle the details of creating the repo.prop file for
211    the package.
212
213    Args:
214        name: Name of the final package, excluding extension.
215        directory: Directory to be packaged.
216        out_dir: Directory to place package.
217    """
218    if not os.path.isdir(directory):
219        raise ValueError('directory must be a directory: ' + directory)
220
221    path = os.path.join(out_dir, name + '.zip')
222    if os.path.exists(path):
223        os.unlink(path)
224
225    cwd = os.getcwd()
226    os.chdir(os.path.dirname(directory))
227    basename = os.path.basename(directory)
228    try:
229        subprocess.check_call(
230            ['zip', '-x', '*.pyc', '-x', '*.pyo', '-x', '*.swp',
231             '-x', '*.git*', '-9qr', path, basename])
232    finally:
233        os.chdir(cwd)
234
235    with zipfile.ZipFile(path, 'a', zipfile.ZIP_DEFLATED) as zip_file:
236        tmpdir = tempfile.mkdtemp()
237        try:
238            make_repo_prop(tmpdir)
239            arcname = os.path.join(basename, 'repo.prop')
240            zip_file.write(os.path.join(tmpdir, 'repo.prop'), arcname)
241        finally:
242            shutil.rmtree(tmpdir)
243
244
245class ArgParser(argparse.ArgumentParser):
246    def __init__(self):
247        super(ArgParser, self).__init__()
248
249        self.add_argument(
250            '--host', choices=('darwin', 'linux', 'windows', 'windows64'),
251            default=get_default_host(),
252            help='Build binaries for given OS (e.g. linux).')
253
254        self.add_argument(
255            '--out-dir', help='Directory to place temporary build files.',
256            type=os.path.realpath, default=get_out_dir())
257
258        # The default for --dist-dir has to be handled after parsing all
259        # arguments because the default is derived from --out-dir. This is
260        # handled in run().
261        self.add_argument(
262            '--dist-dir', help='Directory to place the packaged artifact.',
263            type=os.path.realpath)
264
265
266def run(main_func, arg_parser=ArgParser):
267    if 'ANDROID_BUILD_TOP' not in os.environ:
268        top = os.path.join(os.path.dirname(__file__), '../../..')
269        os.environ['ANDROID_BUILD_TOP'] = os.path.realpath(top)
270
271    args = arg_parser().parse_args()
272
273    if args.dist_dir is None:
274        args.dist_dir = get_dist_dir(args.out_dir)
275
276    # We want any paths to be relative to the invoked build script.
277    main_filename = os.path.realpath(sys.modules['__main__'].__file__)
278    os.chdir(os.path.dirname(main_filename))
279
280    main_func(args)
281