| # |
| # 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. |
| # |
| """Defines the NDK build system API. |
| |
| Note: this isn't the ndk-build API, but the API for building the NDK itself. |
| """ |
| from __future__ import absolute_import |
| |
| import ntpath |
| import os |
| import shutil |
| import stat |
| import subprocess |
| |
| import ndk.abis |
| import ndk.packaging |
| import ndk.paths |
| |
| |
| class ModuleValidateError(RuntimeError): |
| pass |
| |
| |
| class Module(object): |
| name = None |
| path = None |
| deps = set() |
| |
| # If split_build_by_arch is set, one workqueue task will be created for |
| # each architecture. The Module object will be cloned for each arch and |
| # each will have build_arch set to the architecture that should be built by |
| # that module. If build_arch is None, the module has not yet been split. |
| split_build_by_arch = False |
| build_arch = None |
| |
| def __init__(self): |
| self.validate() |
| |
| def validate_error(self, msg): |
| return ModuleValidateError('{}: {}'.format(self.name, msg)) |
| |
| def validate(self): |
| if self.name is None: |
| raise ModuleValidateError('{} has no name'.format(self.__class__)) |
| if self.path is None: |
| raise self.validate_error('path property not set') |
| |
| def build(self, build_dir, dist_dir, args): |
| raise NotImplementedError |
| |
| def install(self, out_dir, dist_dir, args): |
| arches = ndk.abis.ALL_ARCHITECTURES |
| if args.arch is not None: |
| arches = [args.arch] |
| package_installs = ndk.packaging.expand_packages( |
| self.name, self.path, args.system, arches) |
| |
| install_base = ndk.paths.get_install_path(out_dir) |
| for package_name, package_install in package_installs: |
| install_path = os.path.join(install_base, package_install) |
| package = os.path.join(dist_dir, package_name) |
| if os.path.exists(install_path): |
| shutil.rmtree(install_path) |
| ndk.packaging.extract_zip(package, install_path) |
| |
| self.validate_notice(install_path) |
| |
| def get_install_paths(self, build_dir, host, arches): |
| install_subdirs = ndk.packaging.expand_paths(self.path, host, arches) |
| install_base = ndk.paths.get_install_path(build_dir) |
| return [os.path.join(install_base, d) for d in install_subdirs] |
| |
| def get_install_path(self, build_dir, host, arch=None): |
| arch_dependent = False |
| if ndk.packaging.package_varies_by(self.path, 'abi'): |
| arch_dependent = True |
| elif ndk.packaging.package_varies_by(self.path, 'arch'): |
| arch_dependent = True |
| elif ndk.packaging.package_varies_by(self.path, 'toolchain'): |
| arch_dependent = True |
| elif ndk.packaging.package_varies_by(self.path, 'triple'): |
| arch_dependent = True |
| |
| arches = None |
| if arch is not None: |
| arches = [arch] |
| elif arch_dependent: |
| raise ValueError( |
| 'get_install_path for {} requires valid arch'.format(arch)) |
| |
| install_subdirs = self.get_install_paths(build_dir, host, arches) |
| |
| if len(install_subdirs) != 1: |
| raise RuntimeError( |
| 'non-unique install path for single arch: ' + self.path) |
| |
| return install_subdirs[0] |
| |
| def validate_notice(self, install_path): |
| license_file = os.path.join(install_path, 'NOTICE') |
| if not os.path.exists(license_file): |
| raise RuntimeError('{} did not install a NOTICE file at {}'.format( |
| self.name, license_file)) |
| |
| def __str__(self): |
| if self.split_build_by_arch and self.build_arch is not None: |
| return '{} [{}]'.format(self.name, self.build_arch) |
| return self.name |
| |
| def __hash__(self): |
| # The string representation of each module must be unique. This is true |
| # both pre- and post-arch split. |
| return hash(str(self)) |
| |
| def __eq__(self, other): |
| # As with hash(), the str must be unique across all modules. |
| return str(self) == str(other) |
| |
| @property |
| def log_file(self): |
| if self.split_build_by_arch and self.build_arch is not None: |
| return '{}-{}.log'.format(self.name, self.build_arch) |
| elif self.split_build_by_arch: |
| raise RuntimeError('Called log_file on unsplit module') |
| else: |
| return '{}.log'.format(self.name) |
| |
| def log_path(self, log_dir): |
| return os.path.join(log_dir, self.log_file) |
| |
| |
| class PackageModule(Module): |
| src = None |
| create_repo_prop = False |
| |
| def validate(self): |
| super(PackageModule, self).validate() |
| |
| if ndk.packaging.package_varies_by(self.path, 'abi'): |
| raise self.validate_error( |
| 'PackageModule cannot vary by abi') |
| if ndk.packaging.package_varies_by(self.path, 'arch'): |
| raise self.validate_error( |
| 'PackageModule cannot vary by arch') |
| if ndk.packaging.package_varies_by(self.path, 'toolchain'): |
| raise self.validate_error( |
| 'PackageModule cannot vary by toolchain') |
| if ndk.packaging.package_varies_by(self.path, 'triple'): |
| raise self.validate_error( |
| 'PackageModule cannot vary by triple') |
| |
| def build(self, _build_dir, _dist_dir, _args): |
| pass |
| |
| def install(self, out_dir, _dist_dir, args): |
| install_paths = self.get_install_paths( |
| out_dir, args.system, ndk.abis.ALL_ARCHITECTURES) |
| assert len(install_paths) == 1 |
| install_path = install_paths[0] |
| install_directory(self.src, install_path) |
| if self.create_repo_prop: |
| make_repo_prop(install_path) |
| self.validate_notice(install_path) |
| |
| |
| class InvokeExternalBuildModule(Module): |
| script = None |
| arch_specific = False |
| |
| def build(self, build_dir, dist_dir, args): |
| build_args = common_build_args(build_dir, dist_dir, args) |
| if self.split_build_by_arch: |
| build_args.append('--arch={}'.format(self.build_arch)) |
| elif self.arch_specific and args.arch is not None: |
| build_args.append('--arch={}'.format(args.arch)) |
| build_args.extend(self.additional_args(args)) |
| script = self.get_script_path() |
| invoke_external_build(script, build_args) |
| |
| def get_script_path(self): |
| return ndk.paths.android_path(self.script) |
| |
| def additional_args(self, _args): # pylint: disable=no-self-use |
| return [] |
| |
| |
| class InvokeBuildModule(InvokeExternalBuildModule): |
| def get_script_path(self): |
| return ndk.paths.ndk_path('build/tools', self.script) |
| |
| |
| class FileModule(Module): |
| src = None |
| |
| def build(self, _build_dir, _dist_dir, _args): |
| pass |
| |
| def install(self, out_dir, dist_dir, args): |
| install_base = ndk.paths.get_install_path(out_dir) |
| install_path = os.path.join(install_base, self.path) |
| if os.path.exists(install_path): |
| os.remove(install_path) |
| shutil.copy2(self.src, install_path) |
| |
| |
| class ScriptShortcutModule(Module): |
| script = None |
| windows_ext = None |
| |
| def validate_notice(self, _install_base): |
| # These are all trivial shell scripts that we generated. No notice |
| # needed. |
| pass |
| |
| def validate(self): |
| super(ScriptShortcutModule, self).validate() |
| |
| if ndk.packaging.package_varies_by(self.script, 'abi'): |
| raise self.validate_error( |
| 'ScriptShortcutModule cannot vary by abi') |
| if ndk.packaging.package_varies_by(self.script, 'arch'): |
| raise self.validate_error( |
| 'ScriptShortcutModule cannot vary by arch') |
| if ndk.packaging.package_varies_by(self.script, 'toolchain'): |
| raise self.validate_error( |
| 'ScriptShortcutModule cannot vary by toolchain') |
| if ndk.packaging.package_varies_by(self.script, 'triple'): |
| raise self.validate_error( |
| 'ScriptShortcutModule cannot vary by triple') |
| if self.windows_ext is None: |
| raise self.validate_error( |
| 'ScriptShortcutModule requires windows_ext') |
| |
| def build(self, _build_dir, _dist_dir, _args): |
| pass |
| |
| def install(self, out_dir, dist_dir, args): |
| if args.system.startswith('windows'): |
| self.make_cmd_helper(out_dir, args.system) |
| else: |
| self.make_sh_helper(out_dir, args.system) |
| |
| def make_cmd_helper(self, out_dir, system): |
| script = self.get_script_path(system) |
| full_path = ntpath.join( |
| '%~dp0', ntpath.normpath(script) + self.windows_ext) |
| |
| install_base = ndk.paths.get_install_path(out_dir) |
| install_path = os.path.join(install_base, self.path) + '.cmd' |
| with open(os.path.join(install_path), 'w') as helper: |
| helper.writelines([ |
| '@echo off\n', |
| full_path + ' %*\n', |
| ]) |
| |
| def make_sh_helper(self, out_dir, system): |
| script = self.get_script_path(system) |
| |
| install_base = ndk.paths.get_install_path(out_dir) |
| install_path = os.path.join(install_base, self.path) |
| |
| full_path = os.path.join('$DIR', script) |
| with open(install_path, 'w') as helper: |
| helper.writelines([ |
| '#!/bin/sh\n', |
| 'DIR="$(cd "$(dirname "$0")" && pwd)"\n', |
| full_path + ' "$@"', |
| ]) |
| mode = os.stat(install_path).st_mode |
| os.chmod(install_path, |
| mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) |
| |
| def get_script_path(self, system): |
| scripts = ndk.packaging.expand_paths( |
| self.script, system, ndk.abis.ALL_ARCHITECTURES) |
| assert len(scripts) == 1 |
| return scripts[0] |
| |
| |
| class PythonPackage(Module): |
| def build(self, build_dir, _dist_dir, _args): |
| cwd = os.path.dirname(self.path) |
| subprocess.check_call( |
| ['python', self.path, 'sdist', '-d', build_dir], cwd=cwd) |
| |
| def install(self, _out_dir, _dist_dir, _args): |
| pass |
| |
| |
| def _invoke_build(script, args): |
| if args is None: |
| args = [] |
| subprocess.check_call([ndk.paths.android_path(script)] + args) |
| |
| |
| def invoke_build(script, args=None): |
| script_path = os.path.join('build/tools', script) |
| _invoke_build(ndk.paths.ndk_path(script_path), args) |
| |
| |
| def invoke_external_build(script, args=None): |
| _invoke_build(ndk.paths.android_path(script), args) |
| |
| |
| def common_build_args(out_dir, dist_dir, args): |
| return [ |
| '--out-dir={}'.format(out_dir), |
| '--dist-dir={}'.format(dist_dir), |
| '--host={}'.format(args.system), |
| ] |
| |
| |
| def install_directory(src, dst): |
| if os.path.exists(dst): |
| shutil.rmtree(dst) |
| ignore_patterns = shutil.ignore_patterns( |
| '*.pyc', '*.pyo', '*.swp', '*.git*') |
| shutil.copytree(src, dst, ignore=ignore_patterns) |
| |
| |
| 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) |