blob: 8ff653dfa85d2f91baa6c16407088a705949e308 [file] [log] [blame]
#!/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)