The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1022 lines
42 KiB
1022 lines
42 KiB
# Copyright 2012-2022 The Meson development team |
|
|
|
# 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. |
|
from __future__ import annotations |
|
|
|
import os.path |
|
import re |
|
import subprocess |
|
import typing as T |
|
|
|
from .. import mesonlib |
|
from .. import mlog |
|
from ..arglist import CompilerArgs |
|
from ..linkers import RSPFileSyntax |
|
from ..mesonlib import ( |
|
EnvironmentException, version_compare, OptionKey, is_windows |
|
) |
|
|
|
from . import compilers |
|
from .compilers import ( |
|
d_dmd_buildtype_args, |
|
d_gdc_buildtype_args, |
|
d_ldc_buildtype_args, |
|
clike_debug_args, |
|
Compiler, |
|
CompileCheckMode, |
|
) |
|
from .mixins.gnu import GnuCompiler |
|
from .mixins.gnu import gnu_common_warning_args |
|
|
|
if T.TYPE_CHECKING: |
|
from ..dependencies import Dependency |
|
from ..programs import ExternalProgram |
|
from ..envconfig import MachineInfo |
|
from ..environment import Environment |
|
from ..linkers import DynamicLinker |
|
from ..mesonlib import MachineChoice |
|
|
|
CompilerMixinBase = Compiler |
|
else: |
|
CompilerMixinBase = object |
|
|
|
d_feature_args = {'gcc': {'unittest': '-funittest', |
|
'debug': '-fdebug', |
|
'version': '-fversion', |
|
'import_dir': '-J' |
|
}, |
|
'llvm': {'unittest': '-unittest', |
|
'debug': '-d-debug', |
|
'version': '-d-version', |
|
'import_dir': '-J' |
|
}, |
|
'dmd': {'unittest': '-unittest', |
|
'debug': '-debug', |
|
'version': '-version', |
|
'import_dir': '-J' |
|
} |
|
} # type: T.Dict[str, T.Dict[str, str]] |
|
|
|
ldc_optimization_args = {'plain': [], |
|
'0': [], |
|
'g': [], |
|
'1': ['-O1'], |
|
'2': ['-O2'], |
|
'3': ['-O3'], |
|
's': ['-Oz'], |
|
} # type: T.Dict[str, T.List[str]] |
|
|
|
dmd_optimization_args = {'plain': [], |
|
'0': [], |
|
'g': [], |
|
'1': ['-O'], |
|
'2': ['-O'], |
|
'3': ['-O'], |
|
's': ['-O'], |
|
} # type: T.Dict[str, T.List[str]] |
|
|
|
|
|
class DmdLikeCompilerMixin(CompilerMixinBase): |
|
|
|
"""Mixin class for DMD and LDC. |
|
|
|
LDC has a number of DMD like arguments, and this class allows for code |
|
sharing between them as makes sense. |
|
""" |
|
|
|
def __init__(self, dmd_frontend_version: T.Optional[str]): |
|
if dmd_frontend_version is None: |
|
self._dmd_has_depfile = False |
|
else: |
|
# -makedeps switch introduced in 2.095 frontend |
|
self._dmd_has_depfile = version_compare(dmd_frontend_version, ">=2.095.0") |
|
|
|
if T.TYPE_CHECKING: |
|
mscrt_args = {} # type: T.Dict[str, T.List[str]] |
|
|
|
def _get_target_arch_args(self) -> T.List[str]: ... |
|
|
|
LINKER_PREFIX = '-L=' |
|
|
|
def get_output_args(self, outputname: str) -> T.List[str]: |
|
return ['-of=' + outputname] |
|
|
|
def get_linker_output_args(self, outputname: str) -> T.List[str]: |
|
return ['-of=' + outputname] |
|
|
|
def get_include_args(self, path: str, is_system: bool) -> T.List[str]: |
|
if path == "": |
|
path = "." |
|
return ['-I=' + path] |
|
|
|
def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], |
|
build_dir: str) -> T.List[str]: |
|
for idx, i in enumerate(parameter_list): |
|
if i[:3] == '-I=': |
|
parameter_list[idx] = i[:3] + os.path.normpath(os.path.join(build_dir, i[3:])) |
|
if i[:4] == '-L-L': |
|
parameter_list[idx] = i[:4] + os.path.normpath(os.path.join(build_dir, i[4:])) |
|
if i[:5] == '-L=-L': |
|
parameter_list[idx] = i[:5] + os.path.normpath(os.path.join(build_dir, i[5:])) |
|
if i[:6] == '-Wl,-L': |
|
parameter_list[idx] = i[:6] + os.path.normpath(os.path.join(build_dir, i[6:])) |
|
|
|
return parameter_list |
|
|
|
def get_warn_args(self, level: str) -> T.List[str]: |
|
return ['-wi'] |
|
|
|
def get_werror_args(self) -> T.List[str]: |
|
return ['-w'] |
|
|
|
def get_coverage_args(self) -> T.List[str]: |
|
return ['-cov'] |
|
|
|
def get_coverage_link_args(self) -> T.List[str]: |
|
return [] |
|
|
|
def get_preprocess_only_args(self) -> T.List[str]: |
|
return ['-E'] |
|
|
|
def get_compile_only_args(self) -> T.List[str]: |
|
return ['-c'] |
|
|
|
def get_depfile_suffix(self) -> str: |
|
return 'deps' |
|
|
|
def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: |
|
if self._dmd_has_depfile: |
|
return [f'-makedeps={outfile}'] |
|
return [] |
|
|
|
def get_pic_args(self) -> T.List[str]: |
|
if self.info.is_windows(): |
|
return [] |
|
return ['-fPIC'] |
|
|
|
def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]: |
|
# TODO: using a TypeDict here would improve this |
|
res = [] |
|
# get_feature_args can be called multiple times for the same target when there is generated source |
|
# so we have to copy the kwargs (target.d_features) dict before popping from it |
|
kwargs = kwargs.copy() |
|
if 'unittest' in kwargs: |
|
unittest = kwargs.pop('unittest') |
|
unittest_arg = d_feature_args[self.id]['unittest'] |
|
if not unittest_arg: |
|
raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string()) |
|
if unittest: |
|
res.append(unittest_arg) |
|
|
|
if 'debug' in kwargs: |
|
debug_level = -1 |
|
debugs = kwargs.pop('debug') |
|
if not isinstance(debugs, list): |
|
debugs = [debugs] |
|
|
|
debug_arg = d_feature_args[self.id]['debug'] |
|
if not debug_arg: |
|
raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string()) |
|
|
|
# Parse all debug identifiers and the largest debug level identifier |
|
for d in debugs: |
|
if isinstance(d, int): |
|
if d > debug_level: |
|
debug_level = d |
|
elif isinstance(d, str) and d.isdigit(): |
|
if int(d) > debug_level: |
|
debug_level = int(d) |
|
else: |
|
res.append(f'{debug_arg}={d}') |
|
|
|
if debug_level >= 0: |
|
res.append(f'{debug_arg}={debug_level}') |
|
|
|
if 'versions' in kwargs: |
|
version_level = -1 |
|
versions = kwargs.pop('versions') |
|
if not isinstance(versions, list): |
|
versions = [versions] |
|
|
|
version_arg = d_feature_args[self.id]['version'] |
|
if not version_arg: |
|
raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string()) |
|
|
|
# Parse all version identifiers and the largest version level identifier |
|
for v in versions: |
|
if isinstance(v, int): |
|
if v > version_level: |
|
version_level = v |
|
elif isinstance(v, str) and v.isdigit(): |
|
if int(v) > version_level: |
|
version_level = int(v) |
|
else: |
|
res.append(f'{version_arg}={v}') |
|
|
|
if version_level >= 0: |
|
res.append(f'{version_arg}={version_level}') |
|
|
|
if 'import_dirs' in kwargs: |
|
import_dirs = kwargs.pop('import_dirs') |
|
if not isinstance(import_dirs, list): |
|
import_dirs = [import_dirs] |
|
|
|
import_dir_arg = d_feature_args[self.id]['import_dir'] |
|
if not import_dir_arg: |
|
raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string()) |
|
for idir_obj in import_dirs: |
|
basedir = idir_obj.get_curdir() |
|
for idir in idir_obj.get_incdirs(): |
|
bldtreedir = os.path.join(basedir, idir) |
|
# Avoid superfluous '/.' at the end of paths when d is '.' |
|
if idir not in ('', '.'): |
|
expdir = bldtreedir |
|
else: |
|
expdir = basedir |
|
srctreedir = os.path.join(build_to_src, expdir) |
|
res.append(f'{import_dir_arg}{srctreedir}') |
|
res.append(f'{import_dir_arg}{bldtreedir}') |
|
|
|
if kwargs: |
|
raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys())) |
|
|
|
return res |
|
|
|
def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]: |
|
if buildtype != 'plain': |
|
return self._get_target_arch_args() |
|
return [] |
|
|
|
def gen_import_library_args(self, implibname: str) -> T.List[str]: |
|
return self.linker.import_library_args(implibname) |
|
|
|
def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, |
|
rpath_paths: T.Tuple[str, ...], build_rpath: str, |
|
install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: |
|
if self.info.is_windows(): |
|
return ([], set()) |
|
|
|
# GNU ld, solaris ld, and lld acting like GNU ld |
|
if self.linker.id.startswith('ld'): |
|
# The way that dmd and ldc pass rpath to gcc is different than we would |
|
# do directly, each argument -rpath and the value to rpath, need to be |
|
# split into two separate arguments both prefaced with the -L=. |
|
args = [] |
|
(rpath_args, rpath_dirs_to_remove) = super().build_rpath_args( |
|
env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) |
|
for r in rpath_args: |
|
if ',' in r: |
|
a, b = r.split(',', maxsplit=1) |
|
args.append(a) |
|
args.append(self.LINKER_PREFIX + b) |
|
else: |
|
args.append(r) |
|
return (args, rpath_dirs_to_remove) |
|
|
|
return super().build_rpath_args( |
|
env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) |
|
|
|
@classmethod |
|
def _translate_args_to_nongnu(cls, args: T.List[str], info: MachineInfo, link_id: str) -> T.List[str]: |
|
# Translate common arguments to flags the LDC/DMD compilers |
|
# can understand. |
|
# The flags might have been added by pkg-config files, |
|
# and are therefore out of the user's control. |
|
dcargs = [] |
|
# whether we hit a linker argument that expect another arg |
|
# see the comment in the "-L" section |
|
link_expect_arg = False |
|
link_flags_with_arg = [ |
|
'-rpath', '-rpath-link', '-soname', '-compatibility_version', '-current_version', |
|
] |
|
for arg in args: |
|
# Translate OS specific arguments first. |
|
osargs = [] # type: T.List[str] |
|
if info.is_windows(): |
|
osargs = cls.translate_arg_to_windows(arg) |
|
elif info.is_darwin(): |
|
osargs = cls._translate_arg_to_osx(arg) |
|
if osargs: |
|
dcargs.extend(osargs) |
|
continue |
|
|
|
# Translate common D arguments here. |
|
if arg == '-pthread': |
|
continue |
|
if arg.startswith('-fstack-protector'): |
|
continue |
|
if arg.startswith('-D') and not (arg == '-D' or arg.startswith(('-Dd', '-Df'))): |
|
# ignore all '-D*' flags (like '-D_THREAD_SAFE') |
|
# unless they are related to documentation |
|
continue |
|
if arg.startswith('-Wl,'): |
|
# Translate linker arguments here. |
|
linkargs = arg[arg.index(',') + 1:].split(',') |
|
for la in linkargs: |
|
dcargs.append('-L=' + la.strip()) |
|
continue |
|
elif arg.startswith(('-link-defaultlib', '-linker', '-link-internally', '-linkonce-templates', '-lib')): |
|
# these are special arguments to the LDC linker call, |
|
# arguments like "-link-defaultlib-shared" do *not* |
|
# denote a library to be linked, but change the default |
|
# Phobos/DRuntime linking behavior, while "-linker" sets the |
|
# default linker. |
|
dcargs.append(arg) |
|
continue |
|
elif arg.startswith('-l'): |
|
# translate library link flag |
|
dcargs.append('-L=' + arg) |
|
continue |
|
elif arg.startswith('-isystem'): |
|
# translate -isystem system include path |
|
# this flag might sometimes be added by C library Cflags via |
|
# pkg-config. |
|
# NOTE: -isystem and -I are not 100% equivalent, so this is just |
|
# a workaround for the most common cases. |
|
if arg.startswith('-isystem='): |
|
dcargs.append('-I=' + arg[9:]) |
|
else: |
|
dcargs.append('-I' + arg[8:]) |
|
continue |
|
elif arg.startswith('-idirafter'): |
|
# same as -isystem, but appends the path instead |
|
if arg.startswith('-idirafter='): |
|
dcargs.append('-I=' + arg[11:]) |
|
else: |
|
dcargs.append('-I' + arg[10:]) |
|
continue |
|
elif arg.startswith('-L'): |
|
# The D linker expect library search paths in the form of -L=-L/path (the '=' is optional). |
|
# |
|
# This function receives a mix of arguments already prepended |
|
# with -L for the D linker driver and other linker arguments. |
|
# The arguments starting with -L can be: |
|
# - library search path (with or without a second -L) |
|
# - it can come from pkg-config (a single -L) |
|
# - or from the user passing linker flags (-L-L would be expected) |
|
# - arguments like "-L=-rpath" that expect a second argument (also prepended with -L) |
|
# - arguments like "-L=@rpath/xxx" without a second argument (on Apple platform) |
|
# - arguments like "-L=/SUBSYSTEM:CONSOLE (for Windows linker) |
|
# |
|
# The logic that follows tries to detect all these cases (some may be missing) |
|
# in order to prepend a -L only for the library search paths with a single -L |
|
|
|
if arg.startswith('-L='): |
|
suffix = arg[3:] |
|
else: |
|
suffix = arg[2:] |
|
|
|
if link_expect_arg: |
|
# flags like rpath and soname expect a path or filename respectively, |
|
# we must not alter it (i.e. prefixing with -L for a lib search path) |
|
dcargs.append(arg) |
|
link_expect_arg = False |
|
continue |
|
|
|
if suffix in link_flags_with_arg: |
|
link_expect_arg = True |
|
|
|
if suffix.startswith('-') or suffix.startswith('@'): |
|
# this is not search path |
|
dcargs.append(arg) |
|
continue |
|
|
|
# linker flag such as -L=/DEBUG must pass through |
|
if info.is_windows() and link_id == 'link' and suffix.startswith('/'): |
|
dcargs.append(arg) |
|
continue |
|
|
|
# Make sure static library files are passed properly to the linker. |
|
if arg.endswith('.a') or arg.endswith('.lib'): |
|
if len(suffix) > 0 and not suffix.startswith('-'): |
|
dcargs.append('-L=' + suffix) |
|
continue |
|
|
|
dcargs.append('-L=' + arg) |
|
continue |
|
elif not arg.startswith('-') and arg.endswith(('.a', '.lib')): |
|
# ensure static libraries are passed through to the linker |
|
dcargs.append('-L=' + arg) |
|
continue |
|
else: |
|
dcargs.append(arg) |
|
|
|
return dcargs |
|
|
|
@classmethod |
|
def translate_arg_to_windows(cls, arg: str) -> T.List[str]: |
|
args = [] |
|
if arg.startswith('-Wl,'): |
|
# Translate linker arguments here. |
|
linkargs = arg[arg.index(',') + 1:].split(',') |
|
for la in linkargs: |
|
if la.startswith('--out-implib='): |
|
# Import library name |
|
args.append('-L=/IMPLIB:' + la[13:].strip()) |
|
elif arg.startswith('-mscrtlib='): |
|
args.append(arg) |
|
mscrtlib = arg[10:].lower() |
|
if cls is LLVMDCompiler: |
|
# Default crt libraries for LDC2 must be excluded for other |
|
# selected crt options. |
|
if mscrtlib != 'libcmt': |
|
args.append('-L=/NODEFAULTLIB:libcmt') |
|
args.append('-L=/NODEFAULTLIB:libvcruntime') |
|
|
|
# Fixes missing definitions for printf-functions in VS2017 |
|
if mscrtlib.startswith('msvcrt'): |
|
args.append('-L=/DEFAULTLIB:legacy_stdio_definitions.lib') |
|
|
|
return args |
|
|
|
@classmethod |
|
def _translate_arg_to_osx(cls, arg: str) -> T.List[str]: |
|
args = [] |
|
if arg.startswith('-install_name'): |
|
args.append('-L=' + arg) |
|
return args |
|
|
|
@classmethod |
|
def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo, link_id: str = '') -> T.List[str]: |
|
return cls._translate_args_to_nongnu(args, info, link_id) |
|
|
|
def get_debug_args(self, is_debug: bool) -> T.List[str]: |
|
ddebug_args = [] |
|
if is_debug: |
|
ddebug_args = [d_feature_args[self.id]['debug']] |
|
|
|
return clike_debug_args[is_debug] + ddebug_args |
|
|
|
def _get_crt_args(self, crt_val: str, buildtype: str) -> T.List[str]: |
|
if not self.info.is_windows(): |
|
return [] |
|
|
|
if crt_val in self.mscrt_args: |
|
return self.mscrt_args[crt_val] |
|
assert crt_val in {'from_buildtype', 'static_from_buildtype'} |
|
|
|
dbg = 'mdd' |
|
rel = 'md' |
|
if crt_val == 'static_from_buildtype': |
|
dbg = 'mtd' |
|
rel = 'mt' |
|
|
|
# Match what build type flags used to do. |
|
if buildtype == 'plain': |
|
return [] |
|
elif buildtype == 'debug': |
|
return self.mscrt_args[dbg] |
|
elif buildtype == 'debugoptimized': |
|
return self.mscrt_args[rel] |
|
elif buildtype == 'release': |
|
return self.mscrt_args[rel] |
|
elif buildtype == 'minsize': |
|
return self.mscrt_args[rel] |
|
else: |
|
assert buildtype == 'custom' |
|
raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".') |
|
|
|
def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, |
|
suffix: str, soversion: str, |
|
darwin_versions: T.Tuple[str, str]) -> T.List[str]: |
|
sargs = super().get_soname_args(env, prefix, shlib_name, suffix, |
|
soversion, darwin_versions) |
|
|
|
# LDC and DMD actually do use a linker, but they proxy all of that with |
|
# their own arguments |
|
if self.linker.id.startswith('ld.'): |
|
soargs = [] |
|
for arg in sargs: |
|
a, b = arg.split(',', maxsplit=1) |
|
soargs.append(a) |
|
soargs.append(self.LINKER_PREFIX + b) |
|
return soargs |
|
elif self.linker.id.startswith('ld64'): |
|
soargs = [] |
|
for arg in sargs: |
|
if not arg.startswith(self.LINKER_PREFIX): |
|
soargs.append(self.LINKER_PREFIX + arg) |
|
else: |
|
soargs.append(arg) |
|
return soargs |
|
else: |
|
return sargs |
|
|
|
def get_allow_undefined_link_args(self) -> T.List[str]: |
|
args = self.linker.get_allow_undefined_args() |
|
if self.info.is_darwin(): |
|
# On macOS we're passing these options to the C compiler, but |
|
# they're linker options and need -Wl, so clang/gcc knows what to |
|
# do with them. I'm assuming, but don't know for certain, that |
|
# ldc/dmd do some kind of mapping internally for arguments they |
|
# understand, but pass arguments they don't understand directly. |
|
args = [a.replace('-L=', '-Xcc=-Wl,') for a in args] |
|
return args |
|
|
|
|
|
class DCompilerArgs(CompilerArgs): |
|
prepend_prefixes = ('-I', '-L') |
|
dedup2_prefixes = ('-I', ) |
|
|
|
|
|
class DCompiler(Compiler): |
|
mscrt_args = { |
|
'none': ['-mscrtlib='], |
|
'md': ['-mscrtlib=msvcrt'], |
|
'mdd': ['-mscrtlib=msvcrtd'], |
|
'mt': ['-mscrtlib=libcmt'], |
|
'mtd': ['-mscrtlib=libcmtd'], |
|
} |
|
|
|
language = 'd' |
|
|
|
def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, |
|
info: 'MachineInfo', arch: str, *, |
|
exe_wrapper: T.Optional['ExternalProgram'] = None, |
|
linker: T.Optional['DynamicLinker'] = None, |
|
full_version: T.Optional[str] = None, |
|
is_cross: bool = False): |
|
super().__init__([], exelist, version, for_machine, info, linker=linker, |
|
full_version=full_version, is_cross=is_cross) |
|
self.arch = arch |
|
self.exe_wrapper = exe_wrapper |
|
|
|
def sanity_check(self, work_dir: str, environment: 'Environment') -> None: |
|
source_name = os.path.join(work_dir, 'sanity.d') |
|
output_name = os.path.join(work_dir, 'dtest') |
|
with open(source_name, 'w', encoding='utf-8') as ofile: |
|
ofile.write('''void main() { }''') |
|
pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name], cwd=work_dir) |
|
pc.wait() |
|
if pc.returncode != 0: |
|
raise EnvironmentException('D compiler %s can not compile programs.' % self.name_string()) |
|
if self.is_cross: |
|
if self.exe_wrapper is None: |
|
# Can't check if the binaries run so we have to assume they do |
|
return |
|
cmdlist = self.exe_wrapper.get_command() + [output_name] |
|
else: |
|
cmdlist = [output_name] |
|
if subprocess.call(cmdlist) != 0: |
|
raise EnvironmentException('Executables created by D compiler %s are not runnable.' % self.name_string()) |
|
|
|
def needs_static_linker(self) -> bool: |
|
return True |
|
|
|
def get_depfile_suffix(self) -> str: |
|
return 'deps' |
|
|
|
def get_pic_args(self) -> T.List[str]: |
|
if self.info.is_windows(): |
|
return [] |
|
return ['-fPIC'] |
|
|
|
def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]: |
|
# TODO: using a TypeDict here would improve this |
|
res = [] |
|
# get_feature_args can be called multiple times for the same target when there is generated source |
|
# so we have to copy the kwargs (target.d_features) dict before popping from it |
|
kwargs = kwargs.copy() |
|
if 'unittest' in kwargs: |
|
unittest = kwargs.pop('unittest') |
|
unittest_arg = d_feature_args[self.id]['unittest'] |
|
if not unittest_arg: |
|
raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string()) |
|
if unittest: |
|
res.append(unittest_arg) |
|
|
|
if 'debug' in kwargs: |
|
debug_level = -1 |
|
debugs = kwargs.pop('debug') |
|
if not isinstance(debugs, list): |
|
debugs = [debugs] |
|
|
|
debug_arg = d_feature_args[self.id]['debug'] |
|
if not debug_arg: |
|
raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string()) |
|
|
|
# Parse all debug identifiers and the largest debug level identifier |
|
for d in debugs: |
|
if isinstance(d, int): |
|
if d > debug_level: |
|
debug_level = d |
|
elif isinstance(d, str) and d.isdigit(): |
|
if int(d) > debug_level: |
|
debug_level = int(d) |
|
else: |
|
res.append(f'{debug_arg}={d}') |
|
|
|
if debug_level >= 0: |
|
res.append(f'{debug_arg}={debug_level}') |
|
|
|
if 'versions' in kwargs: |
|
version_level = -1 |
|
versions = kwargs.pop('versions') |
|
if not isinstance(versions, list): |
|
versions = [versions] |
|
|
|
version_arg = d_feature_args[self.id]['version'] |
|
if not version_arg: |
|
raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string()) |
|
|
|
# Parse all version identifiers and the largest version level identifier |
|
for v in versions: |
|
if isinstance(v, int): |
|
if v > version_level: |
|
version_level = v |
|
elif isinstance(v, str) and v.isdigit(): |
|
if int(v) > version_level: |
|
version_level = int(v) |
|
else: |
|
res.append(f'{version_arg}={v}') |
|
|
|
if version_level >= 0: |
|
res.append(f'{version_arg}={version_level}') |
|
|
|
if 'import_dirs' in kwargs: |
|
import_dirs = kwargs.pop('import_dirs') |
|
if not isinstance(import_dirs, list): |
|
import_dirs = [import_dirs] |
|
|
|
import_dir_arg = d_feature_args[self.id]['import_dir'] |
|
if not import_dir_arg: |
|
raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string()) |
|
for idir_obj in import_dirs: |
|
basedir = idir_obj.get_curdir() |
|
for idir in idir_obj.get_incdirs(): |
|
bldtreedir = os.path.join(basedir, idir) |
|
# Avoid superfluous '/.' at the end of paths when d is '.' |
|
if idir not in ('', '.'): |
|
expdir = bldtreedir |
|
else: |
|
expdir = basedir |
|
srctreedir = os.path.join(build_to_src, expdir) |
|
res.append(f'{import_dir_arg}{srctreedir}') |
|
res.append(f'{import_dir_arg}{bldtreedir}') |
|
|
|
if kwargs: |
|
raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys())) |
|
|
|
return res |
|
|
|
def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]: |
|
if buildtype != 'plain': |
|
return self._get_target_arch_args() |
|
return [] |
|
|
|
def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> DCompilerArgs: |
|
return DCompilerArgs(self, args) |
|
|
|
def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: |
|
return self.compiles('int i;\n', env, extra_args=args) |
|
|
|
def _get_target_arch_args(self) -> T.List[str]: |
|
# LDC2 on Windows targets to current OS architecture, but |
|
# it should follow the target specified by the MSVC toolchain. |
|
if self.info.is_windows(): |
|
if self.arch == 'x86_64': |
|
return ['-m64'] |
|
return ['-m32'] |
|
return [] |
|
|
|
def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: |
|
return [] |
|
|
|
def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: |
|
return [] |
|
|
|
def _get_compile_extra_args(self, extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None) -> T.List[str]: |
|
args = self._get_target_arch_args() |
|
if extra_args: |
|
if callable(extra_args): |
|
extra_args = extra_args(CompileCheckMode.COMPILE) |
|
if isinstance(extra_args, list): |
|
args.extend(extra_args) |
|
elif isinstance(extra_args, str): |
|
args.append(extra_args) |
|
return args |
|
|
|
def run(self, code: 'mesonlib.FileOrString', env: 'Environment', *, |
|
extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None, |
|
dependencies: T.Optional[T.List['Dependency']] = None) -> compilers.RunResult: |
|
need_exe_wrapper = env.need_exe_wrapper(self.for_machine) |
|
if need_exe_wrapper and self.exe_wrapper is None: |
|
raise compilers.CrossNoRunException('Can not run test applications in this cross environment.') |
|
extra_args = self._get_compile_extra_args(extra_args) |
|
with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p: |
|
if p.returncode != 0: |
|
mlog.debug(f'Could not compile test file {p.input_name}: {p.returncode}\n') |
|
return compilers.RunResult(False) |
|
if need_exe_wrapper: |
|
cmdlist = self.exe_wrapper.get_command() + [p.output_name] |
|
else: |
|
cmdlist = [p.output_name] |
|
try: |
|
pe, so, se = mesonlib.Popen_safe(cmdlist) |
|
except Exception as e: |
|
mlog.debug(f'Could not run: {cmdlist} (error: {e})\n') |
|
return compilers.RunResult(False) |
|
|
|
mlog.debug('Program stdout:\n') |
|
mlog.debug(so) |
|
mlog.debug('Program stderr:\n') |
|
mlog.debug(se) |
|
return compilers.RunResult(True, pe.returncode, so, se) |
|
|
|
def sizeof(self, typename: str, prefix: str, env: 'Environment', *, |
|
extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, |
|
dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: |
|
if extra_args is None: |
|
extra_args = [] |
|
t = f''' |
|
import std.stdio : writeln; |
|
{prefix} |
|
void main() {{ |
|
writeln(({typename}).sizeof); |
|
}} |
|
''' |
|
res = self.cached_run(t, env, extra_args=extra_args, |
|
dependencies=dependencies) |
|
if not res.compiled: |
|
return -1, False |
|
if res.returncode != 0: |
|
raise mesonlib.EnvironmentException('Could not run sizeof test binary.') |
|
return int(res.stdout), res.cached |
|
|
|
def alignment(self, typename: str, prefix: str, env: 'Environment', *, |
|
extra_args: T.Optional[T.List[str]] = None, |
|
dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: |
|
if extra_args is None: |
|
extra_args = [] |
|
t = f''' |
|
import std.stdio : writeln; |
|
{prefix} |
|
void main() {{ |
|
writeln(({typename}).alignof); |
|
}} |
|
''' |
|
res = self.run(t, env, extra_args=extra_args, |
|
dependencies=dependencies) |
|
if not res.compiled: |
|
raise mesonlib.EnvironmentException('Could not compile alignment test.') |
|
if res.returncode != 0: |
|
raise mesonlib.EnvironmentException('Could not run alignment test binary.') |
|
align = int(res.stdout) |
|
if align == 0: |
|
raise mesonlib.EnvironmentException(f'Could not determine alignment of {typename}. Sorry. You might want to file a bug.') |
|
return align, res.cached |
|
|
|
def has_header(self, hname: str, prefix: str, env: 'Environment', *, |
|
extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, |
|
dependencies: T.Optional[T.List['Dependency']] = None, |
|
disable_cache: bool = False) -> T.Tuple[bool, bool]: |
|
|
|
extra_args = self._get_compile_extra_args(extra_args) |
|
code = f'''{prefix} |
|
import {hname}; |
|
''' |
|
return self.compiles(code, env, extra_args=extra_args, |
|
dependencies=dependencies, mode='compile', disable_cache=disable_cache) |
|
|
|
class GnuDCompiler(GnuCompiler, DCompiler): |
|
|
|
# we mostly want DCompiler, but that gives us the Compiler.LINKER_PREFIX instead |
|
LINKER_PREFIX = GnuCompiler.LINKER_PREFIX |
|
id = 'gcc' |
|
|
|
def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, |
|
info: 'MachineInfo', arch: str, *, |
|
exe_wrapper: T.Optional['ExternalProgram'] = None, |
|
linker: T.Optional['DynamicLinker'] = None, |
|
full_version: T.Optional[str] = None, |
|
is_cross: bool = False): |
|
DCompiler.__init__(self, exelist, version, for_machine, info, arch, |
|
exe_wrapper=exe_wrapper, linker=linker, |
|
full_version=full_version, is_cross=is_cross) |
|
GnuCompiler.__init__(self, {}) |
|
default_warn_args = ['-Wall', '-Wdeprecated'] |
|
self.warn_args = {'0': [], |
|
'1': default_warn_args, |
|
'2': default_warn_args + ['-Wextra'], |
|
'3': default_warn_args + ['-Wextra', '-Wpedantic'], |
|
'everything': (default_warn_args + ['-Wextra', '-Wpedantic'] + |
|
self.supported_warn_args(gnu_common_warning_args))} |
|
|
|
self.base_options = { |
|
OptionKey(o) for o in [ |
|
'b_colorout', 'b_sanitize', 'b_staticpic', 'b_vscrt', |
|
'b_coverage', 'b_pgo', 'b_ndebug']} |
|
|
|
self._has_color_support = version_compare(self.version, '>=4.9') |
|
# dependencies were implemented before, but broken - support was fixed in GCC 7.1+ |
|
# (and some backported versions) |
|
self._has_deps_support = version_compare(self.version, '>=7.1') |
|
|
|
def get_colorout_args(self, colortype: str) -> T.List[str]: |
|
if self._has_color_support: |
|
super().get_colorout_args(colortype) |
|
return [] |
|
|
|
def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: |
|
if self._has_deps_support: |
|
return super().get_dependency_gen_args(outtarget, outfile) |
|
return [] |
|
|
|
def get_warn_args(self, level: str) -> T.List[str]: |
|
return self.warn_args[level] |
|
|
|
def get_buildtype_args(self, buildtype: str) -> T.List[str]: |
|
return d_gdc_buildtype_args[buildtype] |
|
|
|
def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], |
|
build_dir: str) -> T.List[str]: |
|
for idx, i in enumerate(parameter_list): |
|
if i[:2] == '-I' or i[:2] == '-L': |
|
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) |
|
|
|
return parameter_list |
|
|
|
def get_allow_undefined_link_args(self) -> T.List[str]: |
|
return self.linker.get_allow_undefined_args() |
|
|
|
def get_linker_always_args(self) -> T.List[str]: |
|
args = super().get_linker_always_args() |
|
if self.info.is_windows(): |
|
return args |
|
return args + ['-shared-libphobos'] |
|
|
|
def get_disable_assert_args(self) -> T.List[str]: |
|
return ['-frelease'] |
|
|
|
# LDC uses the DMD frontend code to parse and analyse the code. |
|
# It then uses LLVM for the binary code generation and optimizations. |
|
# This function retrieves the dmd frontend version, which determines |
|
# the common features between LDC and DMD. |
|
# We need the complete version text because the match is not on first line |
|
# of version_output |
|
def find_ldc_dmd_frontend_version(version_output: T.Optional[str]) -> T.Optional[str]: |
|
if version_output is None: |
|
return None |
|
version_regex = re.search(r'DMD v(\d+\.\d+\.\d+)', version_output) |
|
if version_regex: |
|
return version_regex.group(1) |
|
return None |
|
|
|
class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): |
|
|
|
id = 'llvm' |
|
|
|
def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, |
|
info: 'MachineInfo', arch: str, *, |
|
exe_wrapper: T.Optional['ExternalProgram'] = None, |
|
linker: T.Optional['DynamicLinker'] = None, |
|
full_version: T.Optional[str] = None, |
|
is_cross: bool = False, version_output: T.Optional[str] = None): |
|
DCompiler.__init__(self, exelist, version, for_machine, info, arch, |
|
exe_wrapper=exe_wrapper, linker=linker, |
|
full_version=full_version, is_cross=is_cross) |
|
DmdLikeCompilerMixin.__init__(self, dmd_frontend_version=find_ldc_dmd_frontend_version(version_output)) |
|
self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']} |
|
|
|
def get_colorout_args(self, colortype: str) -> T.List[str]: |
|
if colortype == 'always': |
|
return ['-enable-color'] |
|
return [] |
|
|
|
def get_warn_args(self, level: str) -> T.List[str]: |
|
if level in {'2', '3'}: |
|
return ['-wi', '-dw'] |
|
elif level == '1': |
|
return ['-wi'] |
|
return [] |
|
|
|
def get_buildtype_args(self, buildtype: str) -> T.List[str]: |
|
if buildtype != 'plain': |
|
return self._get_target_arch_args() + d_ldc_buildtype_args[buildtype] |
|
return d_ldc_buildtype_args[buildtype] |
|
|
|
def get_pic_args(self) -> T.List[str]: |
|
return ['-relocation-model=pic'] |
|
|
|
def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: |
|
return self._get_crt_args(crt_val, buildtype) |
|
|
|
def unix_args_to_native(self, args: T.List[str]) -> T.List[str]: |
|
return self._unix_args_to_native(args, self.info, self.linker.id) |
|
|
|
def get_optimization_args(self, optimization_level: str) -> T.List[str]: |
|
return ldc_optimization_args[optimization_level] |
|
|
|
@classmethod |
|
def use_linker_args(cls, linker: str, version: str) -> T.List[str]: |
|
return [f'-linker={linker}'] |
|
|
|
def get_linker_always_args(self) -> T.List[str]: |
|
args = super().get_linker_always_args() |
|
if self.info.is_windows(): |
|
return args |
|
return args + ['-link-defaultlib-shared'] |
|
|
|
def get_disable_assert_args(self) -> T.List[str]: |
|
return ['--release'] |
|
|
|
def rsp_file_syntax(self) -> RSPFileSyntax: |
|
# We use `mesonlib.is_windows` here because we want to know what the |
|
# build machine is, not the host machine. This really means we would |
|
# have the Environment not the MachineInfo in the compiler. |
|
return RSPFileSyntax.MSVC if is_windows() else RSPFileSyntax.GCC |
|
|
|
|
|
class DmdDCompiler(DmdLikeCompilerMixin, DCompiler): |
|
|
|
id = 'dmd' |
|
|
|
def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, |
|
info: 'MachineInfo', arch: str, *, |
|
exe_wrapper: T.Optional['ExternalProgram'] = None, |
|
linker: T.Optional['DynamicLinker'] = None, |
|
full_version: T.Optional[str] = None, |
|
is_cross: bool = False): |
|
DCompiler.__init__(self, exelist, version, for_machine, info, arch, |
|
exe_wrapper=exe_wrapper, linker=linker, |
|
full_version=full_version, is_cross=is_cross) |
|
DmdLikeCompilerMixin.__init__(self, version) |
|
self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']} |
|
|
|
def get_colorout_args(self, colortype: str) -> T.List[str]: |
|
if colortype == 'always': |
|
return ['-color=on'] |
|
return [] |
|
|
|
def get_buildtype_args(self, buildtype: str) -> T.List[str]: |
|
if buildtype != 'plain': |
|
return self._get_target_arch_args() + d_dmd_buildtype_args[buildtype] |
|
return d_dmd_buildtype_args[buildtype] |
|
|
|
def get_std_exe_link_args(self) -> T.List[str]: |
|
if self.info.is_windows(): |
|
# DMD links against D runtime only when main symbol is found, |
|
# so these needs to be inserted when linking static D libraries. |
|
if self.arch == 'x86_64': |
|
return ['phobos64.lib'] |
|
elif self.arch == 'x86_mscoff': |
|
return ['phobos32mscoff.lib'] |
|
return ['phobos.lib'] |
|
return [] |
|
|
|
def get_std_shared_lib_link_args(self) -> T.List[str]: |
|
libname = 'libphobos2.so' |
|
if self.info.is_windows(): |
|
if self.arch == 'x86_64': |
|
libname = 'phobos64.lib' |
|
elif self.arch == 'x86_mscoff': |
|
libname = 'phobos32mscoff.lib' |
|
else: |
|
libname = 'phobos.lib' |
|
return ['-shared', '-defaultlib=' + libname] |
|
|
|
def _get_target_arch_args(self) -> T.List[str]: |
|
# DMD32 and DMD64 on 64-bit Windows defaults to 32-bit (OMF). |
|
# Force the target to 64-bit in order to stay consistent |
|
# across the different platforms. |
|
if self.info.is_windows(): |
|
if self.arch == 'x86_64': |
|
return ['-m64'] |
|
elif self.arch == 'x86_mscoff': |
|
return ['-m32mscoff'] |
|
return ['-m32'] |
|
return [] |
|
|
|
def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: |
|
return self._get_crt_args(crt_val, buildtype) |
|
|
|
def unix_args_to_native(self, args: T.List[str]) -> T.List[str]: |
|
return self._unix_args_to_native(args, self.info, self.linker.id) |
|
|
|
def get_optimization_args(self, optimization_level: str) -> T.List[str]: |
|
return dmd_optimization_args[optimization_level] |
|
|
|
def can_linker_accept_rsp(self) -> bool: |
|
return False |
|
|
|
def get_linker_always_args(self) -> T.List[str]: |
|
args = super().get_linker_always_args() |
|
if self.info.is_windows(): |
|
return args |
|
return args + ['-defaultlib=phobos2', '-debuglib=phobos2'] |
|
|
|
def get_disable_assert_args(self) -> T.List[str]: |
|
return ['-release'] |
|
|
|
def rsp_file_syntax(self) -> RSPFileSyntax: |
|
return RSPFileSyntax.MSVC
|
|
|