build_support.py 7.27 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#
# Copyright (C) 2015 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 multiprocessing
import os
19
import shutil
20 21
import subprocess
import sys
22
import tempfile
23
import zipfile
24 25


26 27 28
THIS_DIR = os.path.realpath(os.path.dirname(__file__))


29
# TODO: Make the x86 toolchain names just be the triple.
30 31 32 33 34 35 36 37 38 39
ALL_TOOLCHAINS = (
    'arm-linux-androideabi',
    'aarch64-linux-android',
    'mipsel-linux-android',
    'mips64el-linux-android',
    'x86',
    'x86_64',
)


40 41 42 43 44
ALL_TRIPLES = (
    'arm-linux-androideabi',
    'aarch64-linux-android',
    'mipsel-linux-android',
    'mips64el-linux-android',
Dan Albert's avatar
Dan Albert committed
45
    'i686-linux-android',
46 47 48 49
    'x86_64-linux-android',
)


50 51 52 53 54 55 56 57 58 59
ALL_ARCHITECTURES = (
    'arm',
    'arm64',
    'mips',
    'mips64',
    'x86',
    'x86_64',
)


60 61 62 63 64 65 66 67 68 69 70 71
ALL_ABIS = (
    'armeabi',
    'armeabi-v7a',
    'armeabi-v7a-hard',
    'arm64-v8a',
    'mips',
    'mips64',
    'x86',
    'x86_64',
)


72 73 74 75
def arch_to_toolchain(arch):
    return dict(zip(ALL_ARCHITECTURES, ALL_TOOLCHAINS))[arch]


76 77 78 79
def arch_to_triple(arch):
    return dict(zip(ALL_ARCHITECTURES, ALL_TRIPLES))[arch]


80 81 82 83
def toolchain_to_arch(toolchain):
    return dict(zip(ALL_TOOLCHAINS, ALL_ARCHITECTURES))[toolchain]


Dan Albert's avatar
Dan Albert committed
84 85 86 87 88 89 90 91 92 93 94
def arch_to_abis(arch):
    return {
        'arm': ['armeabi', 'armeabi-v7a', 'armeabi-v7a-hard'],
        'arm64': ['arm64-v8a'],
        'mips': ['mips'],
        'mips64': ['mips64'],
        'x86': ['x86'],
        'x86_64': ['x86_64'],
    }[arch]


95 96 97 98 99 100 101 102 103 104 105 106 107
def abi_to_arch(arch):
    return {
        'armeabi': 'arm',
        'armeabi-v7a': 'arm',
        'armeabi-v7a-hard': 'arm',
        'arm64-v8a': 'arm64',
        'mips': 'mips',
        'mips64': 'mips64',
        'x86': 'x86',
        'x86_64': 'x86_64',
    }[arch]


108 109
def android_path(*args):
    top = os.path.realpath(os.path.join(THIS_DIR, '../../..'))
110
    return os.path.normpath(os.path.join(top, *args))
111 112 113 114 115 116 117 118


def sysroot_path(toolchain):
    arch = toolchain_to_arch(toolchain)
    version = default_api_level(arch)

    prebuilt_ndk = 'prebuilts/ndk/current'
    sysroot_subpath = 'platforms/android-{}/arch-{}'.format(version, arch)
119
    return android_path(prebuilt_ndk, sysroot_subpath)
120 121


122 123
def ndk_path(*args):
    return android_path('ndk', *args)
124 125


126 127
def toolchain_path(*args):
    return android_path('toolchain', *args)
128 129 130 131 132 133 134 135 136 137 138 139 140


def default_api_level(arch):
    if '64' in arch:
        return 21
    else:
        return 9


def jobs_arg():
    return '-j{}'.format(multiprocessing.cpu_count() * 2)


141 142
def build(cmd, args, intermediate_package=False):
    package_dir = args.out_dir if intermediate_package else args.dist_dir
143 144
    common_args = [
        '--verbose',
145
        '--package-dir={}'.format(package_dir),
146 147 148 149 150 151 152 153
    ]

    build_env = dict(os.environ)
    build_env['NDK_BUILDTOOLS_PATH'] = android_path('ndk/build/tools')
    build_env['ANDROID_NDK_ROOT'] = ndk_path()
    subprocess.check_call(cmd + common_args, env=build_env)


