| #!/usr/bin/env python |
| # |
| # Copyright (C) 2016 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. |
| # |
| """NDK packaging APIs.""" |
| from __future__ import absolute_import |
| |
| import os |
| import shutil |
| import subprocess |
| import tempfile |
| from typing import Iterable, List, Optional, Set, Tuple |
| |
| import ndk.abis |
| from ndk.hosts import Host, host_to_tag |
| |
| |
| PACKAGE_VARIANTS = ( |
| 'abi', |
| 'arch', |
| 'host', |
| 'toolchain', |
| 'triple', |
| ) |
| |
| |
| def expand_paths(package: str, host: Host, |
| arches: Optional[Iterable[ndk.abis.Arch]]) -> List[str]: |
| """Expands package definition tuple into list of full package names. |
| |
| >>> expand_paths('gcc-{toolchain}-{host}', Host.Linux, ['arm', 'x86_64']) |
| ['gcc-arm-linux-androideabi-linux-x86_64', 'gcc-x86_64-linux-x86_64'] |
| |
| >>> expand_paths('gdbserver-{arch}', Host.Linux, ['arm64', 'x86_64']) |
| ['gdbserver-arm64', 'gdbserver-x86_64'] |
| |
| >>> expand_paths('llvm-{host}', Host.Linux, None) |
| ['llvm-linux-x86_64'] |
| |
| >>> expand_paths('platforms', Host.Linux, ['arm']) |
| ['platforms'] |
| |
| >>> expand_paths('libc++-{abi}', Host.Linux, ['arm']) |
| ['libc++-armeabi-v7a'] |
| |
| >>> expand_paths('binutils/{triple}', Host.Linux, ['arm', 'x86_64']) |
| ['binutils/arm-linux-androideabi', 'binutils/x86_64-linux-android'] |
| |
| >> expand_paths('toolchains/{toolchain}-4.9', Host.Linux, ['arm', 'x86']) |
| ['toolchains/arm-linux-androideabi-4.9', 'toolchains/x86-4.9'] |
| """ |
| host_tag = host_to_tag(host) |
| if arches is None: |
| return [package.format(host=host_tag)] |
| |
| seen_packages: Set[str] = set() |
| packages = [] |
| for arch in arches: |
| triple = ndk.abis.arch_to_triple(arch) |
| toolchain = ndk.abis.arch_to_toolchain(arch) |
| for abi in ndk.abis.arch_to_abis(arch): |
| expanded = package.format( |
| abi=abi, arch=arch, host=host_tag, triple=triple, |
| toolchain=toolchain) |
| if expanded not in seen_packages: |
| packages.append(expanded) |
| seen_packages.add(expanded) |
| return packages |
| |
| |
| def package_varies_by(install_path: str, variant: str) -> bool: |
| """Determines if a package varies by a given input. |
| |
| >>> package_varies_by('foo-{host}', 'host') |
| True |
| |
| >>> package_varies_by('foo', 'host') |
| False |
| |
| >>> package_varies_by('foo-{arch}', 'host') |
| False |
| """ |
| |
| if variant not in PACKAGE_VARIANTS: |
| raise ValueError |
| |
| variant_replacement_str = '{' + variant + '}' |
| return variant_replacement_str in install_path |
| |
| |
| def expand_packages(package: str, install_path: str, host: Host, |
| arches: List[ndk.abis.Arch]) -> Iterable[Tuple[str, str]]: |
| """Returns a list of tuples of `(package, install_path)`.""" |
| package_template = package |
| for variant in PACKAGE_VARIANTS: |
| if package_varies_by(install_path, variant): |
| package_template += '-{' + variant + '}' |
| |
| expanded_packages = expand_paths(package_template, host, arches) |
| expanded_installs = expand_paths(install_path, host, arches) |
| return zip(expanded_packages, expanded_installs) |
| |
| |
| def extract_zip(package_path: str, install_path: str) -> None: |
| """Extracts the contents of a zipfile to a directory. |
| |
| This behaves similar to the following shell commands (using tar instead of |
| zip because `unzip` doesn't support `--strip-components`): |
| |
| mkdir -p $install_path |
| tar xf $package_path -C $install_path --strip-components=1 |
| |
| That is, the first directory in the package is stripped and the contents |
| are placed in the install path. |
| |
| Args: |
| package_path: Path to the zip file to extract. |
| install_path: Directory in which to extract zip contents. |
| |
| Raises: |
| RuntimeError: The zip file was not in the allowed format. i.e. the zip |
| had more than one top level directory or was empty. |
| """ |
| package_name = os.path.basename(package_path) |
| extract_dir = tempfile.mkdtemp() |
| try: |
| subprocess.check_call( |
| ['unzip', '-q', package_path, '-d', extract_dir]) |
| dirs = os.listdir(extract_dir) |
| if len(dirs) > 1: |
| msg = 'Package has more than one root directory: ' + package_name |
| raise RuntimeError(msg) |
| if not dirs: |
| raise RuntimeError('Package was empty: ' + package_name) |
| parent_dir = os.path.dirname(install_path) |
| if not os.path.exists(parent_dir): |
| os.makedirs(parent_dir) |
| shutil.move(os.path.join(extract_dir, dirs[0]), install_path) |
| finally: |
| shutil.rmtree(extract_dir) |