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