# 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 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 = {'0': [], 'g': [], '1': ['-O1'], '2': ['-O2'], '3': ['-O3'], 's': ['-Oz'], } # type: T.Dict[str, T.List[str]] dmd_optimization_args = {'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) def _translate_args_to_nongnu(self, args: T.List[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 self.info.is_windows(): osargs = self.translate_arg_to_windows(arg) elif self.info.is_darwin(): osargs = self._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'): 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 self.linker.id == 'link' and self.info.is_windows() 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 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']} 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._translate_args_to_nongnu(args) 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._translate_args_to_nongnu(args) 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