| # |
| # Copyright (C) 2020 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. |
| # |
| """APIs for dealing with cmake scripts.""" |
| |
| import os |
| from pathlib import Path |
| import pprint |
| import shlex |
| import shutil |
| import subprocess |
| from typing import Dict, List, Optional |
| |
| from ndk.hosts import Host, get_default_host |
| import ndk.paths |
| import ndk.toolchains |
| |
| SYSTEM_NAME_MAP = { |
| Host.Darwin: 'Darwin', |
| Host.Linux: 'Linux', |
| Host.Windows64: 'Windows' |
| } |
| |
| HOST_TRIPLE_MAP = { |
| Host.Darwin: 'x86_64-apple-darwin', |
| Host.Linux: 'x86_64-linux-gnu', |
| Host.Windows64: 'x86_64-w64-mingw32', |
| } |
| |
| |
| class CMakeBuilder: |
| """Builder for an cmake project.""" |
| |
| toolchain: ndk.toolchains.Toolchain |
| |
| def __init__(self, |
| src_path: Path, |
| build_dir: Path, |
| host: Host, |
| additional_flags: List[str] = None, |
| additional_env: Optional[Dict[str, str]] = None) -> None: |
| """Initializes an autoconf builder. |
| |
| Args: |
| src_path: Path to the cmake project. |
| build_dir: Directory to use for building. If the directory exists, |
| it will be deleted and recreated to ensure the build is correct. |
| host: Host to be used for the --host argument (the |
| cross-compilation target). |
| additional_flags: Additional flags to pass to the compiler. |
| additional_env: Additional environment to set, used during |
| configure, build, and install. |
| """ |
| self.src_path = src_path |
| self.build_directory = build_dir |
| self.host = host |
| self.additional_flags = additional_flags |
| self.additional_env = additional_env |
| |
| self.working_directory = self.build_directory / 'build' |
| self.install_directory = self.build_directory / 'install' |
| |
| self.toolchain = ndk.toolchains.ClangToolchain(self.host) |
| |
| @property |
| def flags(self) -> List[str]: |
| """Returns default cflags for the target.""" |
| # TODO: Are these the flags we want? These are what we've used |
| # historically. |
| flags = [ |
| '-Os', |
| '-fomit-frame-pointer', |
| '-s', |
| ] |
| if not self.host == Host.Darwin: |
| flags.append('-fuse-ld=lld') |
| if self.additional_flags: |
| flags.extend(self.additional_flags) |
| return flags |
| |
| def _run(self, cmd: List[str]) -> None: |
| """Runs and logs execution of a subprocess.""" |
| subproc_env = dict(os.environ) |
| if self.additional_env: |
| subproc_env.update(self.additional_env) |
| |
| pp_cmd = shlex.join(cmd) |
| if subproc_env != os.environ: |
| pp_env = pprint.pformat(self.additional_env, indent=4) |
| print('Running: {} with env:\n{}'.format(pp_cmd, pp_env)) |
| else: |
| print('Running: {}'.format(pp_cmd)) |
| |
| subprocess.check_call(cmd, env=subproc_env, cwd=self.working_directory) |
| |
| @property |
| def _cmake(self) -> Path: |
| return (ndk.paths.ANDROID_DIR / 'prebuilts' / 'cmake' / |
| (get_default_host().value + '-x86') / 'bin' / 'cmake') |
| |
| @property |
| def _ninja(self) -> Path: |
| return (ndk.paths.ANDROID_DIR / 'prebuilts' / 'ninja' / |
| (get_default_host().value + '-x86') / 'ninja') |
| |
| @property |
| def cmake_defines(self) -> Dict[str, str]: |
| """CMake defines.""" |
| flags = self.toolchain.flags + self.flags |
| cflags = ' '.join(flags) |
| cxxflags = ' '.join(flags + ['-stdlib=libc++']) |
| defines: Dict[str, str] = { |
| 'CMAKE_C_COMPILER': str(self.toolchain.cc), |
| 'CMAKE_C_COMPILER_TARGET': HOST_TRIPLE_MAP[self.host], |
| 'CMAKE_CXX_COMPILER': str(self.toolchain.cxx), |
| 'CMAKE_CXX_COMPILER_TARGET': HOST_TRIPLE_MAP[self.host], |
| 'CMAKE_AR': str(self.toolchain.ar), |
| 'CMAKE_RANLIB': str(self.toolchain.ranlib), |
| 'CMAKE_NM': str(self.toolchain.nm), |
| 'CMAKE_STRIP': str(self.toolchain.strip), |
| 'CMAKE_LINKER': str(self.toolchain.ld), |
| 'CMAKE_ASM_FLAGS': cflags, |
| 'CMAKE_C_FLAGS': cflags, |
| 'CMAKE_CXX_FLAGS': cxxflags, |
| 'CMAKE_BUILD_TYPE': 'Release', |
| 'CMAKE_INSTALL_PREFIX': str(self.install_directory), |
| 'CMAKE_MAKE_PROGRAM': str(self._ninja), |
| 'CMAKE_SYSTEM_NAME': SYSTEM_NAME_MAP[self.host], |
| 'CMAKE_SYSTEM_PROCESSOR': 'x86_64', |
| 'CMAKE_FIND_ROOT_PATH_MODE_INCLUDE': 'ONLY', |
| 'CMAKE_FIND_ROOT_PATH_MODE_LIBRARY': 'ONLY', |
| 'CMAKE_FIND_ROOT_PATH_MODE_PACKAGE': 'ONLY', |
| 'CMAKE_FIND_ROOT_PATH_MODE_PROGRAM': 'NEVER', |
| } |
| if self.host.is_windows: |
| defines['CMAKE_RC'] = str(self.toolchain.rescomp) |
| return defines |
| |
| def clean(self) -> None: |
| """Cleans output directory. |
| |
| If necessary, existing output directory will be removed. After |
| removal, the inner directories (working directory, install directory, |
| and toolchain directory) will be created. |
| """ |
| if self.build_directory.exists(): |
| shutil.rmtree(self.build_directory) |
| |
| self.working_directory.mkdir(parents=True) |
| self.install_directory.mkdir(parents=True) |
| |
| def configure(self, additional_defines: Dict[str, str]) -> None: |
| """Invokes cmake configure.""" |
| cmake_cmd = [str(self._cmake), '-GNinja'] |
| defines = self.cmake_defines |
| defines.update(additional_defines) |
| cmake_cmd.extend(f'-D{key}={val}' for key, val in defines.items()) |
| cmake_cmd.append(str(self.src_path)) |
| |
| self._run(cmake_cmd) |
| |
| def make(self) -> None: |
| """Builds the project.""" |
| self._run([str(self._ninja)]) |
| |
| def install(self) -> None: |
| """Installs the project.""" |
| self._run([str(self._ninja), 'install']) |
| |
| def build(self, |
| additional_defines: Optional[Dict[str, str]] = None) -> None: |
| """Configures and builds an cmake project. |
| |
| Args: |
| configure_args: List of arguments to be passed to configure. Does |
| not need to include --prefix, --build, or --host. Those are set |
| up automatically. |
| """ |
| self.clean() |
| self.configure( |
| {} if additional_defines is None else additional_defines) |
| self.make() |
| self.install() |