1# Copyright 2012-2017 The Meson development team
2
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6
7#     http://www.apache.org/licenses/LICENSE-2.0
8
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import os.path
16import re
17import subprocess
18import typing as T
19
20from ..mesonlib import (
21    EnvironmentException, MachineChoice, version_compare, OptionKey, is_windows
22)
23
24from ..arglist import CompilerArgs
25from ..linkers import RSPFileSyntax
26from .compilers import (
27    d_dmd_buildtype_args,
28    d_gdc_buildtype_args,
29    d_ldc_buildtype_args,
30    clike_debug_args,
31    Compiler,
32)
33from .mixins.gnu import GnuCompiler
34
35if T.TYPE_CHECKING:
36    from .compilers import Compiler as CompilerMixinBase
37    from ..programs import ExternalProgram
38    from ..envconfig import MachineInfo
39    from ..environment import Environment
40    from ..linkers import DynamicLinker
41else:
42    CompilerMixinBase = object
43
44d_feature_args = {'gcc':  {'unittest': '-funittest',
45                           'debug': '-fdebug',
46                           'version': '-fversion',
47                           'import_dir': '-J'
48                           },
49                  'llvm': {'unittest': '-unittest',
50                           'debug': '-d-debug',
51                           'version': '-d-version',
52                           'import_dir': '-J'
53                           },
54                  'dmd':  {'unittest': '-unittest',
55                           'debug': '-debug',
56                           'version': '-version',
57                           'import_dir': '-J'
58                           }
59                  }  # type: T.Dict[str, T.Dict[str, str]]
60
61ldc_optimization_args = {'0': [],
62                         'g': [],
63                         '1': ['-O1'],
64                         '2': ['-O2'],
65                         '3': ['-O3'],
66                         's': ['-Os'],
67                         }  # type: T.Dict[str, T.List[str]]
68
69dmd_optimization_args = {'0': [],
70                         'g': [],
71                         '1': ['-O'],
72                         '2': ['-O'],
73                         '3': ['-O'],
74                         's': ['-O'],
75                         }  # type: T.Dict[str, T.List[str]]
76
77
78class DmdLikeCompilerMixin(CompilerMixinBase):
79
80    """Mixin class for DMD and LDC.
81
82    LDC has a number of DMD like arguments, and this class allows for code
83    sharing between them as makes sense.
84    """
85
86    def __init__(self, dmd_frontend_version: T.Optional[str]):
87        if dmd_frontend_version is None:
88            self._dmd_has_depfile = False
89        else:
90            # -makedeps switch introduced in 2.095 frontend
91            self._dmd_has_depfile = version_compare(dmd_frontend_version, ">=2.095.0")
92
93    if T.TYPE_CHECKING:
94        mscrt_args = {}  # type: T.Dict[str, T.List[str]]
95
96        def _get_target_arch_args(self) -> T.List[str]: ...
97
98    LINKER_PREFIX = '-L='
99
100    def get_output_args(self, outputname: str) -> T.List[str]:
101        return ['-of=' + outputname]
102
103    def get_linker_output_args(self, outputname: str) -> T.List[str]:
104        return ['-of=' + outputname]
105
106    def get_include_args(self, path: str, is_system: bool) -> T.List[str]:
107        return ['-I=' + path]
108
109    def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str],
110                                               build_dir: str) -> T.List[str]:
111        for idx, i in enumerate(parameter_list):
112            if i[:3] == '-I=':
113                parameter_list[idx] = i[:3] + os.path.normpath(os.path.join(build_dir, i[3:]))
114            if i[:4] == '-L-L':
115                parameter_list[idx] = i[:4] + os.path.normpath(os.path.join(build_dir, i[4:]))
116            if i[:5] == '-L=-L':
117                parameter_list[idx] = i[:5] + os.path.normpath(os.path.join(build_dir, i[5:]))
118            if i[:6] == '-Wl,-L':
119                parameter_list[idx] = i[:6] + os.path.normpath(os.path.join(build_dir, i[6:]))
120
121        return parameter_list
122
123    def get_warn_args(self, level: str) -> T.List[str]:
124        return ['-wi']
125
126    def get_werror_args(self) -> T.List[str]:
127        return ['-w']
128
129    def get_coverage_args(self) -> T.List[str]:
130        return ['-cov']
131
132    def get_coverage_link_args(self) -> T.List[str]:
133        return []
134
135    def get_preprocess_only_args(self) -> T.List[str]:
136        return ['-E']
137
138    def get_compile_only_args(self) -> T.List[str]:
139        return ['-c']
140
141    def get_depfile_suffix(self) -> str:
142        return 'deps'
143
144    def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
145        if self._dmd_has_depfile:
146            return [f'-makedeps={outfile}']
147        return []
148
149    def get_pic_args(self) -> T.List[str]:
150        if self.info.is_windows():
151            return []
152        return ['-fPIC']
153
154    def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]:
155        # TODO: using a TypeDict here would improve this
156        res = []
157        # get_feature_args can be called multiple times for the same target when there is generated source
158        # so we have to copy the kwargs (target.d_features) dict before popping from it
159        kwargs = kwargs.copy()
160        if 'unittest' in kwargs:
161            unittest = kwargs.pop('unittest')
162            unittest_arg = d_feature_args[self.id]['unittest']
163            if not unittest_arg:
164                raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string())
165            if unittest:
166                res.append(unittest_arg)
167
168        if 'debug' in kwargs:
169            debug_level = -1
170            debugs = kwargs.pop('debug')
171            if not isinstance(debugs, list):
172                debugs = [debugs]
173
174            debug_arg = d_feature_args[self.id]['debug']
175            if not debug_arg:
176                raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string())
177
178            # Parse all debug identifiers and the largest debug level identifier
179            for d in debugs:
180                if isinstance(d, int):
181                    if d > debug_level:
182                        debug_level = d
183                elif isinstance(d, str) and d.isdigit():
184                    if int(d) > debug_level:
185                        debug_level = int(d)
186                else:
187                    res.append(f'{debug_arg}={d}')
188
189            if debug_level >= 0:
190                res.append(f'{debug_arg}={debug_level}')
191
192        if 'versions' in kwargs:
193            version_level = -1
194            versions = kwargs.pop('versions')
195            if not isinstance(versions, list):
196                versions = [versions]
197
198            version_arg = d_feature_args[self.id]['version']
199            if not version_arg:
200                raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string())
201
202            # Parse all version identifiers and the largest version level identifier
203            for v in versions:
204                if isinstance(v, int):
205                    if v > version_level:
206                        version_level = v
207                elif isinstance(v, str) and v.isdigit():
208                    if int(v) > version_level:
209                        version_level = int(v)
210                else:
211                    res.append(f'{version_arg}={v}')
212
213            if version_level >= 0:
214                res.append(f'{version_arg}={version_level}')
215
216        if 'import_dirs' in kwargs:
217            import_dirs = kwargs.pop('import_dirs')
218            if not isinstance(import_dirs, list):
219                import_dirs = [import_dirs]
220
221            import_dir_arg = d_feature_args[self.id]['import_dir']
222            if not import_dir_arg:
223                raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string())
224            for idir_obj in import_dirs:
225                basedir = idir_obj.get_curdir()
226                for idir in idir_obj.get_incdirs():
227                    bldtreedir = os.path.join(basedir, idir)
228                    # Avoid superfluous '/.' at the end of paths when d is '.'
229                    if idir not in ('', '.'):
230                        expdir = bldtreedir
231                    else:
232                        expdir = basedir
233                    srctreedir = os.path.join(build_to_src, expdir)
234                    res.append(f'{import_dir_arg}{srctreedir}')
235                    res.append(f'{import_dir_arg}{bldtreedir}')
236
237        if kwargs:
238            raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys()))
239
240        return res
241
242    def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]:
243        if buildtype != 'plain':
244            return self._get_target_arch_args()
245        return []
246
247    def gen_import_library_args(self, implibname: str) -> T.List[str]:
248        return self.linker.import_library_args(implibname)
249
250    def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str,
251                         rpath_paths: str, build_rpath: str,
252                         install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]:
253        if self.info.is_windows():
254            return ([], set())
255
256        # GNU ld, solaris ld, and lld acting like GNU ld
257        if self.linker.id.startswith('ld'):
258            # The way that dmd and ldc pass rpath to gcc is different than we would
259            # do directly, each argument -rpath and the value to rpath, need to be
260            # split into two separate arguments both prefaced with the -L=.
261            args = []
262            (rpath_args, rpath_dirs_to_remove) = super().build_rpath_args(
263                    env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
264            for r in rpath_args:
265                if ',' in r:
266                    a, b = r.split(',', maxsplit=1)
267                    args.append(a)
268                    args.append(self.LINKER_PREFIX + b)
269                else:
270                    args.append(r)
271            return (args, rpath_dirs_to_remove)
272
273        return super().build_rpath_args(
274            env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
275
276    def _translate_args_to_nongnu(self, args: T.List[str]) -> T.List[str]:
277        # Translate common arguments to flags the LDC/DMD compilers
278        # can understand.
279        # The flags might have been added by pkg-config files,
280        # and are therefore out of the user's control.
281        dcargs = []
282        # whether we hit a linker argument that expect another arg
283        # see the comment in the "-L" section
284        link_expect_arg = False
285        link_flags_with_arg = [
286            '-rpath', '-soname', '-compatibility_version', '-current_version',
287        ]
288        for arg in args:
289            # Translate OS specific arguments first.
290            osargs = []  # type: T.List[str]
291            if self.info.is_windows():
292                osargs = self.translate_arg_to_windows(arg)
293            elif self.info.is_darwin():
294                osargs = self._translate_arg_to_osx(arg)
295            if osargs:
296                dcargs.extend(osargs)
297                continue
298
299            # Translate common D arguments here.
300            if arg == '-pthread':
301                continue
302            if arg.startswith('-fstack-protector'):
303                continue
304            if arg.startswith('-D'):
305                continue
306            if arg.startswith('-Wl,'):
307                # Translate linker arguments here.
308                linkargs = arg[arg.index(',') + 1:].split(',')
309                for la in linkargs:
310                    dcargs.append('-L=' + la.strip())
311                continue
312            elif arg.startswith(('-link-defaultlib', '-linker', '-link-internally', '-linkonce-templates', '-lib')):
313                # these are special arguments to the LDC linker call,
314                # arguments like "-link-defaultlib-shared" do *not*
315                # denote a library to be linked, but change the default
316                # Phobos/DRuntime linking behavior, while "-linker" sets the
317                # default linker.
318                dcargs.append(arg)
319                continue
320            elif arg.startswith('-l'):
321                # translate library link flag
322                dcargs.append('-L=' + arg)
323                continue
324            elif arg.startswith('-isystem'):
325                # translate -isystem system include path
326                # this flag might sometimes be added by C library Cflags via
327                # pkg-config.
328                # NOTE: -isystem and -I are not 100% equivalent, so this is just
329                # a workaround for the most common cases.
330                if arg.startswith('-isystem='):
331                    dcargs.append('-I=' + arg[9:])
332                else:
333                    dcargs.append('-I' + arg[8:])
334                continue
335            elif arg.startswith('-idirafter'):
336                # same as -isystem, but appends the path instead
337                if arg.startswith('-idirafter='):
338                    dcargs.append('-I=' + arg[11:])
339                else:
340                    dcargs.append('-I' + arg[10:])
341                continue
342            elif arg.startswith('-L'):
343                # The D linker expect library search paths in the form of -L=-L/path (the '=' is optional).
344                #
345                # This function receives a mix of arguments already prepended
346                # with -L for the D linker driver and other linker arguments.
347                # The arguments starting with -L can be:
348                #  - library search path (with or without a second -L)
349                #     - it can come from pkg-config (a single -L)
350                #     - or from the user passing linker flags (-L-L would be expected)
351                #  - arguments like "-L=-rpath" that expect a second argument (also prepended with -L)
352                #  - arguments like "-L=@rpath/xxx" without a second argument (on Apple platform)
353                #  - arguments like "-L=/SUBSYSTEM:CONSOLE (for Windows linker)
354                #
355                # The logic that follows trys to detect all these cases (some may be missing)
356                # in order to prepend a -L only for the library search paths with a single -L
357
358                if arg.startswith('-L='):
359                    suffix = arg[3:]
360                else:
361                    suffix = arg[2:]
362
363                if link_expect_arg:
364                    # flags like rpath and soname expect a path or filename respectively,
365                    # we must not alter it (i.e. prefixing with -L for a lib search path)
366                    dcargs.append(arg)
367                    link_expect_arg = False
368                    continue
369
370                if suffix in link_flags_with_arg:
371                    link_expect_arg = True
372
373                if suffix.startswith('-') or suffix.startswith('@'):
374                    # this is not search path
375                    dcargs.append(arg)
376                    continue
377
378                # linker flag such as -L=/DEBUG must pass through
379                if self.linker.id == 'link' and self.info.is_windows() and suffix.startswith('/'):
380                    dcargs.append(arg)
381                    continue
382
383                # Make sure static library files are passed properly to the linker.
384                if arg.endswith('.a') or arg.endswith('.lib'):
385                    if len(suffix) > 0 and not suffix.startswith('-'):
386                        dcargs.append('-L=' + suffix)
387                        continue
388
389                dcargs.append('-L=' + arg)
390                continue
391            elif not arg.startswith('-') and arg.endswith(('.a', '.lib')):
392                # ensure static libraries are passed through to the linker
393                dcargs.append('-L=' + arg)
394                continue
395            else:
396                dcargs.append(arg)
397
398        return dcargs
399
400    @classmethod
401    def translate_arg_to_windows(cls, arg: str) -> T.List[str]:
402        args = []
403        if arg.startswith('-Wl,'):
404            # Translate linker arguments here.
405            linkargs = arg[arg.index(',') + 1:].split(',')
406            for la in linkargs:
407                if la.startswith('--out-implib='):
408                    # Import library name
409                    args.append('-L=/IMPLIB:' + la[13:].strip())
410        elif arg.startswith('-mscrtlib='):
411            args.append(arg)
412            mscrtlib = arg[10:].lower()
413            if cls is LLVMDCompiler:
414                # Default crt libraries for LDC2 must be excluded for other
415                # selected crt options.
416                if mscrtlib != 'libcmt':
417                    args.append('-L=/NODEFAULTLIB:libcmt')
418                    args.append('-L=/NODEFAULTLIB:libvcruntime')
419
420                # Fixes missing definitions for printf-functions in VS2017
421                if mscrtlib.startswith('msvcrt'):
422                    args.append('-L=/DEFAULTLIB:legacy_stdio_definitions.lib')
423
424        return args
425
426    @classmethod
427    def _translate_arg_to_osx(cls, arg: str) -> T.List[str]:
428        args = []
429        if arg.startswith('-install_name'):
430            args.append('-L=' + arg)
431        return args
432
433    def get_debug_args(self, is_debug: bool) -> T.List[str]:
434        ddebug_args = []
435        if is_debug:
436            ddebug_args = [d_feature_args[self.id]['debug']]
437
438        return clike_debug_args[is_debug] + ddebug_args
439
440    def _get_crt_args(self, crt_val: str, buildtype: str) -> T.List[str]:
441        if not self.info.is_windows():
442            return []
443
444        if crt_val in self.mscrt_args:
445            return self.mscrt_args[crt_val]
446        assert(crt_val in ['from_buildtype', 'static_from_buildtype'])
447
448        dbg = 'mdd'
449        rel = 'md'
450        if crt_val == 'static_from_buildtype':
451            dbg = 'mtd'
452            rel = 'mt'
453
454        # Match what build type flags used to do.
455        if buildtype == 'plain':
456            return []
457        elif buildtype == 'debug':
458            return self.mscrt_args[dbg]
459        elif buildtype == 'debugoptimized':
460            return self.mscrt_args[rel]
461        elif buildtype == 'release':
462            return self.mscrt_args[rel]
463        elif buildtype == 'minsize':
464            return self.mscrt_args[rel]
465        else:
466            assert(buildtype == 'custom')
467            raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
468
469    def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str,
470                        suffix: str, soversion: str,
471                        darwin_versions: T.Tuple[str, str],
472                        is_shared_module: bool) -> T.List[str]:
473        sargs = super().get_soname_args(env, prefix, shlib_name, suffix,
474                                        soversion, darwin_versions, is_shared_module)
475
476        # LDC and DMD actually do use a linker, but they proxy all of that with
477        # their own arguments
478        if self.linker.id.startswith('ld.'):
479            soargs = []
480            for arg in sargs:
481                a, b = arg.split(',', maxsplit=1)
482                soargs.append(a)
483                soargs.append(self.LINKER_PREFIX + b)
484            return soargs
485        elif self.linker.id.startswith('ld64'):
486            soargs = []
487            for arg in sargs:
488                if not arg.startswith(self.LINKER_PREFIX):
489                    soargs.append(self.LINKER_PREFIX + arg)
490                else:
491                    soargs.append(arg)
492            return soargs
493        else:
494            return sargs
495
496    def get_allow_undefined_link_args(self) -> T.List[str]:
497        args = self.linker.get_allow_undefined_args()
498        if self.info.is_darwin():
499            # On macOS we're passing these options to the C compiler, but
500            # they're linker options and need -Wl, so clang/gcc knows what to
501            # do with them. I'm assuming, but don't know for certain, that
502            # ldc/dmd do some kind of mapping internally for arguments they
503            # understand, but pass arguments they don't understand directly.
504            args = [a.replace('-L=', '-Xcc=-Wl,') for a in args]
505        return args
506
507
508class DCompilerArgs(CompilerArgs):
509    prepend_prefixes = ('-I', '-L')
510    dedup2_prefixes = ('-I', )
511
512
513class DCompiler(Compiler):
514    mscrt_args = {
515        'none': ['-mscrtlib='],
516        'md': ['-mscrtlib=msvcrt'],
517        'mdd': ['-mscrtlib=msvcrtd'],
518        'mt': ['-mscrtlib=libcmt'],
519        'mtd': ['-mscrtlib=libcmtd'],
520    }
521
522    language = 'd'
523
524    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
525                 info: 'MachineInfo', arch: str, *,
526                 exe_wrapper: T.Optional['ExternalProgram'] = None,
527                 linker: T.Optional['DynamicLinker'] = None,
528                 full_version: T.Optional[str] = None,
529                 is_cross: bool = False):
530        super().__init__(exelist, version, for_machine, info, linker=linker,
531                         full_version=full_version, is_cross=is_cross)
532        self.arch = arch
533        self.exe_wrapper = exe_wrapper
534
535    def sanity_check(self, work_dir: str, environment: 'Environment') -> None:
536        source_name = os.path.join(work_dir, 'sanity.d')
537        output_name = os.path.join(work_dir, 'dtest')
538        with open(source_name, 'w', encoding='utf-8') as ofile:
539            ofile.write('''void main() { }''')
540        pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name], cwd=work_dir)
541        pc.wait()
542        if pc.returncode != 0:
543            raise EnvironmentException('D compiler %s can not compile programs.' % self.name_string())
544        if self.is_cross:
545            if self.exe_wrapper is None:
546                # Can't check if the binaries run so we have to assume they do
547                return
548            cmdlist = self.exe_wrapper.get_command() + [output_name]
549        else:
550            cmdlist = [output_name]
551        if subprocess.call(cmdlist) != 0:
552            raise EnvironmentException('Executables created by D compiler %s are not runnable.' % self.name_string())
553
554    def needs_static_linker(self) -> bool:
555        return True
556
557    def get_depfile_suffix(self) -> str:
558        return 'deps'
559
560    def get_pic_args(self) -> T.List[str]:
561        if self.info.is_windows():
562            return []
563        return ['-fPIC']
564
565    def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]:
566        # TODO: using a TypeDict here would improve this
567        res = []
568        # get_feature_args can be called multiple times for the same target when there is generated source
569        # so we have to copy the kwargs (target.d_features) dict before popping from it
570        kwargs = kwargs.copy()
571        if 'unittest' in kwargs:
572            unittest = kwargs.pop('unittest')
573            unittest_arg = d_feature_args[self.id]['unittest']
574            if not unittest_arg:
575                raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string())
576            if unittest:
577                res.append(unittest_arg)
578
579        if 'debug' in kwargs:
580            debug_level = -1
581            debugs = kwargs.pop('debug')
582            if not isinstance(debugs, list):
583                debugs = [debugs]
584
585            debug_arg = d_feature_args[self.id]['debug']
586            if not debug_arg:
587                raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string())
588
589            # Parse all debug identifiers and the largest debug level identifier
590            for d in debugs:
591                if isinstance(d, int):
592                    if d > debug_level:
593                        debug_level = d
594                elif isinstance(d, str) and d.isdigit():
595                    if int(d) > debug_level:
596                        debug_level = int(d)
597                else:
598                    res.append(f'{debug_arg}={d}')
599
600            if debug_level >= 0:
601                res.append(f'{debug_arg}={debug_level}')
602
603        if 'versions' in kwargs:
604            version_level = -1
605            versions = kwargs.pop('versions')
606            if not isinstance(versions, list):
607                versions = [versions]
608
609            version_arg = d_feature_args[self.id]['version']
610            if not version_arg:
611                raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string())
612
613            # Parse all version identifiers and the largest version level identifier
614            for v in versions:
615                if isinstance(v, int):
616                    if v > version_level:
617                        version_level = v
618                elif isinstance(v, str) and v.isdigit():
619                    if int(v) > version_level:
620                        version_level = int(v)
621                else:
622                    res.append(f'{version_arg}={v}')
623
624            if version_level >= 0:
625                res.append(f'{version_arg}={version_level}')
626
627        if 'import_dirs' in kwargs:
628            import_dirs = kwargs.pop('import_dirs')
629            if not isinstance(import_dirs, list):
630                import_dirs = [import_dirs]
631
632            import_dir_arg = d_feature_args[self.id]['import_dir']
633            if not import_dir_arg:
634                raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string())
635            for idir_obj in import_dirs:
636                basedir = idir_obj.get_curdir()
637                for idir in idir_obj.get_incdirs():
638                    bldtreedir = os.path.join(basedir, idir)
639                    # Avoid superfluous '/.' at the end of paths when d is '.'
640                    if idir not in ('', '.'):
641                        expdir = bldtreedir
642                    else:
643                        expdir = basedir
644                    srctreedir = os.path.join(build_to_src, expdir)
645                    res.append(f'{import_dir_arg}{srctreedir}')
646                    res.append(f'{import_dir_arg}{bldtreedir}')
647
648        if kwargs:
649            raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys()))
650
651        return res
652
653    def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]:
654        if buildtype != 'plain':
655            return self._get_target_arch_args()
656        return []
657
658    def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> DCompilerArgs:
659        return DCompilerArgs(self, args)
660
661    def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]:
662        return self.compiles('int i;\n', env, extra_args=args)
663
664    def _get_target_arch_args(self) -> T.List[str]:
665        # LDC2 on Windows targets to current OS architecture, but
666        # it should follow the target specified by the MSVC toolchain.
667        if self.info.is_windows():
668            if self.arch == 'x86_64':
669                return ['-m64']
670            return ['-m32']
671        return []
672
673    def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]:
674        return []
675
676    def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]:
677        return []
678
679
680class GnuDCompiler(GnuCompiler, DCompiler):
681
682    # we mostly want DCompiler, but that gives us the Compiler.LINKER_PREFIX instead
683    LINKER_PREFIX = GnuCompiler.LINKER_PREFIX
684
685    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
686                 info: 'MachineInfo', arch: str, *,
687                 exe_wrapper: T.Optional['ExternalProgram'] = None,
688                 linker: T.Optional['DynamicLinker'] = None,
689                 full_version: T.Optional[str] = None,
690                 is_cross: bool = False):
691        DCompiler.__init__(self, exelist, version, for_machine, info, arch,
692                           exe_wrapper=exe_wrapper, linker=linker,
693                           full_version=full_version, is_cross=is_cross)
694        GnuCompiler.__init__(self, {})
695        self.id = 'gcc'
696        default_warn_args = ['-Wall', '-Wdeprecated']
697        self.warn_args = {'0': [],
698                          '1': default_warn_args,
699                          '2': default_warn_args + ['-Wextra'],
700                          '3': default_warn_args + ['-Wextra', '-Wpedantic']}
701        self.base_options = {
702            OptionKey(o) for o in [
703             'b_colorout', 'b_sanitize', 'b_staticpic', 'b_vscrt',
704             'b_coverage', 'b_pgo', 'b_ndebug']}
705
706        self._has_color_support = version_compare(self.version, '>=4.9')
707        # dependencies were implemented before, but broken - support was fixed in GCC 7.1+
708        # (and some backported versions)
709        self._has_deps_support = version_compare(self.version, '>=7.1')
710
711    def get_colorout_args(self, colortype: str) -> T.List[str]:
712        if self._has_color_support:
713            super().get_colorout_args(colortype)
714        return []
715
716    def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
717        if self._has_deps_support:
718            return super().get_dependency_gen_args(outtarget, outfile)
719        return []
720
721    def get_warn_args(self, level: str) -> T.List[str]:
722        return self.warn_args[level]
723
724    def get_buildtype_args(self, buildtype: str) -> T.List[str]:
725        return d_gdc_buildtype_args[buildtype]
726
727    def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str],
728                                               build_dir: str) -> T.List[str]:
729        for idx, i in enumerate(parameter_list):
730            if i[:2] == '-I' or i[:2] == '-L':
731                parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
732
733        return parameter_list
734
735    def get_allow_undefined_link_args(self) -> T.List[str]:
736        return self.linker.get_allow_undefined_args()
737
738    def get_linker_always_args(self) -> T.List[str]:
739        args = super().get_linker_always_args()
740        if self.info.is_windows():
741            return args
742        return args + ['-shared-libphobos']
743
744    def get_disable_assert_args(self) -> T.List[str]:
745        return ['-frelease']
746
747# LDC uses the DMD frontend code to parse and analyse the code.
748# It then uses LLVM for the binary code generation and optimizations.
749# This function retrieves the dmd frontend version, which determines
750# the common features between LDC and DMD.
751# We need the complete version text because the match is not on first line
752# of version_output
753def find_ldc_dmd_frontend_version(version_output: T.Optional[str]) -> T.Optional[str]:
754    if version_output is None:
755        return None
756    version_regex = re.search(r'DMD v(\d+\.\d+\.\d+)', version_output)
757    if version_regex:
758        return version_regex.group(1)
759    return None
760
761class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler):
762
763    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
764                 info: 'MachineInfo', arch: str, *,
765                 exe_wrapper: T.Optional['ExternalProgram'] = None,
766                 linker: T.Optional['DynamicLinker'] = None,
767                 full_version: T.Optional[str] = None,
768                 is_cross: bool = False, version_output: T.Optional[str] = None):
769        DCompiler.__init__(self, exelist, version, for_machine, info, arch,
770                           exe_wrapper=exe_wrapper, linker=linker,
771                           full_version=full_version, is_cross=is_cross)
772        DmdLikeCompilerMixin.__init__(self, dmd_frontend_version=find_ldc_dmd_frontend_version(version_output))
773        self.id = 'llvm'
774        self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']}
775
776    def get_colorout_args(self, colortype: str) -> T.List[str]:
777        if colortype == 'always':
778            return ['-enable-color']
779        return []
780
781    def get_warn_args(self, level: str) -> T.List[str]:
782        if level in {'2', '3'}:
783            return ['-wi', '-dw']
784        elif level == '1':
785            return ['-wi']
786        return []
787
788    def get_buildtype_args(self, buildtype: str) -> T.List[str]:
789        if buildtype != 'plain':
790            return self._get_target_arch_args() + d_ldc_buildtype_args[buildtype]
791        return d_ldc_buildtype_args[buildtype]
792
793    def get_pic_args(self) -> T.List[str]:
794        return ['-relocation-model=pic']
795
796    def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]:
797        return self._get_crt_args(crt_val, buildtype)
798
799    def unix_args_to_native(self, args: T.List[str]) -> T.List[str]:
800        return self._translate_args_to_nongnu(args)
801
802    def get_optimization_args(self, optimization_level: str) -> T.List[str]:
803        return ldc_optimization_args[optimization_level]
804
805    @classmethod
806    def use_linker_args(cls, linker: str) -> T.List[str]:
807        return [f'-linker={linker}']
808
809    def get_linker_always_args(self) -> T.List[str]:
810        args = super().get_linker_always_args()
811        if self.info.is_windows():
812            return args
813        return args + ['-link-defaultlib-shared']
814
815    def get_disable_assert_args(self) -> T.List[str]:
816        return ['--release']
817
818    def rsp_file_syntax(self) -> RSPFileSyntax:
819        # We use `mesonlib.is_windows` here because we want to konw what the
820        # build machine is, not the host machine. This really means whe whould
821        # have the Environment not the MachineInfo in the compiler.
822        return RSPFileSyntax.MSVC if is_windows() else RSPFileSyntax.GCC
823
824
825class DmdDCompiler(DmdLikeCompilerMixin, DCompiler):
826
827    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
828                 info: 'MachineInfo', arch: str, *,
829                 exe_wrapper: T.Optional['ExternalProgram'] = None,
830                 linker: T.Optional['DynamicLinker'] = None,
831                 full_version: T.Optional[str] = None,
832                 is_cross: bool = False):
833        DCompiler.__init__(self, exelist, version, for_machine, info, arch,
834                           exe_wrapper=exe_wrapper, linker=linker,
835                           full_version=full_version, is_cross=is_cross)
836        DmdLikeCompilerMixin.__init__(self, version)
837        self.id = 'dmd'
838        self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']}
839
840    def get_colorout_args(self, colortype: str) -> T.List[str]:
841        if colortype == 'always':
842            return ['-color=on']
843        return []
844
845    def get_buildtype_args(self, buildtype: str) -> T.List[str]:
846        if buildtype != 'plain':
847            return self._get_target_arch_args() + d_dmd_buildtype_args[buildtype]
848        return d_dmd_buildtype_args[buildtype]
849
850    def get_std_exe_link_args(self) -> T.List[str]:
851        if self.info.is_windows():
852            # DMD links against D runtime only when main symbol is found,
853            # so these needs to be inserted when linking static D libraries.
854            if self.arch == 'x86_64':
855                return ['phobos64.lib']
856            elif self.arch == 'x86_mscoff':
857                return ['phobos32mscoff.lib']
858            return ['phobos.lib']
859        return []
860
861    def get_std_shared_lib_link_args(self) -> T.List[str]:
862        libname = 'libphobos2.so'
863        if self.info.is_windows():
864            if self.arch == 'x86_64':
865                libname = 'phobos64.lib'
866            elif self.arch == 'x86_mscoff':
867                libname = 'phobos32mscoff.lib'
868            else:
869                libname = 'phobos.lib'
870        return ['-shared', '-defaultlib=' + libname]
871
872    def _get_target_arch_args(self) -> T.List[str]:
873        # DMD32 and DMD64 on 64-bit Windows defaults to 32-bit (OMF).
874        # Force the target to 64-bit in order to stay consistent
875        # across the different platforms.
876        if self.info.is_windows():
877            if self.arch == 'x86_64':
878                return ['-m64']
879            elif self.arch == 'x86_mscoff':
880                return ['-m32mscoff']
881            return ['-m32']
882        return []
883
884    def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]:
885        return self._get_crt_args(crt_val, buildtype)
886
887    def unix_args_to_native(self, args: T.List[str]) -> T.List[str]:
888        return self._translate_args_to_nongnu(args)
889
890    def get_optimization_args(self, optimization_level: str) -> T.List[str]:
891        return dmd_optimization_args[optimization_level]
892
893    def can_linker_accept_rsp(self) -> bool:
894        return False
895
896    def get_linker_always_args(self) -> T.List[str]:
897        args = super().get_linker_always_args()
898        if self.info.is_windows():
899            return args
900        return args + ['-defaultlib=phobos2', '-debuglib=phobos2']
901
902    def get_disable_assert_args(self) -> T.List[str]:
903        return ['-release']
904
905    def rsp_file_syntax(self) -> RSPFileSyntax:
906        return RSPFileSyntax.MSVC
907