Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2015 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 #
     17 """Makes an old style monolithic NDK package out of individual modules."""
     18 from __future__ import print_function
     19 
     20 import argparse
     21 import datetime
     22 import os
     23 import shutil
     24 import site
     25 import stat
     26 import subprocess
     27 import sys
     28 import tempfile
     29 import zipfile
     30 
     31 
     32 site.addsitedir(os.path.join(os.path.dirname(__file__), '../lib'))
     33 import build_support  # pylint: disable=import-error
     34 
     35 
     36 THIS_DIR = os.path.dirname(__file__)
     37 ANDROID_TOP = os.path.realpath(os.path.join(THIS_DIR, '../../..'))
     38 
     39 # Note that we can't actually support creating both layouts from the same
     40 # sources because changes are needed in gnustl's Android.mk and in the build
     41 # system. The various "USE_NEW_LAYOUT" blocks are so we can more easily undo
     42 # this change when we finalize a new layout.
     43 USE_NEW_LAYOUT = False
     44 
     45 
     46 def expand_packages(package, host, arches):
     47     """Expands package definition tuple into list of full package names.
     48 
     49     >>> expand_packages('gcc-{arch}-{host}', 'linux', ['arm64', 'x86_64'])
     50     ['gcc-arm64-linux-x86_64', 'gcc-x86_64-linux-x86_64']
     51 
     52     >>> expand_packages('gcclibs-{arch}', 'linux', ['arm64', 'x86_64'])
     53     ['gcclibs-arm64', 'gcclibs-x86_64']
     54 
     55     >>> expand_packages('llvm-{host}', 'linux', ['arm'])
     56     ['llvm-linux-x86_64']
     57 
     58     >>> expand_packages('platforms', 'linux', ['arm'])
     59     ['platforms']
     60 
     61     >>> expand_packages('libc++-{abi}', 'linux', ['arm'])
     62     ['libc++-armeabi', 'libc++-armeabi-v7a', 'libc++-armeabi-v7a-hard']
     63 
     64     >>> expand_packages('binutils/{triple}', 'linux', ['arm', 'x86_64'])
     65     ['binutils/arm-linux-androideabi', 'binutils/x86_64-linux-android']
     66 
     67     >> expand_packages('toolchains/{toolchain}-4.9', 'linux', ['arm', 'x86'])
     68     ['toolchains/arm-linux-androideabi-4.9', 'toolchains/x86-4.9']
     69     """
     70     host_tag = build_support.host_to_tag(host)
     71     seen_packages = set()
     72     packages = []
     73     for arch in arches:
     74         triple = build_support.arch_to_triple(arch)
     75         toolchain = build_support.arch_to_toolchain(arch)
     76         for abi in build_support.arch_to_abis(arch):
     77             expanded = package.format(
     78                 abi=abi, arch=arch, host=host_tag, triple=triple,
     79                 toolchain=toolchain)
     80             if expanded not in seen_packages:
     81                 packages.append(expanded)
     82             seen_packages.add(expanded)
     83     return packages
     84 
     85 
     86 def get_all_packages(host, arches):
     87     new_layout = [
     88         ('binutils-{arch}-{host}', 'binutils/{triple}'),
     89         ('build', 'build'),
     90         ('cpufeatures', 'sources/android/cpufeatures'),
     91         ('gabixx', 'sources/cxx-stl/gabi++'),
     92         ('gcc-{arch}-{host}', 'toolchains/{toolchain}-4.9'),
     93         ('gcclibs-{arch}', 'gcclibs/{triple}'),
     94         ('gdbserver-{arch}', 'gdbserver/{arch}'),
     95         ('gnustl-4.9', 'sources/cxx-stl/gnu-libstdc++'),
     96         ('gtest', 'sources/third_party/googletest'),
     97         ('host-tools-{host}', 'host-tools'),
     98         ('libandroid_support', 'sources/android/support'),
     99         ('libcxx', 'sources/cxx-stl/llvm-libc++'),
    100         ('libcxxabi', 'sources/cxx-stl/llvm-libc++abi'),
    101         ('llvm-{host}', 'toolchains/llvm'),
    102         ('native_app_glue', 'sources/android/native_app_glue'),
    103         ('ndk_helper', 'sources/android/ndk_helper'),
    104         ('python-packages', 'python-packages'),
    105         ('stlport', 'sources/cxx-stl/stlport'),
    106         ('system-stl', 'sources/cxx-stl/system'),
    107     ]
    108 
    109     old_layout = [
    110         ('build', 'build'),
    111         ('cpufeatures', 'sources/android/cpufeatures'),
    112         ('gabixx', 'sources/cxx-stl/gabi++'),
    113         ('gcc-{arch}-{host}', 'toolchains/{toolchain}-4.9/prebuilt/{host}'),
    114         ('gdbserver-{arch}', 'prebuilt/android-{arch}/gdbserver'),
    115         ('gnustl-4.9', 'sources/cxx-stl/gnu-libstdc++/4.9'),
    116         ('gtest', 'sources/third_party/googletest'),
    117         ('host-tools-{host}', 'prebuilt/{host}'),
    118         ('libandroid_support', 'sources/android/support'),
    119         ('libcxx', 'sources/cxx-stl/llvm-libc++'),
    120         ('libcxxabi', 'sources/cxx-stl/llvm-libc++abi'),
    121         ('llvm-{host}', 'toolchains/llvm/prebuilt/{host}'),
    122         ('native_app_glue', 'sources/android/native_app_glue'),
    123         ('ndk_helper', 'sources/android/ndk_helper'),
    124         ('python-packages', 'python-packages'),
    125         ('stlport', 'sources/cxx-stl/stlport'),
    126         ('system-stl', 'sources/cxx-stl/system'),
    127     ]
    128 
    129     if USE_NEW_LAYOUT:
    130         packages = new_layout
    131     else:
    132         packages = old_layout
    133 
    134     platforms_path = 'development/ndk/platforms'
    135     for platform_dir in os.listdir(build_support.android_path(platforms_path)):
    136         if not platform_dir.startswith('android-'):
    137             continue
    138         _, platform_str = platform_dir.split('-')
    139         package_name = 'platform-' + platform_str
    140         install_path = 'platforms/android-' + platform_str
    141         packages.append((package_name, install_path))
    142 
    143     expanded = []
    144     for package, extract_path in packages:
    145         package_names = expand_packages(package, host, arches)
    146         extract_paths = expand_packages(extract_path, host, arches)
    147         expanded.extend(zip(package_names, extract_paths))
    148     return expanded
    149 
    150 
    151 def check_packages(path, packages):
    152     for package, _ in packages:
    153         print('Checking ' + package)
    154         package_path = os.path.join(path, package + '.zip')
    155         if not os.path.exists(package_path):
    156             raise RuntimeError('Missing package: ' + package_path)
    157 
    158         top_level_files = []
    159         with zipfile.ZipFile(package_path, 'r') as zip_file:
    160             for f in zip_file.namelist():
    161                 components = os.path.split(f)
    162                 if len(components) == 2:
    163                     top_level_files.append(components[1])
    164 
    165         if 'repo.prop' not in top_level_files:
    166             msg = 'Package does not contain a repo.prop: ' + package_path
    167             raise RuntimeError(msg)
    168 
    169         if 'NOTICE' not in top_level_files:
    170             msg = 'Package does not contain a NOTICE: ' + package_path
    171             raise RuntimeError(msg)
    172 
    173 
    174 def extract_all(path, packages, out_dir):
    175     os.makedirs(out_dir)
    176     for package, extract_path in packages:
    177         print('Unpacking ' + package)
    178         package_path = os.path.join(path, package + '.zip')
    179         install_dir = os.path.join(out_dir, extract_path)
    180 
    181         if os.path.exists(install_dir):
    182             raise RuntimeError('Install path already exists: ' + install_dir)
    183 
    184         if extract_path == '.':
    185             raise RuntimeError('Found old style package: ' + package)
    186 
    187         extract_dir = tempfile.mkdtemp()
    188         try:
    189             subprocess.check_call(
    190                 ['unzip', '-q', package_path, '-d', extract_dir])
    191             dirs = os.listdir(extract_dir)
    192             if len(dirs) > 1:
    193                 msg = 'Package has more than one root directory: ' + package
    194                 raise RuntimeError(msg)
    195             elif len(dirs) == 0:
    196                 raise RuntimeError('Package was empty: ' + package)
    197             parent_dir = os.path.dirname(install_dir)
    198             if not os.path.exists(parent_dir):
    199                 os.makedirs(parent_dir)
    200             shutil.move(os.path.join(extract_dir, dirs[0]), install_dir)
    201         finally:
    202             shutil.rmtree(extract_dir)
    203 
    204     if not USE_NEW_LAYOUT:
    205         # FIXME(danalbert): OMG HACK
    206         # The old package layout had libstdc++'s Android.mk at
    207         # sources/cxx-stl/gnu-libstdc++/Android.mk. The gnustl package doesn't
    208         # include the version in the path. To mimic the old package layout, we
    209         # extract the gnustl package to sources/cxx-stl/gnu-libstdc++/4.9. As
    210         # such, the Android.mk ends up in the 4.9 directory. We need to pull it
    211         # up a directory.
    212         gnustl_path = os.path.join(out_dir, 'sources/cxx-stl/gnu-libstdc++')
    213         shutil.move(os.path.join(gnustl_path, '4.9/Android.mk'),
    214                     os.path.join(gnustl_path, 'Android.mk'))
    215 
    216 
    217 def make_ndk_build_shortcut(out_dir, host):
    218     if host.startswith('windows'):
    219         make_ndk_build_cmd_helper(out_dir)
    220     else:
    221         make_ndk_build_sh_helper(out_dir)
    222 
    223 
    224 def make_ndk_build_cmd_helper(out_dir):
    225     with open(os.path.join(out_dir, 'ndk-build.cmd'), 'w') as helper:
    226         helper.writelines([
    227             '@echo off\n',
    228             r'%~dp0\build\ndk-build.cmd %*',
    229         ])
    230 
    231 
    232 def make_ndk_build_sh_helper(out_dir):
    233     file_path = os.path.join(out_dir, 'ndk-build')
    234     with open(file_path, 'w') as helper:
    235         helper.writelines([
    236             '#!/bin/sh\n',
    237             'DIR="$(cd "$(dirname "$0")" && pwd)"\n',
    238             '$DIR/build/ndk-build "$@"',
    239         ])
    240     mode = os.stat(file_path).st_mode
    241     os.chmod(file_path, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
    242 
    243 
    244 def make_source_properties(out_dir):
    245     path = os.path.join(out_dir, 'source.properties')
    246     with open(path, 'w') as source_properties:
    247         source_properties.write('\n'.join([
    248             'Pkg.Desc = Android NDK',
    249             'Pkg.Revision = 11.0.0',
    250         ]))
    251 
    252 
    253 def copy_changelog(out_dir):
    254     changelog_path = build_support.ndk_path('CHANGELOG.md')
    255     shutil.copy2(changelog_path, out_dir)
    256 
    257 
    258 def make_package(release, package_dir, packages, host, out_dir, temp_dir):
    259     release_name = 'android-ndk-{}'.format(release)
    260     extract_dir = os.path.join(temp_dir, release_name)
    261     if os.path.exists(extract_dir):
    262         shutil.rmtree(extract_dir)
    263     extract_all(package_dir, packages, extract_dir)
    264 
    265     make_ndk_build_shortcut(extract_dir, host)
    266     make_source_properties(extract_dir)
    267     copy_changelog(extract_dir)
    268 
    269     host_tag = build_support.host_to_tag(host)
    270     package_name = '{}-{}'.format(release_name, host_tag)
    271     package_path = os.path.join(out_dir, package_name)
    272     print('Packaging ' + package_name)
    273     files = os.path.relpath(extract_dir, temp_dir)
    274 
    275     if host.startswith('windows'):
    276         _make_zip_package(package_path, temp_dir, files)
    277     else:
    278         _make_tar_package(package_path, temp_dir, files)
    279 
    280 
    281 def _make_tar_package(package_path, base_dir, files):
    282     subprocess.check_call(
    283         ['tar', 'cjf', package_path + '.tar.bz2', '-C', base_dir, files])
    284 
    285 
    286 def _make_zip_package(package_path, base_dir, files):
    287     cwd = os.getcwd()
    288     package_path = os.path.realpath(package_path)
    289     os.chdir(base_dir)
    290     try:
    291         subprocess.check_call(['zip', '-9qr', package_path + '.zip', files])
    292     finally:
    293         os.chdir(cwd)
    294 
    295 
    296 class ArgParser(argparse.ArgumentParser):
    297     def __init__(self):
    298         super(ArgParser, self).__init__(
    299             description='Repackages NDK modules as a monolithic package. If '
    300                         '--unpack is used, instead installs the monolithic '
    301                         'package to a directory.')
    302 
    303         self.add_argument(
    304             '--arch', choices=build_support.ALL_ARCHITECTURES,
    305             help='Bundle only the given architecture.')
    306         self.add_argument(
    307             '--host', choices=('darwin', 'linux', 'windows', 'windows64'),
    308             default=build_support.get_default_host(),
    309             help='Package binaries for given OS (e.g. linux).')
    310         self.add_argument(
    311             '--release', default=datetime.date.today().strftime('%Y%m%d'),
    312             help='Release name for the package.')
    313 
    314         self.add_argument(
    315             '-f', '--force', dest='force', action='store_true',
    316             help='Clobber out directory if it exists.')
    317         self.add_argument(
    318             '--dist-dir', type=os.path.realpath,
    319             default=build_support.get_dist_dir(build_support.get_out_dir()),
    320             help='Directory containing NDK modules.')
    321         self.add_argument(
    322             '--unpack', action='store_true',
    323             help='Unpack the NDK to a directory rather than make a tarball.')
    324         self.add_argument(
    325             'out_dir', metavar='OUT_DIR',
    326             help='Directory to install bundle or tarball to.')
    327 
    328 
    329 def main():
    330     if 'ANDROID_BUILD_TOP' not in os.environ:
    331         os.environ['ANDROID_BUILD_TOP'] = ANDROID_TOP
    332 
    333     args = ArgParser().parse_args()
    334     arches = build_support.ALL_ARCHITECTURES
    335     if args.arch is not None:
    336         arches = [args.arch]
    337 
    338     if os.path.exists(args.out_dir) and args.unpack:
    339         if args.force:
    340             shutil.rmtree(args.out_dir)
    341         else:
    342             sys.exit(args.out_dir + ' already exists. Use -f to overwrite.')
    343 
    344     packages = get_all_packages(args.host, arches)
    345     check_packages(args.dist_dir, packages)
    346 
    347     if args.unpack:
    348         extract_all(args.dist_dir, packages, args.out_dir)
    349         make_ndk_build_shortcut(args.out_dir, args.host)
    350     else:
    351         make_package(args.release, args.dist_dir, packages, args.host,
    352                      args.out_dir, build_support.get_out_dir())
    353 
    354 
    355 if __name__ == '__main__':
    356     main()
    357