blob: 938769b439c3cb17cf98e0dab7431af67bab4e7c [file] [log] [blame]
#
# Copyright (C) 2019 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 accessing toolchains."""
import enum
from pathlib import Path
import subprocess
from typing import List
from ndk.hosts import Host, get_default_host
import ndk.paths
CLANG_VERSION = 'clang-r399163'
HOST_TRIPLE_MAP = {
Host.Darwin: 'x86_64-apple-darwin',
Host.Linux: 'x86_64-linux-gnu',
Host.Windows64: 'x86_64-w64-mingw32',
}
@enum.unique
class LinkerOption(enum.Enum):
Deprecated = 'deprecated'
Lld = 'lld'
class DarwinSdk:
"""The Darwin SDK."""
MACOSX_TARGET = '10.9'
def __init__(self) -> None:
self.mac_sdk_path = self._get_sdk_path()
self.linker_version = self._get_ld_version()
self.ar = self.sdk_tool('ar')
self.asm = self.sdk_tool('as')
self.ld = self.sdk_tool('ld')
self.nm = self.sdk_tool('nm')
self.ranlib = self.sdk_tool('ranlib')
self.strings = self.sdk_tool('strings')
self.strip = self.sdk_tool('strip')
@property
def flags(self) -> List[str]:
"""The default flags to be used with the SDK."""
return [
f'-mmacosx-version-min={self.MACOSX_TARGET}',
f'-DMACOSX_DEPLOYMENT_TARGET={self.MACOSX_TARGET}',
f'-isysroot{self.mac_sdk_path}',
f'-Wl,-syslibroot,{self.mac_sdk_path}',
# https://stackoverflow.com/a/60958449/632035
# Our Clang is not built to handle old linkers by default, so if we
# do not configure this explicitly it may attempt to use flags that
# are not supported by the version of the Darwin linker installed on
# the build machine.
f'-mlinker-version={self.linker_version}',
]
def sdk_tool(self, name: str) -> Path:
"""Returns the path to the given SDK tool."""
proc_result = subprocess.run(['xcrun', '--find', name],
stdout=subprocess.PIPE,
check=True, encoding='utf-8')
return Path(proc_result.stdout.strip())
def _get_sdk_path(self) -> Path:
"""Gets the path to the Mac SDK."""
proc_result = subprocess.run(['xcrun', '--show-sdk-path'],
stdout=subprocess.PIPE,
check=True, encoding='utf-8')
return Path(proc_result.stdout.strip())
def _get_ld_version(self) -> str:
"""Gets the version of the system linker."""
proc_result = subprocess.run(['ld', '-v'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True, encoding='utf-8')
output = proc_result.stderr.strip().splitlines()[0]
# Example first line: @(#)PROGRAM:ld PROJECT:ld64-409.12
return output.rsplit('-', 1)[-1]
class Toolchain:
"""A compiler toolchain.
Describes the directories, executables, and default flags needed to use a
toolchain.
"""
def __init__(self, target: Host, host: Host = get_default_host()) -> None:
if host.is_windows:
raise NotImplementedError
self.host = host
self.target = target
if self.target == Host.Darwin:
self.darwin_sdk = DarwinSdk()
@property
def ar(self) -> Path:
"""The path to the archiver."""
raise NotImplementedError
@property
def asm(self) -> Path:
"""The path to the assembler."""
raise NotImplementedError
@property
def bin_paths(self) -> List[Path]:
"""The path to the toolchain binary directories for use with PATH."""
raise NotImplementedError
@property
def cc(self) -> Path:
"""The path to the C compiler."""
raise NotImplementedError
@property
def cxx(self) -> Path:
"""The path to the C++ compiler."""
raise NotImplementedError
@property
def flags(self) -> List[str]:
"""The default flags to be used with the compiler."""
raise NotImplementedError
@property
def ld(self) -> Path:
"""The path to the linker."""
raise NotImplementedError
@property
def nm(self) -> Path:
"""The path to nm."""
raise NotImplementedError
@property
def path(self) -> Path:
"""The path to the top level toolchain directory."""
raise NotImplementedError
@property
def ranlib(self) -> Path:
"""The path to ranlib."""
raise NotImplementedError
@property
def rescomp(self) -> Path:
"""The path to the resource compiler."""
raise NotImplementedError
@property
def strip(self) -> Path:
"""The path to strip."""
raise NotImplementedError
@property
def strings(self) -> Path:
"""The path to strings."""
raise NotImplementedError
class GccToolchain(Toolchain):
"""A GCC compiler toolchain."""
def gcc_tool(self, tool_name: str) -> Path:
"""Returns the path to the GCC tool targeting the given host."""
return self.path / 'bin' / f'{self.triple}-{tool_name}'
@property
def ar(self) -> Path:
"""The path to the archiver."""
if self.target == Host.Darwin:
return self.darwin_sdk.ar
return self.gcc_tool('ar')
@property
def asm(self) -> Path:
"""The path to the assembler."""
if self.target == Host.Darwin:
return self.darwin_sdk.asm
return self.gcc_tool('as')
@property
def bin_paths(self) -> List[Path]:
"""The path to the toolchain binary directories for use with PATH."""
return [self.path / 'bin']
@property
def cc(self) -> Path:
"""The path to the C compiler."""
return self.gcc_tool('gcc')
@property
def cxx(self) -> Path:
"""The path to the C++ compiler."""
return self.gcc_tool('g++')
@property
def flags(self) -> List[str]:
"""The default flags to be used with the compiler."""
if self.target == Host.Darwin:
return self.darwin_sdk.flags
return []
@property
def ld(self) -> Path:
"""The path to the linker."""
if self.target == Host.Darwin:
return self.darwin_sdk.ld
return self.gcc_tool('ld')
@property
def lib_dirs(self) -> List[Path]:
"""Returns the paths to the GCC library directories for the given host.
The GCC library directory contains libgcc and other compiler runtime
libraries. These may be split across multiple directories.
"""
lib_dirs = [self.path / {
Host.Darwin: 'lib/gcc/i686-apple-darwin11/4.2.1',
Host.Linux: 'lib/gcc/x86_64-linux/4.8.3',
Host.Windows64: 'lib/gcc/x86_64-w64-mingw32/4.8.3',
}[self.target]]
if self.target != Host.Darwin:
lib_dirs.append(self.path / self.triple / 'lib64')
return lib_dirs
@property
def nm(self) -> Path:
"""The path to nm."""
if self.target == Host.Darwin:
return self.darwin_sdk.nm
return self.gcc_tool('nm')
@property
def path(self) -> Path:
"""Returns the path to the top level toolchain directory."""
if self.target == Host.Darwin:
return (ndk.paths.ANDROID_DIR /
'prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1')
elif self.target == Host.Linux:
return (ndk.paths.ANDROID_DIR /
'prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8')
else:
return (ndk.paths.ANDROID_DIR /
'prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8')
@property
def ranlib(self) -> Path:
"""The path to ranlib."""
if self.target == Host.Darwin:
return self.darwin_sdk.ranlib
return self.gcc_tool('ranlib')
@property
def rescomp(self) -> Path:
"""The path to the resource compiler."""
if not self.target.is_windows:
raise NotImplementedError
return self.gcc_tool('windres')
@property
def strip(self) -> Path:
"""The path to strip."""
if self.target == Host.Darwin:
return self.darwin_sdk.strip
return self.gcc_tool('strip')
@property
def strings(self) -> Path:
"""The path to strings."""
if self.target == Host.Darwin:
return self.darwin_sdk.strings
return self.gcc_tool('strings')
@property
def sysroot(self) -> Path:
"""The path to the GCC sysroot."""
if self.target == Host.Linux:
return self.path / 'sysroot'
return self.path / self.triple
@property
def triple(self) -> str:
"""Returns the GCC triple for the host toolchain."""
return {
Host.Darwin: 'x86_64-apple-darwin11',
Host.Linux: 'x86_64-linux',
Host.Windows64: 'x86_64-w64-mingw32',
}[self.target]
class ClangToolchain(Toolchain):
"""A Clang compiler toolchain."""
def __init__(self, target: Host, host: Host = get_default_host()) -> None:
super().__init__(target, host=host)
self.gcc_toolchain = GccToolchain(target, host=host)
@staticmethod
def path_for_host(host: Host) -> Path:
"""Returns the path to the Clang directory for the given host."""
host_tag = {
Host.Darwin: 'darwin-x86',
Host.Linux: 'linux-x86',
Host.Windows64: 'windows-x86',
}[host]
return (ndk.paths.ANDROID_DIR / 'prebuilts/clang/host' / host_tag /
CLANG_VERSION)
@property
def path(self) -> Path:
"""Returns the path to the top level toolchain directory."""
return self.path_for_host(self.host)
def clang_tool(self, tool_name: str) -> Path:
"""Returns the path to the Clang tool for the build host."""
return self.path / 'bin' / tool_name
@property
def ar(self) -> Path:
"""The path to the archiver."""
return self.gcc_toolchain.ar
@property
def asm(self) -> Path:
"""The path to the assembler."""
return self.gcc_toolchain.asm
@property
def bin_paths(self) -> List[Path]:
"""The path to the toolchain binary directories for use with PATH."""
return self.gcc_toolchain.bin_paths + [self.path / 'bin']
@property
def cc(self) -> Path:
return self.clang_tool('clang')
@property
def cxx(self) -> Path:
return self.clang_tool('clang++')
@property
def lib_dirs(self) -> List[Path]:
lib_dirs = self.gcc_toolchain.lib_dirs
# libc++ library path. Static only for Windows.
if self.target.is_windows:
lib_dirs.append(self.path_for_host(self.target) / 'lib64')
else:
lib_dirs.append(self.path / 'lib64')
return lib_dirs
@property
def flags(self) -> List[str]:
host_triple = HOST_TRIPLE_MAP[self.target]
toolchain_bin = (
self.gcc_toolchain.path / self.gcc_toolchain.triple / 'bin')
flags = [
f'--target={host_triple}',
f'-B{toolchain_bin}',
]
if self.target.is_windows:
flags.append('-I' + str(self.path_for_host(self.target) / 'include/c++/v1'))
if self.target == Host.Darwin:
flags.extend(self.darwin_sdk.flags)
else:
flags.append(f'--sysroot={self.gcc_toolchain.sysroot}')
for lib_dir in self.lib_dirs:
# Both -L and -B because Clang only searches for CRT
# objects in -B directories.
flags.extend([
f'-L{lib_dir}',
f'-B{lib_dir}',
])
return flags
@property
def ld(self) -> Path:
"""The path to the linker."""
return self.gcc_toolchain.ld
@property
def nm(self) -> Path:
"""The path to nm."""
return self.gcc_toolchain.nm
@property
def ranlib(self) -> Path:
"""The path to ranlib."""
return self.gcc_toolchain.ranlib
@property
def rescomp(self) -> Path:
"""The path to the resource compiler."""
return self.gcc_toolchain.rescomp
@property
def strip(self) -> Path:
"""The path to strip."""
return self.gcc_toolchain.strip
@property
def strings(self) -> Path:
"""The path to strings."""
return self.gcc_toolchain.strings