summaryrefslogtreecommitdiffstats
path: root/mesonbuild/compilers/d.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--mesonbuild/compilers/d.py1022
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