154 155 156 157 158 159 160 161 162 163 164 165 166
def _get_dir_from_env(default, env_var):
    path = os.path.realpath(os.getenv(env_var, default))
    if not os.path.isdir(path):
        os.makedirs(path)
    return path


def get_out_dir():
    return _get_dir_from_env(android_path('out'), 'OUT_DIR')


def get_dist_dir(out_dir):
    return _get_dir_from_env(os.path.join(out_dir, 'dist'), 'DIST_DIR')
167 168 169 170 171 172 173 174 175 176


def get_default_host():
    if sys.platform in ('linux', 'linux2'):
        return 'linux'
    elif sys.platform == 'darwin':
        return 'darwin'
    else:
        raise RuntimeError('Unsupported host: {}'.format(sys.platform))

177

178 179 180 181
def host_to_tag(host):
    if host in ['darwin', 'linux']:
        return host + '-x86_64'
    elif host == 'windows':
182
        return 'windows'
183 184 185 186
    elif host == 'windows64':
        return 'windows-x86_64'
    else:
        raise RuntimeError('Unsupported host: {}'.format(host))
187

188

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
def make_repo_prop(out_dir):
    file_name = 'repo.prop'

    dist_dir = os.environ.get('DIST_DIR')
    if dist_dir is not None:
        dist_repo_prop = os.path.join(dist_dir, file_name)
        shutil.copy(dist_repo_prop, out_dir)
    else:
        out_file = os.path.join(out_dir, file_name)
        with open(out_file, 'w') as prop_file:
            cmd = [
                'repo', 'forall', '-c',
                'echo $REPO_PROJECT $(git rev-parse HEAD)',
            ]
            subprocess.check_call(cmd, stdout=prop_file)


206
def make_package(name, directory, out_dir):
207 208
    """Pacakges an NDK module for release.

209
    Makes a zipfile of the single NDK module that can be released in the SDK
210 211 212 213 214
    manager. This will handle the details of creating the repo.prop file for
    the package.

    Args:
        name: Name of the final package, excluding extension.
215
        directory: Directory to be packaged.
216 217
        out_dir: Directory to place package.
    """
218 219 220
    if not os.path.isdir(directory):
        raise ValueError('directory must be a directory: ' + directory)

221 222 223 224 225 226 227 228 229
    path = os.path.join(out_dir, name + '.zip')
    if os.path.exists(path):
        os.unlink(path)

    cwd = os.getcwd()
    os.chdir(os.path.dirname(directory))
    basename = os.path.basename(directory)
    try:
        subprocess.check_call(
Josh Gao's avatar
Josh Gao committed
230
            ['zip', '-x', '*.pyc', '-x', '*.pyo', '-x', '*.swp',
231 232 233 234 235
             '-x', '*.git*', '-9qr', path, basename])
    finally:
        os.chdir(cwd)

    with zipfile.ZipFile(path, 'a', zipfile.ZIP_DEFLATED) as zip_file:
236 237 238
        tmpdir = tempfile.mkdtemp()
        try:
            make_repo_prop(tmpdir)
239
            arcname = os.path.join(basename, 'repo.prop')
240
            zip_file.write(os.path.join(tmpdir, 'repo.prop'), arcname)
241 242 243 244
        finally:
            shutil.rmtree(tmpdir)


245 246 247 248 249 250 251 252
class ArgParser(argparse.ArgumentParser):
    def __init__(self):
        super(ArgParser, self).__init__()

        self.add_argument(
            '--host', choices=('darwin', 'linux', 'windows', 'windows64'),
            default=get_default_host(),
            help='Build binaries for given OS (e.g. linux).')
253 254 255 256 257 258 259 260

        self.add_argument(
            '--out-dir', help='Directory to place temporary build files.',
            type=os.path.realpath, default=get_out_dir())

        # The default for --dist-dir has to be handled after parsing all
        # arguments because the default is derived from --out-dir. This is
        # handled in run().
261
        self.add_argument(
262 263
            '--dist-dir', help='Directory to place the packaged artifact.',
            type=os.path.realpath)
264 265 266 267 268 269 270


def run(main_func, arg_parser=ArgParser):
    if 'ANDROID_BUILD_TOP' not in os.environ:
        top = os.path.join(os.path.dirname(__file__), '../../..')
        os.environ['ANDROID_BUILD_TOP'] = os.path.realpath(top)

271 272
    args = arg_parser().parse_args()

273 274 275
    if args.dist_dir is None:
        args.dist_dir = get_dist_dir(args.out_dir)

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)