diff options
Diffstat (limited to 'mesonbuild/compilers/d.py')
-rw-r--r-- | mesonbuild/compilers/d.py | 1022 |
1 files changed, 1022 insertions, 0 deletions
diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py new file mode 100644 index 0000000..90c0498 --- /dev/null +++ b/mesonbuild/compilers/d.py @@ -0,0 +1,1022 @@ +# 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) -> int: + if extra_args is None: + extra_args = [] + t = f''' + import std.stdio : writeln; + {prefix} + void main() {{ + writeln(({typename}).sizeof); + }} + ''' + res = self.run(t, env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + return -1 + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run sizeof test binary.') + return int(res.stdout) + + def alignment(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + 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 + + 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 |