toolchain.configure (129468B)
1 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- 2 # vim: set filetype=python: 3 # This Source Code Form is subject to the terms of the Mozilla Public 4 # License, v. 2.0. If a copy of the MPL was not distributed with this 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 # Environment variables that impact the compilation step 8 # ============================================================== 9 option( 10 env="HOST_CPPFLAGS", 11 help="Extra flags for Preprocessing host sources", 12 nargs=1, 13 default="", 14 ) 15 16 17 option( 18 env="HOST_CFLAGS", 19 help="Extra flags for compiling host C sources", 20 nargs=1, 21 default="", 22 ) 23 24 25 option( 26 env="HOST_CXXFLAGS", 27 help="Extra flags for compiling host C++ sources", 28 nargs=1, 29 default="", 30 ) 31 32 33 option( 34 env="HOST_LDFLAGS", 35 help="Extra flags for linking host object files", 36 nargs=1, 37 default="", 38 ) 39 40 41 option( 42 env="CPPFLAGS", 43 help="Extra flags for preprocessing sources", 44 nargs=1, 45 default="", 46 ) 47 48 49 option( 50 env="CFLAGS", 51 help="Extra flags for compiling C sources", 52 nargs=1, 53 default="", 54 ) 55 56 57 option( 58 env="CXXFLAGS", 59 help="Extra flags for compiling C++ sources", 60 nargs=1, 61 default="", 62 ) 63 64 65 option( 66 env="ASFLAGS", 67 help="Extra flags for assembling sources", 68 nargs=1, 69 default="", 70 ) 71 72 73 option( 74 env="LDFLAGS", 75 help="Extra flags for linking object files", 76 nargs=1, 77 default="", 78 ) 79 80 81 option( 82 env="LIBS", 83 help="Extra libraries for linking object files", 84 nargs=1, 85 default="", 86 ) 87 88 89 option( 90 env="MOZ_OPTIMIZE_FLAGS", 91 help="Extra optimization flags", 92 nargs=1, 93 ) 94 95 96 # Code optimization 97 # ============================================================== 98 99 option("--disable-optimize", nargs="?", help="Disable optimizations via compiler flags") 100 101 102 @depends(target, when="MOZ_PGO") 103 def forced_pgo_optimization_level(target): 104 if target.kernel == "Linux" and target.os != "Android": 105 return "-O3" 106 107 108 @imports(_from="mozshellutil", _import="quote") 109 def check_optimize_flags(src, flags): 110 for flag in reversed(flags): 111 if flag.startswith(("-O", "/O")): 112 if flag[2:] == "0": 113 die( 114 f"Optimization enabled through {src} but last optimization flag is {flag} which disables optimizations" 115 ) 116 break 117 else: 118 die( 119 f"Optimization enabled through {src} but no optimization flag found in {quote(*flags)}" 120 ) 121 return flags 122 123 124 @depends("--enable-optimize", "MOZ_OPTIMIZE_FLAGS") 125 @imports(_from="mozshellutil", _import="split") 126 def configured_moz_optimize_flags(enable_optimize, env_flags): 127 if len(enable_optimize): 128 return check_optimize_flags("--enable-optimize", split(enable_optimize[0])) 129 if len(env_flags): 130 return check_optimize_flags("MOZ_OPTIMIZE_FLAGS", split(env_flags[0])) 131 132 133 @depends("--enable-optimize", "MOZ_OPTIMIZE_FLAGS") 134 def moz_optimize(enable_optimize, env_flags): 135 return "1" if enable_optimize or env_flags else None 136 137 138 set_config("MOZ_OPTIMIZE", moz_optimize) 139 140 141 @depends( 142 target, moz_optimize, configured_moz_optimize_flags, forced_pgo_optimization_level 143 ) 144 @imports(_from="mozshellutil", _import="split") 145 def moz_optimize_flags( 146 target, moz_optimize, configured_moz_optimize_flags, forced_pgo_optimization_level 147 ): 148 if configured_moz_optimize_flags: 149 return configured_moz_optimize_flags 150 151 if moz_optimize and forced_pgo_optimization_level: 152 return [forced_pgo_optimization_level] 153 154 if target.kernel == "Darwin": 155 return ["-O3"] 156 elif target.kernel in ("Linux", "WINNT"): 157 return ["-O2"] 158 else: 159 return ["-O"] 160 161 162 # Android NDK 163 # ============================================================== 164 165 166 @depends("--disable-compile-environment", target) 167 def compiling_android(compile_env, target): 168 return compile_env and target.os == "Android" 169 170 171 include("android-ndk.configure", when=compiling_android) 172 173 with only_when(target_is_osx): 174 # MacOS deployment target version 175 # ============================================================== 176 # This needs to happen before any compilation test is done. 177 178 option( 179 "--enable-macos-target", 180 env="MACOSX_DEPLOYMENT_TARGET", 181 nargs=1, 182 default=depends(target, developer_options) 183 # We continue to target 10.15 on Intel, but can target 11.0 for 184 # aarch64 since the earliest hardware was released alongside 11.0. 185 # For local builds, we want to target 10.15 regardless of the 186 # underlying platform to catch any errors or warnings that wouldn't 187 # show up when targeting 11.0, since these would later show up on 188 # CI for Intel builds. 189 (lambda t, d: "11.0" if (t.cpu == "aarch64" and not d) else "10.15"), 190 help="Set the minimum MacOS version needed at runtime{|}", 191 ) 192 193 @depends_if("--enable-macos-target", developer_options) 194 def macos_target(value, _): 195 return value[0] 196 197 198 @imports("plistlib") 199 @imports(_from="__builtin__", _import="open") 200 @imports(_from="__builtin__", _import="Exception") 201 def get_sdk_settings(sdk): 202 with open(os.path.join(sdk, "SDKSettings.plist"), "rb") as plist: 203 obj = plistlib.load(plist) 204 if not obj: 205 raise Exception( 206 "Error parsing SDKSettings.plist in the SDK directory: %s" % sdk 207 ) 208 return obj 209 210 211 @imports(_from="__builtin__", _import="Exception") 212 def get_sdk_version_from_settings(sdk, settings): 213 if "Version" not in settings: 214 raise Exception( 215 "Error finding Version information in SDKSettings.plist from the SDK: %s" 216 % sdk 217 ) 218 return Version(settings["Version"]) 219 220 221 def get_sdk_version(sdk): 222 return get_sdk_version_from_settings(sdk, get_sdk_settings(sdk)) 223 224 225 with only_when(host_is_osx | target_is_osx): 226 # MacOS SDK 227 # ========= 228 option( 229 "--with-macos-sdk", 230 env="MACOS_SDK_DIR", 231 nargs=1, 232 help="Location of platform SDK to use", 233 ) 234 235 def mac_sdk_min_version(): 236 return "26.2" 237 238 @depends( 239 "--with-macos-sdk", 240 host, 241 bootstrap_path( 242 "MacOSX{}.sdk".format(mac_sdk_min_version()), 243 when=depends("--with-macos-sdk")(lambda x: not x), 244 allow_failure=True, 245 ), 246 ) 247 @imports(_from="__builtin__", _import="Exception") 248 @imports(_from="os.path", _import="isdir") 249 @imports(_from="os", _import="listdir") 250 def macos_sdk(sdk, host, bootstrapped): 251 if bootstrapped: 252 sdk = [bootstrapped] 253 if sdk: 254 sdk = sdk[0] 255 try: 256 version = get_sdk_version(sdk) 257 except Exception as e: 258 die(e) 259 elif host.os == "OSX": 260 sdk = check_cmd_output( 261 "xcrun", "--show-sdk-path", onerror=lambda: "" 262 ).rstrip() 263 if not sdk: 264 die( 265 "Could not find the macOS SDK. Please use --with-macos-sdk to give " 266 "the path to a macOS SDK." 267 ) 268 # Scan the parent directory xcrun returns for the most recent SDK. 269 sdk_dir = os.path.dirname(sdk) 270 versions = [] 271 for d in listdir(sdk_dir): 272 if d.lower().startswith("macos"): 273 try: 274 sdk = os.path.join(sdk_dir, d) 275 versions.append((get_sdk_version(sdk), sdk)) 276 except Exception: 277 pass 278 version, sdk = max(versions) 279 else: 280 die( 281 "Need a macOS SDK when targeting macOS. Please use --with-macos-sdk " 282 "to give the path to a macOS SDK." 283 ) 284 285 if not isdir(sdk): 286 die( 287 "SDK not found in %s. When using --with-macos-sdk, you must specify a " 288 "valid SDK. SDKs are installed when the optional cross-development " 289 "tools are selected during the Xcode/Developer Tools installation." 290 % sdk 291 ) 292 if version < Version(mac_sdk_min_version()): 293 die( 294 'SDK version "%s" is too old. Please upgrade to at least %s. Try ' 295 "updating your system Xcode." % (version, mac_sdk_min_version()) 296 ) 297 return sdk 298 299 set_config("MACOS_SDK_DIR", macos_sdk) 300 301 302 with only_when(target_is_ios): 303 # iOS deployment target version 304 # ============================================================== 305 # This needs to happen before any compilation test is done. 306 307 option( 308 "--enable-ios-target", 309 env="IPHONEOS_DEPLOYMENT_TARGET", 310 nargs=1, 311 default="18.4", 312 help="Set the minimum iOS version needed at runtime", 313 ) 314 315 @depends_if("--enable-ios-target") 316 def ios_target(value): 317 return value[0] 318 319 320 with only_when(target_is_ios): 321 # iOS SDK 322 # ======= 323 option( 324 "--with-ios-sdk", 325 env="IPHONEOS_SDK_DIR", 326 nargs=1, 327 help="Location of platform SDK to use", 328 ) 329 330 @depends(target) 331 def target_is_ios_simulator(target): 332 # x86_64-apple-ios is simulator 333 # aarch64-apple-ios is iphone 334 # aarch64-apple-ios-sim is simulator 335 return target.cpu == "x86_64" or target.raw_os == "ios-sim" 336 337 set_config("IPHONEOS_IS_SIMULATOR", target_is_ios_simulator) 338 339 def ios_sdk_min_version(): 340 return "18.4" 341 342 @depends(target_is_ios_simulator) 343 def ios_sdk_name(target_is_ios_simulator): 344 return "iPhone{}{}.sdk".format( 345 "Simulator" if target_is_ios_simulator else "OS", 346 ios_sdk_min_version(), 347 ) 348 349 @depends( 350 "--with-ios-sdk", 351 host, 352 target, 353 target_is_ios_simulator, 354 bootstrap_path(ios_sdk_name, when=depends("--with-ios-sdk")(lambda x: not x)), 355 ) 356 @imports(_from="__builtin__", _import="Exception") 357 @imports(_from="os.path", _import="isdir") 358 @imports(_from="os", _import="listdir") 359 def ios_sdk(sdk, host, target, target_is_ios_simulator, bootstrapped): 360 if bootstrapped: 361 sdk = [bootstrapped] 362 sdk_name = "iphonesimulator" if target_is_ios_simulator else "iphoneos" 363 if sdk: 364 sdk = sdk[0] 365 try: 366 settings = get_sdk_settings(sdk) 367 version = get_sdk_version_from_settings(sdk, settings) 368 except Exception as e: 369 die(e) 370 elif host.os == "OSX": 371 sdk = check_cmd_output( 372 "xcrun", "--show-sdk-path", "--sdk", sdk_name, onerror=lambda: "" 373 ).rstrip() 374 if not sdk: 375 die( 376 "Could not find the iOS SDK. Please use --with-ios-sdk to give " 377 "the path to a iOS SDK." 378 ) 379 # Scan the parent directory xcrun returns for the most recent SDK. 380 sdk_dir = os.path.dirname(sdk) 381 versions = [] 382 for d in listdir(sdk_dir): 383 if d.lower().startswith(sdk_name): 384 try: 385 sdk = os.path.join(sdk_dir, d) 386 settings = get_sdk_settings(sdk) 387 version = get_sdk_version_from_settings(sdk, settings) 388 versions.append((version, sdk, settings)) 389 except Exception: 390 pass 391 version, sdk, settings = max(versions) 392 else: 393 die( 394 "Need an iOS SDK when targeting iOS. Please use --with-ios-sdk " 395 "to give the path to a iOS SDK." 396 ) 397 398 if not isdir(sdk): 399 die( 400 "SDK not found in %s. When using --with-ios-sdk, you must specify a " 401 "valid SDK. SDKs are installed when the optional cross-development " 402 "tools are selected during the Xcode installation." % sdk 403 ) 404 405 supported_targets = settings.get("SupportedTargets") 406 if supported_targets: 407 supported_archs = supported_targets.get(sdk_name, {}).get("Archs", []) 408 cpu = { 409 "aarch64": "arm64", 410 }.get(target.cpu, str(target.cpu)) 411 if cpu not in supported_archs: 412 die("The SDK in %s does not support target %s" % (sdk, target.alias)) 413 else: 414 log.warning( 415 "Cannot check whether the iOS SDK is for the right target, " 416 "assuming it is." 417 ) 418 if version < Version(ios_sdk_min_version()): 419 die( 420 'SDK version "%s" is too old. Please upgrade to at least %s. Try ' 421 "updating your system Xcode." % (version, ios_sdk_min_version()) 422 ) 423 return sdk 424 425 set_config("IPHONEOS_SDK_DIR", ios_sdk) 426 427 428 # GC rooting and hazard analysis. 429 # ============================================================== 430 option(env="MOZ_HAZARD", help="Build for the GC rooting hazard analysis") 431 432 433 @depends("MOZ_HAZARD") 434 def hazard_analysis(value): 435 if value: 436 return True 437 438 439 # Cross-compilation related things. 440 # ============================================================== 441 option( 442 "--with-toolchain-prefix", 443 env="TOOLCHAIN_PREFIX", 444 nargs=1, 445 help="Prefix for the target toolchain", 446 ) 447 448 449 @depends("--with-toolchain-prefix", host, target, cross_compiling) 450 def toolchain_prefix(value, host, target, cross_compiling): 451 if value: 452 return tuple(value) 453 # We don't want a toolchain prefix by default when building on mac for mac. 454 if cross_compiling and not (target.os == "OSX" and host.os == "OSX"): 455 return ("%s-" % target.toolchain, "%s-" % target.alias) 456 457 458 # Compilers 459 # ============================================================== 460 include("compilers-util.configure") 461 462 463 def try_preprocess( 464 configure_cache, compiler, language, source, onerror=None, wrapper=[] 465 ): 466 return try_invoke_compiler( 467 configure_cache, compiler, language, source, ["-E"], onerror, wrapper 468 ) 469 470 471 @imports(_from="mozbuild.configure.constants", _import="CompilerType") 472 @imports(_from="mozbuild.configure.constants", _import="CPU_preprocessor_checks") 473 @imports(_from="mozbuild.configure.constants", _import="kernel_preprocessor_checks") 474 @imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks") 475 @imports(_from="textwrap", _import="dedent") 476 @imports(_from="__builtin__", _import="Exception") 477 def get_compiler_info(configure_cache, compiler, language): 478 """Returns information about the given `compiler` (command line in the 479 form of a list or tuple), in the given `language`. 480 481 The returned information includes: 482 - the compiler type (clang-cl, clang or gcc) 483 - the compiler version 484 - the compiler supported language 485 - the compiler supported language version 486 """ 487 # Xcode clang versions are different from the underlying llvm version (they 488 # instead are aligned with the Xcode version). Fortunately, we can tell 489 # apart plain clang from Xcode clang, and convert the Xcode clang version 490 # into the more or less corresponding plain clang version. 491 check = dedent( 492 """\ 493 #if defined(_MSC_VER) && defined(__clang__) && defined(_MT) 494 %COMPILER "clang-cl" 495 %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__ 496 #elif defined(__clang__) 497 %COMPILER "clang" 498 %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__ 499 # ifdef __apple_build_version__ 500 %XCODE 1 501 # endif 502 #elif defined(__GNUC__) && !defined(__MINGW32__) 503 %COMPILER "gcc" 504 %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__ 505 #endif 506 507 #if __cplusplus 508 %cplusplus __cplusplus 509 #elif __STDC_VERSION__ 510 %STDC_VERSION __STDC_VERSION__ 511 #endif 512 """ 513 ) 514 515 # While we're doing some preprocessing, we might as well do some more 516 # preprocessor-based tests at the same time, to check the toolchain 517 # matches what we want. 518 for name, preprocessor_checks in ( 519 ("CPU", CPU_preprocessor_checks), 520 ("KERNEL", kernel_preprocessor_checks), 521 ("OS", OS_preprocessor_checks), 522 ): 523 for n, (value, condition) in enumerate(preprocessor_checks.items()): 524 check += dedent( 525 """\ 526 #%(if)s %(condition)s 527 %%%(name)s "%(value)s" 528 """ 529 % { 530 "if": "elif" if n else "if", 531 "condition": condition, 532 "name": name, 533 "value": value, 534 } 535 ) 536 check += "#endif\n" 537 538 # Also check for endianness. The advantage of living in modern times is 539 # that all the modern compilers we support now have __BYTE_ORDER__ defined 540 # by the preprocessor. 541 check += dedent( 542 """\ 543 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 544 %ENDIANNESS "little" 545 #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 546 %ENDIANNESS "big" 547 #endif 548 """ 549 ) 550 551 result = try_preprocess(configure_cache, compiler, language, check) 552 553 if not result: 554 raise FatalCheckError("Unknown compiler or compiler not supported.") 555 556 # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may 557 # have non-ASCII characters. Treat the output as bytearray. 558 data = {} 559 for line in result.splitlines(): 560 if line.startswith("%"): 561 k, _, v = line.partition(" ") 562 k = k.lstrip("%") 563 data[k] = v.replace(" ", "").lstrip('"').rstrip('"') 564 log.debug("%s = %s", k, data[k]) 565 566 try: 567 type = CompilerType(data["COMPILER"]) 568 except Exception: 569 raise FatalCheckError("Unknown compiler or compiler not supported.") 570 571 cplusplus = int(data.get("cplusplus", "0L").rstrip("L")) 572 stdc_version = int(data.get("STDC_VERSION", "0L").rstrip("L")) 573 574 version = data.get("VERSION") 575 if version: 576 version = Version(version) 577 if data.get("XCODE"): 578 # Derived from https://en.wikipedia.org/wiki/Xcode#Toolchain_versions 579 # with enough granularity for major.minor version checks further 580 # down the line. `version` is compared against the version in the 581 # "clang version string" column, and the resolved version is the 582 # value from the "LLVM version" column. 583 if version < "9.1": 584 version = Version("4.0.0.or.less") 585 elif version < "10.0": 586 version = Version("5.0.2") 587 elif version < "10.0.1": 588 version = Version("6.0.1") 589 elif version < "11.0": 590 version = Version("7.0.0") 591 elif version < "11.0.3": 592 version = Version("8.0.0") 593 elif version < "12.0": 594 version = Version("9.0.0") 595 elif version < "12.0.5": 596 version = Version("10.0.0") 597 elif version < "13.0": 598 version = Version("11.1.0") 599 elif version < "13.0.1": 600 version = Version("12.0.0") 601 elif version < "14.0": 602 version = Version("13.0.0") 603 elif version < "14.3": 604 version = Version("14.0.0") 605 elif version < "15.0": 606 version = Version("15.0.0") 607 elif version < "16.0": 608 version = Version("16.0.0") 609 elif version < "17.0": 610 version = Version("17.0.6") 611 else: 612 version = Version("19.1.4.or.more") 613 614 return namespace( 615 type=type, 616 version=version, 617 cpu=data.get("CPU"), 618 kernel=data.get("KERNEL"), 619 endianness=data.get("ENDIANNESS"), 620 os=data.get("OS"), 621 language="C++" if cplusplus else "C", 622 language_version=cplusplus if cplusplus else stdc_version, 623 xcode=bool(data.get("XCODE")), 624 ) 625 626 627 def same_arch_different_bits(): 628 return ( 629 ("x86", "x86_64"), 630 ("ppc", "ppc64"), 631 ("sparc", "sparc64"), 632 ) 633 634 635 @imports(_from="mozshellutil", _import="quote") 636 @imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks") 637 def check_compiler(configure_cache, compiler, language, target, android_version): 638 info = get_compiler_info(configure_cache, compiler, language) 639 640 flags = [] 641 642 # Check language standards 643 # -------------------------------------------------------------------- 644 if language != info.language: 645 raise FatalCheckError( 646 "`%s` is not a %s compiler." % (quote(*compiler), language) 647 ) 648 649 # Note: We do a strict version check because there sometimes are backwards 650 # incompatible changes in the standard, and not all code that compiles as 651 # C17 compiles as C23. 652 c17_version = 201710 653 if info.language == "C" and info.language_version != c17_version: 654 if info.type == "clang-cl": 655 flags.append("-Xclang") 656 flags.append("-std=gnu17") 657 658 cxx20_version = 202002 659 if info.language == "C++" and info.language_version != cxx20_version: 660 if info.type == "clang-cl": 661 flags.append("-std:c++20") 662 else: 663 flags.append("-std=gnu++20") 664 665 # gcc 10 defines C++20's __cplusplus as 201709L instead of 202002L. 666 # Redefine as C++20's final vesion for gcc 10 so modern C++20 code's 667 # __cplusplus version checks work as expected. 668 if info.type == "gcc" and info.version < Version("11.0.0"): 669 flags.append("-U__cplusplus") 670 flags.append("-D__cplusplus=202002L") 671 672 # Check compiler target 673 # -------------------------------------------------------------------- 674 has_target = False 675 if target.os == "Android" and android_version: 676 # This makes clang define __ANDROID_API__ and use versioned library 677 # directories from the NDK. 678 toolchain = "%s%d" % (target.toolchain, android_version) 679 else: 680 toolchain = target.toolchain 681 682 if info.type == "clang": 683 # Add the target explicitly when the target is aarch64 macosx, because 684 # the Xcode clang target is named differently, and we need to work around 685 # https://github.com/rust-lang/rust-bindgen/issues/1871 and 686 # https://github.com/alexcrichton/cc-rs/issues/542 so we always want 687 # the target on the command line, even if the compiler would default to 688 # that. 689 if info.xcode and target.os == "OSX" and target.cpu == "aarch64": 690 if "--target=arm64-apple-darwin" not in compiler: 691 flags.append("--target=arm64-apple-darwin") 692 has_target = True 693 elif target.os == "iOS": 694 target_flag = "--target=%s" % toolchain 695 if target_flag not in compiler: 696 flags.append(target_flag) 697 has_target = True 698 elif ( 699 not info.kernel 700 or info.kernel != target.kernel 701 or not info.endianness 702 or info.endianness != target.endianness 703 ): 704 flags.append("--target=%s" % toolchain) 705 has_target = True 706 707 # Add target flag when there is an OS mismatch (e.g. building for Android on 708 # Linux). However, only do this if the target OS is in our whitelist, to 709 # keep things the same on other platforms. 710 elif target.os in OS_preprocessor_checks and ( 711 not info.os or info.os != target.os 712 ): 713 flags.append("--target=%s" % toolchain) 714 has_target = True 715 716 if not has_target and (not info.cpu or info.cpu != target.cpu): 717 same_arch = same_arch_different_bits() 718 if (target.cpu, info.cpu) in same_arch: 719 flags.append("-m32") 720 elif (info.cpu, target.cpu) in same_arch: 721 flags.append("-m64") 722 elif info.type == "clang-cl" and target.cpu == "aarch64": 723 flags.append("--target=%s" % toolchain) 724 elif info.type == "clang": 725 flags.append("--target=%s" % toolchain) 726 727 return namespace( 728 type=info.type, 729 version=info.version, 730 target_cpu=info.cpu, 731 target_kernel=info.kernel, 732 target_endianness=info.endianness, 733 target_os=info.os, 734 flags=flags, 735 ) 736 737 738 @imports(_from="__builtin__", _import="open") 739 @imports(_from="mozfile", _import="json") 740 @imports("os") 741 def get_vc_paths(host, topsrcdir): 742 def vswhere(args): 743 program_files = os.environ.get("PROGRAMFILES(X86)") or os.environ.get( 744 "PROGRAMFILES" 745 ) 746 if not program_files: 747 return [] 748 vswhere = os.path.join( 749 program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe" 750 ) 751 if not os.path.exists(vswhere): 752 return [] 753 return json.loads(check_cmd_output(vswhere, "-format", "json", *args)) 754 755 variant = "arm64" if host.cpu == "aarch64" else "x86.x64" 756 for install in vswhere( 757 [ 758 "-products", 759 "*", 760 "-requires", 761 f"Microsoft.VisualStudio.Component.VC.Tools.{variant}", 762 ] 763 ): 764 path = install["installationPath"] 765 tools_version = ( 766 open( 767 os.path.join( 768 path, r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" 769 ), 770 "r", 771 ) 772 .read() 773 .strip() 774 ) 775 tools_path = os.path.join(path, r"VC\Tools\MSVC", tools_version) 776 yield (Version(install["installationVersion"]), tools_path) 777 778 779 @depends(target, host) 780 def is_windows(target, host): 781 return host.kernel == "WINNT" or target.kernel == "WINNT" 782 783 784 # Calling this a sysroot is a little weird, but it's the terminology clang went 785 # with with its -winsysroot flag. 786 option( 787 env="WINSYSROOT", 788 nargs=1, 789 when=is_windows, 790 help='Path to a Windows "sysroot" (directory containing MSVC, SDKs)', 791 ) 792 793 794 @depends( 795 "WINSYSROOT", 796 bootstrap_path( 797 "vs", 798 when=depends("WINSYSROOT", when=is_windows)(lambda x: not x), 799 ), 800 when=is_windows, 801 ) 802 def winsysroot(winsysroot, bootstrapped): 803 if bootstrapped: 804 return bootstrapped 805 if winsysroot: 806 return winsysroot[0] 807 808 809 option( 810 env="VC_PATH", 811 nargs=1, 812 when=is_windows, 813 help="Path to the Microsoft Visual C/C++ compiler", 814 ) 815 816 817 @depends( 818 host, 819 build_environment, 820 "VC_PATH", 821 winsysroot, 822 when=is_windows, 823 ) 824 @imports("os") 825 @imports(_from="operator", _import="itemgetter") 826 def vc_compiler_paths_for_version(host, env, vc_path, winsysroot): 827 if winsysroot: 828 if vc_path: 829 die("WINSYSROOT and VC_PATH cannot be set together.") 830 base_vc_path = os.path.join(winsysroot, "VC", "Tools", "MSVC") 831 versions = os.listdir(base_vc_path) 832 vc_path = [os.path.join(base_vc_path, str(max(Version(v) for v in versions)))] 833 if vc_path: 834 # Use an arbitrary version, it doesn't matter. 835 all_versions = [(Version("15"), vc_path[0])] 836 elif host.kernel != "WINNT": 837 # Don't try to do anything when VC_PATH is not set on cross-compiles. 838 return 839 else: 840 all_versions = sorted(get_vc_paths(host, env.topsrcdir), key=itemgetter(0)) 841 if not all_versions: 842 return 843 # Choose the newest version. 844 path = all_versions[-1][1] 845 host_dir = { 846 "x86_64": "Hostx64", 847 "x86": "Hostx86", 848 "aarch64": "Hostarm64", 849 }.get(host.cpu) 850 if host_dir: 851 path = os.path.join(path, "bin", host_dir) 852 return { 853 "x64": [os.path.join(path, "x64")], 854 # The cross toolchains require DLLs from the native x64 toolchain. 855 "x86": [os.path.join(path, "x86"), os.path.join(path, "x64")], 856 "arm64": [os.path.join(path, "arm64"), os.path.join(path, "x64")], 857 } 858 859 860 @depends(target, host, vc_compiler_paths_for_version, when=is_windows) 861 def vc_compiler_path(target, host, paths): 862 cpu = target.cpu if target.os == "WINNT" else host.cpu 863 vc_target = { 864 "x86": "x86", 865 "x86_64": "x64", 866 "arm": "arm", 867 "aarch64": "arm64", 868 }.get(cpu) 869 if not paths: 870 return 871 return paths.get(vc_target) 872 873 874 @depends(vc_compiler_path, original_path) 875 @imports("os") 876 @imports(_from="os", _import="environ") 877 def vc_toolchain_search_path(vc_compiler_path, original_path): 878 result = list(original_path) 879 880 if vc_compiler_path: 881 # The second item, if there is one, is necessary to have in $PATH for 882 # Windows to load the required DLLs from there. 883 if len(vc_compiler_path) > 1: 884 environ["PATH"] = os.pathsep.join(result + vc_compiler_path[1:]) 885 886 # The first item is where the programs are going to be 887 result.append(vc_compiler_path[0]) 888 889 return result 890 891 892 @depends_if(vc_compiler_path, when=is_windows) 893 def vc_compiler_version(vc_compiler_path): 894 version = Version( 895 os.path.basename( 896 os.path.dirname(os.path.dirname(os.path.dirname(vc_compiler_path[0]))) 897 ) 898 ) 899 # MSVC path with version 14.x is actually version 19.x 900 if version.major == 14: 901 return Version(f"19.{version.minor}") 902 903 904 @depends_if(vc_compiler_version) 905 def msvs_version(vc_compiler_version): 906 # clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to 907 # be set for GYP on Windows. 908 if vc_compiler_version >= Version("19.30"): 909 return "2022" 910 configure_error("Only Visual Studio 2022 or newer are supported") 911 912 return "" 913 914 915 set_config("MSVS_VERSION", msvs_version) 916 917 with only_when(target_is_windows): 918 919 @depends(target) 920 def dxc_task_name(target): 921 return "dxc-" + target.cpu + "-pc-windows-msvc" 922 923 @depends_if(bootstrap_path(dxc_task_name)) 924 def dxc_dll_path(bootstrap): 925 return os.path.join(bootstrap, "dxcompiler.dll") 926 927 928 clang_search_path = bootstrap_search_path("clang/bin") 929 930 931 @depends( 932 bootstrap_path("rustc/bin", when=moz_automation), 933 original_path, 934 ) 935 @imports("os") 936 @imports(_from="os", _import="environ") 937 def rust_search_path(rust_path, original_path): 938 result = [] 939 # The order we search for a rust compiler is: 940 # - bootstrap directory (only used by CI at the moment) 941 # - rustup install directory ($CARGO_HOME/bin) 942 # - $PATH 943 cargo_home = environ.get("CARGO_HOME", "") 944 if cargo_home: 945 cargo_home = os.path.abspath(cargo_home) 946 else: 947 cargo_home = os.path.expanduser(os.path.join("~", ".cargo")) 948 rustup_path = os.path.join(cargo_home, "bin") 949 if rust_path: 950 result.append(rust_path) 951 result.append(rustup_path) 952 result.extend(original_path) 953 return result 954 955 956 # Prepend the mozilla-build msys2 path, since otherwise we can get mismatched 957 # cygwin dll errors during configure if we get called from another msys2 958 # environment, see bug 1801826. 959 @depends( 960 mozillabuild_bin_paths, clang_search_path, rust_search_path, target, original_path 961 ) 962 @imports("os") 963 def altered_path( 964 mozillabuild_bin_paths, clang_search_path, rust_search_path, target, original_path 965 ): 966 altered_path = mozillabuild_bin_paths 967 if target.kernel == "Darwin": 968 # The rust compiler wants to execute dsymutil, but it does so in a 969 # non-configurable way (https://github.com/rust-lang/rust/issues/52728) 970 # so we add the clang path. 971 path = clang_search_path 972 else: 973 path = original_path 974 # cargo needs the rust search path to find cargo-$subcommand. 975 path += rust_search_path 976 for p in path: 977 if p not in altered_path: 978 altered_path.append(p) 979 return os.pathsep.join(altered_path) 980 981 982 set_config("PATH", altered_path) 983 984 985 # Compiler wrappers 986 # ============================================================== 987 option( 988 "--with-compiler-wrapper", 989 env="COMPILER_WRAPPER", 990 nargs=1, 991 help="Enable compiling with wrappers such as distcc and ccache", 992 ) 993 994 option("--with-ccache", env="CCACHE", nargs="?", help="Enable compiling with ccache") 995 996 997 @depends_if("--with-ccache") 998 def ccache(value): 999 if len(value): 1000 return value 1001 # If --with-ccache was given without an explicit value, we default to 1002 # 'ccache'. 1003 return "ccache" 1004 1005 1006 ccache = check_prog( 1007 "CCACHE", 1008 progs=(), 1009 input=ccache, 1010 paths=bootstrap_search_path( 1011 "sccache", when=depends("CCACHE")(lambda c: len(c) and c[0] == "sccache") 1012 ), 1013 allow_missing=True, 1014 ) 1015 1016 option(env="CCACHE_PREFIX", nargs=1, help="Compiler prefix to use when using ccache") 1017 1018 ccache_prefix = depends_if("CCACHE_PREFIX")(lambda prefix: prefix[0]) 1019 set_config("CCACHE_PREFIX", ccache_prefix) 1020 1021 # Distinguish ccache from sccache. 1022 1023 1024 @depends_if(ccache) 1025 def ccache_is_sccache(ccache): 1026 return check_cmd_output(ccache, "--version").startswith("sccache") 1027 1028 1029 @depends(ccache, ccache_is_sccache) 1030 def using_ccache(ccache, ccache_is_sccache): 1031 return ccache and not ccache_is_sccache 1032 1033 1034 @depends_if(ccache, ccache_is_sccache) 1035 def using_sccache(ccache, ccache_is_sccache): 1036 return ccache and ccache_is_sccache 1037 1038 1039 option(env="RUSTC_WRAPPER", nargs=1, help="Wrap rust compilation with given tool") 1040 1041 1042 @depends(ccache, ccache_is_sccache, "RUSTC_WRAPPER") 1043 @imports(_from="textwrap", _import="dedent") 1044 @imports("os") 1045 def check_sccache_version(ccache, ccache_is_sccache, rustc_wrapper): 1046 sccache_min_version = Version("0.2.13") 1047 1048 def check_version(path): 1049 out = check_cmd_output(path, "--version") 1050 version = Version(out.rstrip().split()[-1]) 1051 if version < sccache_min_version: 1052 die( 1053 dedent( 1054 """\ 1055 sccache %s or later is required. sccache in use at %s has 1056 version %s. 1057 1058 Please upgrade or acquire a new version with |./mach bootstrap|. 1059 """ 1060 ), 1061 sccache_min_version, 1062 path, 1063 version, 1064 ) 1065 1066 if ccache and ccache_is_sccache: 1067 check_version(ccache) 1068 1069 if rustc_wrapper and ( 1070 os.path.splitext(os.path.basename(rustc_wrapper[0]))[0].lower() == "sccache" 1071 ): 1072 check_version(rustc_wrapper[0]) 1073 1074 1075 set_config("MOZ_USING_CCACHE", using_ccache) 1076 set_config("MOZ_USING_SCCACHE", using_sccache) 1077 1078 option(env="SCCACHE_VERBOSE_STATS", help="Print verbose sccache stats after build") 1079 1080 1081 @depends(using_sccache, "SCCACHE_VERBOSE_STATS") 1082 def sccache_verbose_stats(using_sccache, verbose_stats): 1083 return using_sccache and bool(verbose_stats) 1084 1085 1086 set_config("SCCACHE_VERBOSE_STATS", sccache_verbose_stats) 1087 1088 1089 @depends("--with-compiler-wrapper", ccache) 1090 @imports(_from="mozshellutil", _import="split", _as="shell_split") 1091 def compiler_wrapper(wrapper, ccache): 1092 if wrapper: 1093 raw_wrapper = wrapper[0] 1094 wrapper = shell_split(raw_wrapper) 1095 wrapper_program = find_program(wrapper[0]) 1096 if not wrapper_program: 1097 die( 1098 "Cannot find `%s` from the given compiler wrapper `%s`", 1099 wrapper[0], 1100 raw_wrapper, 1101 ) 1102 wrapper[0] = wrapper_program 1103 1104 if ccache: 1105 if wrapper: 1106 return tuple([ccache] + wrapper) 1107 else: 1108 return (ccache,) 1109 elif wrapper: 1110 return tuple(wrapper) 1111 1112 1113 @dependable 1114 def wasm(): 1115 return split_triplet("wasm32-wasi", allow_wasi=True) 1116 1117 1118 @template 1119 def default_c_compilers(host_or_target, other_c_compiler=None): 1120 """Template defining the set of default C compilers for the host and 1121 target platforms. 1122 `host_or_target` is either `host` or `target` (the @depends functions 1123 from init.configure. 1124 `other_c_compiler` is the `target` C compiler when `host_or_target` is `host`. 1125 """ 1126 assert host_or_target in {host, target, wasm} 1127 1128 other_c_compiler = () if other_c_compiler is None else (other_c_compiler,) 1129 1130 @depends(host_or_target, target, toolchain_prefix, *other_c_compiler) 1131 def default_c_compilers( 1132 host_or_target, target, toolchain_prefix, *other_c_compiler 1133 ): 1134 if host_or_target.kernel == "WINNT": 1135 if host_or_target.abi: 1136 if host_or_target.abi == "msvc": 1137 supported = types = ("clang-cl",) 1138 elif host_or_target.abi == "mingw": 1139 supported = types = ("clang",) 1140 else: 1141 supported = types = ("clang-cl", "clang") 1142 elif host_or_target.kernel == "Darwin": 1143 types = ("clang",) 1144 supported = ("clang", "gcc") 1145 elif host_or_target.kernel == "WASI": 1146 supported = types = ("clang",) 1147 else: 1148 supported = types = ("clang", "gcc") 1149 1150 info = other_c_compiler[0] if other_c_compiler else None 1151 1152 if ( 1153 info 1154 and info.type == "clang-cl" 1155 and "clang-cl" not in supported 1156 and "clang" in supported 1157 ): 1158 dir_path = os.path.dirname(info.compiler) 1159 basename = os.path.basename(info.compiler) 1160 clang_name = basename.replace("clang-cl", "clang") 1161 info = namespace(compiler=os.path.join(dir_path, clang_name), type="clang") 1162 1163 if info and info.type in supported: 1164 # When getting default C compilers for the host, we prioritize the 1165 # same compiler as the target C compiler. 1166 prioritized = info.compiler 1167 if info.type == "gcc": 1168 same_arch = same_arch_different_bits() 1169 if ( 1170 target.cpu != host_or_target.cpu 1171 and (target.cpu, host_or_target.cpu) not in same_arch 1172 and (host_or_target.cpu, target.cpu) not in same_arch 1173 ): 1174 # If the target C compiler is GCC, and it can't be used with 1175 # -m32/-m64 for the host, it's probably toolchain-prefixed, 1176 # so we prioritize a raw 'gcc' instead. 1177 prioritized = info.type 1178 if target.os != "WINNT" and host_or_target.os == "WINNT": 1179 # When cross-compiling on Windows, don't prioritize. We'll fallback 1180 # to checking for clang-cl first. 1181 pass 1182 else: 1183 types = [prioritized] + [t for t in types if t != info.type] 1184 1185 gcc = ("gcc",) 1186 if toolchain_prefix and host_or_target is target: 1187 gcc = tuple("%sgcc" % p for p in toolchain_prefix) + gcc 1188 1189 result = [] 1190 for type in types: 1191 if type == "gcc": 1192 result.extend(gcc) 1193 else: 1194 result.append(type) 1195 1196 return tuple(result) 1197 1198 return default_c_compilers 1199 1200 1201 @template 1202 def default_cxx_compilers(c_compiler, other_c_compiler=None, other_cxx_compiler=None): 1203 """Template defining the set of default C++ compilers for the host and 1204 target platforms. 1205 `c_compiler` is the @depends function returning a Compiler instance for 1206 the desired platform. 1207 1208 Because the build system expects the C and C++ compilers to be from the 1209 same compiler suite, we derive the default C++ compilers from the C 1210 compiler that was found if none was provided. 1211 1212 We also factor in the target C++ compiler when getting the default host 1213 C++ compiler, using the target C++ compiler if the host and target C 1214 compilers are the same. 1215 """ 1216 1217 assert (other_c_compiler is None) == (other_cxx_compiler is None) 1218 if other_c_compiler is not None: 1219 other_compilers = (other_c_compiler, other_cxx_compiler) 1220 else: 1221 other_compilers = () 1222 1223 @depends(c_compiler, *other_compilers) 1224 def default_cxx_compilers(c_compiler, *other_compilers): 1225 if other_compilers: 1226 other_c_compiler, other_cxx_compiler = other_compilers 1227 if other_c_compiler.compiler == c_compiler.compiler: 1228 return (other_cxx_compiler.compiler,) 1229 1230 dir = os.path.dirname(c_compiler.compiler) 1231 file = os.path.basename(c_compiler.compiler) 1232 1233 if c_compiler.type == "gcc": 1234 return (os.path.join(dir, file.replace("gcc", "g++")),) 1235 1236 if c_compiler.type == "clang": 1237 return (os.path.join(dir, file.replace("clang", "clang++")),) 1238 1239 return (c_compiler.compiler,) 1240 1241 return default_cxx_compilers 1242 1243 1244 @template 1245 def provided_program(env_var, when=None): 1246 """Template handling cases where a program can be specified either as a 1247 path or as a path with applicable arguments. 1248 """ 1249 1250 @depends_if(env_var, when=when) 1251 @imports(_from="itertools", _import="takewhile") 1252 @imports(_from="mozshellutil", _import="split", _as="shell_split") 1253 def provided(cmd): 1254 # Assume the first dash-prefixed item (and any subsequent items) are 1255 # command-line options, the item before the dash-prefixed item is 1256 # the program we're looking for, and anything before that is a wrapper 1257 # of some kind (e.g. sccache). 1258 cmd = shell_split(cmd[0]) 1259 1260 without_flags = list(takewhile(lambda x: not x.startswith("-"), cmd)) 1261 1262 return namespace( 1263 wrapper=without_flags[:-1], 1264 program=without_flags[-1], 1265 flags=cmd[len(without_flags) :], 1266 ) 1267 1268 return provided 1269 1270 1271 @template 1272 def sysroot(host_or_target, target_sysroot=None): 1273 assert target_sysroot or host_or_target is target 1274 bootstrap_target_when = target_is_linux_or_wasi 1275 if host_or_target is host: 1276 host_or_target_str = "host" 1277 opt = "--with-host-sysroot" 1278 env = "HOST_SYSROOT" 1279 when = depends(host)(lambda h: h.kernel == "Linux") 1280 1281 # Only bootstrap a host sysroot when using a bootstrapped target sysroot 1282 # or when the target doesn't use a bootstrapped sysroot in the first place. 1283 @depends(when, bootstrap_target_when, target_sysroot.bootstrapped) 1284 def bootstrap_when(when, bootstrap_target_when, bootstrapped): 1285 return when and (bootstrapped or not bootstrap_target_when) 1286 1287 else: 1288 assert host_or_target is target 1289 host_or_target_str = "target" 1290 opt = "--with-sysroot" 1291 env = "SYSROOT" 1292 when = target_is_linux_or_wasi 1293 bootstrap_when = bootstrap_target_when 1294 1295 option( 1296 opt, 1297 env=env, 1298 nargs=1, 1299 when=when, 1300 help="Use the given sysroot directory for %s build" % host_or_target_str, 1301 ) 1302 1303 sysroot_input = depends(opt, when=when)(lambda x: x) 1304 bootstrap_sysroot = depends(bootstrap_when, sysroot_input)( 1305 # Only bootstrap when no flag was explicitly given (either --with or --without) 1306 lambda bootstrap, input: bootstrap 1307 and not input 1308 and input.origin == "default" 1309 ) 1310 1311 @depends( 1312 sysroot_input, 1313 host_or_target, 1314 macos_sdk, 1315 ios_sdk, 1316 bootstrap_path( 1317 depends(host_or_target)(lambda t: "sysroot-{}".format(t.toolchain)), 1318 when=bootstrap_sysroot, 1319 ), 1320 ) 1321 @imports("os") 1322 def sysroot(sysroot_input, host_or_target, macos_sdk, ios_sdk, path): 1323 version = None 1324 if sysroot_input: 1325 path = sysroot_input[0] 1326 elif host_or_target.os == "OSX" and macos_sdk: 1327 path = macos_sdk 1328 elif host_or_target.os == "iOS" and ios_sdk: 1329 path = ios_sdk 1330 if path: 1331 # Find the version of libstdc++ headers in the sysroot 1332 include = os.path.join(path, "usr/include/c++") 1333 if os.path.isdir(include): 1334 with os.scandir(include) as d: 1335 version = max(Version(e.name) for e in d if e.is_dir()) 1336 log.info("Using %s sysroot in %s", host_or_target_str, path) 1337 return namespace( 1338 path=path, 1339 bootstrapped=bool(path and not sysroot_input), 1340 stdcxx_version=version, 1341 ) 1342 1343 return sysroot 1344 1345 1346 target_sysroot = sysroot(target) 1347 1348 1349 # Use `system_lib_option` instead of `option` for options that enable building 1350 # with a system library for which the development headers are not available in 1351 # the bootstrapped sysroots. 1352 @template 1353 def system_lib_option(name, *args, **kwargs): 1354 option(name, *args, **kwargs) 1355 1356 @depends( 1357 name, 1358 target_sysroot.bootstrapped, 1359 when=kwargs.get("when"), 1360 ) 1361 def no_system_lib_in_sysroot(value, bootstrapped): 1362 if bootstrapped and value: 1363 die( 1364 "%s is not supported with bootstrapped sysroot. " 1365 "Drop the option, or use --without-sysroot or --disable-bootstrap", 1366 value.format(name), 1367 ) 1368 1369 1370 host_sysroot = sysroot(host, target_sysroot) 1371 1372 1373 @template 1374 def multiarch_dir(host_or_target): 1375 sysroot = { 1376 host: host_sysroot, 1377 target: target_sysroot, 1378 }[host_or_target] 1379 1380 @depends(host_or_target, when=sysroot.path) 1381 def multiarch_dir(target): 1382 if target.cpu == "x86": 1383 # Turn e.g. i686-linux-gnu into i386-linux-gnu 1384 return target.toolchain.replace(target.raw_cpu, "i386") 1385 return target.toolchain 1386 1387 return multiarch_dir 1388 1389 1390 target_multiarch_dir = multiarch_dir(target) 1391 host_multiarch_dir = multiarch_dir(host) 1392 1393 1394 def minimum_gcc_version(): 1395 return Version("10.1.0") 1396 1397 1398 @template 1399 def compiler( 1400 language, 1401 host_or_target, 1402 c_compiler=None, 1403 other_compiler=None, 1404 other_c_compiler=None, 1405 ): 1406 """Template handling the generic base checks for the compiler for the 1407 given `language` on the given platform (`host_or_target`). 1408 `host_or_target` is either `host` or `target` (the @depends functions 1409 from init.configure. 1410 When the language is 'C++', `c_compiler` is the result of the `compiler` 1411 template for the language 'C' for the same `host_or_target`. 1412 When `host_or_target` is `host`, `other_compiler` is the result of the 1413 `compiler` template for the same `language` for `target`. 1414 When `host_or_target` is `host` and the language is 'C++', 1415 `other_c_compiler` is the result of the `compiler` template for the 1416 language 'C' for `target`. 1417 """ 1418 assert host_or_target in {host, target, wasm} 1419 assert language in ("C", "C++") 1420 assert language == "C" or c_compiler is not None 1421 assert host_or_target is target or other_compiler is not None 1422 assert language == "C" or host_or_target is target or other_c_compiler is not None 1423 1424 host_or_target_str = { 1425 host: "host", 1426 target: "target", 1427 wasm: "wasm", 1428 }[host_or_target] 1429 1430 sysroot = { 1431 host: host_sysroot, 1432 target: target_sysroot, 1433 wasm: dependable(lambda: namespace(path=None)), 1434 }[host_or_target] 1435 1436 multiarch_dir = { 1437 host: host_multiarch_dir, 1438 target: target_multiarch_dir, 1439 wasm: never, 1440 }[host_or_target] 1441 1442 var = { 1443 ("C", target): "CC", 1444 ("C++", target): "CXX", 1445 ("C", host): "HOST_CC", 1446 ("C++", host): "HOST_CXX", 1447 ("C", wasm): "WASM_CC", 1448 ("C++", wasm): "WASM_CXX", 1449 }[language, host_or_target] 1450 1451 default_compilers = { 1452 "C": lambda: default_c_compilers(host_or_target, other_compiler), 1453 "C++": lambda: default_cxx_compilers( 1454 c_compiler, other_c_compiler, other_compiler 1455 ), 1456 }[language]() 1457 1458 what = "the %s %s compiler" % (host_or_target_str, language) 1459 1460 option(env=var, nargs=1, help="Path to %s" % what) 1461 1462 # Handle the compiler given by the user through one of the CC/CXX/HOST_CC/ 1463 # HOST_CXX variables. 1464 provided_compiler = provided_program(var) 1465 1466 # Normally, we'd use `var` instead of `_var`, but the interaction with 1467 # old-configure complicates things, and for now, we a) can't take the plain 1468 # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let 1469 # old-configure AC_SUBST it (because it's autoconf doing it, not us) 1470 compiler = check_prog( 1471 "_%s" % var, 1472 what=what, 1473 progs=default_compilers, 1474 input=provided_compiler.program, 1475 paths=clang_search_path, 1476 ) 1477 1478 @depends( 1479 configure_cache, 1480 compiler, 1481 provided_compiler, 1482 compiler_wrapper, 1483 host_or_target, 1484 sysroot, 1485 macos_target, 1486 ios_target, 1487 android_version, 1488 vc_compiler_version, 1489 multiarch_dir, 1490 winsysroot, 1491 host, 1492 ) 1493 @checking("whether %s can be used" % what, lambda x: bool(x)) 1494 @imports(_from="mozshellutil", _import="quote") 1495 @imports("os") 1496 def valid_compiler( 1497 configure_cache, 1498 compiler, 1499 provided_compiler, 1500 compiler_wrapper, 1501 host_or_target, 1502 sysroot, 1503 macos_target, 1504 ios_target, 1505 android_version, 1506 vc_compiler_version, 1507 multiarch_dir, 1508 winsysroot, 1509 host, 1510 ): 1511 wrapper = list(compiler_wrapper or ()) 1512 flags = [] 1513 if sysroot.path: 1514 if host_or_target.kernel == "Darwin": 1515 # While --sysroot and -isysroot are roughly equivalent, when not using 1516 # -isysroot on mac, clang takes the SDKROOT environment variable into 1517 # consideration, which may be set by python and break things. 1518 flags.extend(("-isysroot", sysroot.path)) 1519 else: 1520 flags.extend(("--sysroot", sysroot.path)) 1521 if provided_compiler: 1522 wrapper.extend(provided_compiler.wrapper) 1523 flags.extend(provided_compiler.flags) 1524 1525 info = check_compiler( 1526 configure_cache, 1527 wrapper + [compiler] + flags, 1528 language, 1529 host_or_target, 1530 android_version, 1531 ) 1532 1533 if host_or_target.os == "OSX" and macos_target: 1534 flags.append("-mmacosx-version-min=%s" % macos_target) 1535 if host_or_target.os == "iOS" and ios_target: 1536 flags.append("-mios-version-min=%s" % ios_target) 1537 1538 # When not given an explicit compatibility version, clang-cl tries 1539 # to get one from MSVC, which might not even be the one used by the 1540 # build. And when it can't find one, its default might also not match 1541 # what the build is using. So if we were able to figure out the version 1542 # we're building with, explicitly use that. 1543 # This also means that, as a side effect, clang-cl will not try to find 1544 # MSVC, which saves a little overhead. 1545 if info.type == "clang-cl" and vc_compiler_version: 1546 flags.append(f"-fms-compatibility-version={vc_compiler_version}") 1547 1548 if info.type == "clang" and language == "C++" and host_or_target.os == "OSX": 1549 flags.append("-stdlib=libc++") 1550 1551 # Check that the additional flags we got are enough to not require any 1552 # more flags. If we get an exception, just ignore it; it's liable to be 1553 # invalid command-line flags, which means the compiler we're checking 1554 # doesn't support those command-line flags and will fail one or more of 1555 # the checks below. 1556 try: 1557 if info.flags: 1558 flags += info.flags 1559 info = check_compiler( 1560 configure_cache, 1561 wrapper + [compiler] + flags, 1562 language, 1563 host_or_target, 1564 android_version, 1565 ) 1566 except FatalCheckError: 1567 pass 1568 1569 if not info.target_cpu or info.target_cpu != host_or_target.cpu: 1570 raise FatalCheckError( 1571 "%s %s compiler target CPU (%s) does not match --%s CPU (%s)" 1572 % ( 1573 host_or_target_str.capitalize(), 1574 language, 1575 info.target_cpu or "unknown", 1576 host_or_target_str, 1577 host_or_target.raw_cpu, 1578 ) 1579 ) 1580 1581 if not info.target_kernel or (info.target_kernel != host_or_target.kernel): 1582 raise FatalCheckError( 1583 "%s %s compiler target kernel (%s) does not match --%s kernel (%s)" 1584 % ( 1585 host_or_target_str.capitalize(), 1586 language, 1587 info.target_kernel or "unknown", 1588 host_or_target_str, 1589 host_or_target.kernel, 1590 ) 1591 ) 1592 1593 if not info.target_endianness or ( 1594 info.target_endianness != host_or_target.endianness 1595 ): 1596 raise FatalCheckError( 1597 "%s %s compiler target endianness (%s) does not match --%s " 1598 "endianness (%s)" 1599 % ( 1600 host_or_target_str.capitalize(), 1601 language, 1602 info.target_endianness or "unknown", 1603 host_or_target_str, 1604 host_or_target.endianness, 1605 ) 1606 ) 1607 1608 # Compiler version checks 1609 # =================================================== 1610 # Check the compiler version here instead of in `compiler_version` so 1611 # that the `checking` message doesn't pretend the compiler can be used 1612 # to then bail out one line later. 1613 if info.type == "gcc": 1614 if host_or_target.os == "Android": 1615 raise FatalCheckError( 1616 "GCC is not supported on Android.\n" 1617 "Please use clang from the Android NDK instead." 1618 ) 1619 gcc_version = minimum_gcc_version() 1620 if info.version < gcc_version: 1621 raise FatalCheckError( 1622 "Only GCC %d.%d or newer is supported (found version %s)." 1623 % (gcc_version.major, gcc_version.minor, info.version) 1624 ) 1625 1626 # Force GCC to use the C++ headers from the sysroot, and to prefer the 1627 # sysroot system headers to /usr/include. 1628 # Non-Debian GCC also doesn't look at headers in multiarch directory. 1629 if sysroot.bootstrapped and sysroot.stdcxx_version: 1630 version = sysroot.stdcxx_version 1631 for path in ( 1632 "usr/include/c++/{}".format(version), 1633 "usr/include/{}/c++/{}".format(multiarch_dir, version), 1634 "usr/include/{}".format(multiarch_dir), 1635 "usr/include", 1636 ): 1637 flags.extend(("-isystem", os.path.join(sysroot.path, path))) 1638 1639 if info.type == "clang-cl": 1640 if winsysroot and host.os != "WINNT": 1641 overlay = os.path.join(winsysroot, "overlay.yaml") 1642 if os.path.exists(overlay): 1643 flags.extend(["-Xclang", "-ivfsoverlay", "-Xclang", overlay]) 1644 1645 if (info.type, host_or_target.abi) in ( 1646 ("clang", "msvc"), 1647 ("clang-cl", "mingw"), 1648 ): 1649 raise FatalCheckError("Unknown compiler or compiler not supported.") 1650 1651 # If you want to bump the version check here ensure the version 1652 # is known for Xcode in get_compiler_info. 1653 # This version can be no higher than the version used in 1654 # src/bootstrap/src/core/build_steps/llvm.rs's check_llvm_version in the source 1655 # code of the minimum supported Rust version. 1656 if info.type in ("clang", "clang-cl") and info.version < "17.0": 1657 raise FatalCheckError( 1658 "Only %s 17.0 or newer is supported (found version %s)." 1659 % (("clang/llvm" if info.type == "clang" else "clang-cl"), info.version) 1660 ) 1661 1662 if host_or_target.kernel == "WASI" and info.type != "clang": 1663 raise FatalCheckError( 1664 "Only clang is supported for %s" % host_or_target.alias 1665 ) 1666 1667 if host_or_target.os == "WINNT" and info.type == "gcc": 1668 raise FatalCheckError( 1669 "Firefox cannot be built with mingw-gcc and requires a mingw-clang toolchain to work." 1670 ) 1671 1672 if info.flags: 1673 raise FatalCheckError("Unknown compiler or compiler not supported.") 1674 1675 return namespace( 1676 wrapper=wrapper, 1677 compiler=compiler, 1678 flags=flags, 1679 type=info.type, 1680 version=info.version, 1681 language=language, 1682 ) 1683 1684 @depends(valid_compiler) 1685 @checking("%s version" % what) 1686 def compiler_version(compiler): 1687 return compiler.version 1688 1689 if language == "C++": 1690 1691 @depends(valid_compiler, c_compiler) 1692 def valid_compiler(compiler, c_compiler): 1693 if compiler.type != c_compiler.type: 1694 die( 1695 "The %s C compiler is %s, while the %s C++ compiler is " 1696 "%s. Need to use the same compiler suite.", 1697 host_or_target_str, 1698 c_compiler.type, 1699 host_or_target_str, 1700 compiler.type, 1701 ) 1702 1703 if compiler.version != c_compiler.version: 1704 die( 1705 "The %s C compiler is version %s, while the %s C++ " 1706 "compiler is version %s. Need to use the same compiler " 1707 "version.", 1708 host_or_target_str, 1709 c_compiler.version, 1710 host_or_target_str, 1711 compiler.version, 1712 ) 1713 return compiler 1714 1715 # This excludes WASM_CC from the list. 1716 if var in ("CC", "CXX", "HOST_CC", "HOST_CXX"): 1717 # FIXME: we should return a plain list here. 1718 @depends_if(valid_compiler) 1719 @imports(_from="mozshellutil", _import="quote") 1720 def value(x): 1721 return quote(*x.wrapper, x.compiler, *x.flags) 1722 1723 set_config(var, value) 1724 1725 # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow 1726 # old-configure to do some of its still existing checks. 1727 if language == "C": 1728 set_config("%s_TYPE" % var, valid_compiler.type) 1729 set_config( 1730 "%s_VERSION" % var, depends(valid_compiler.version)(lambda v: str(v)) 1731 ) 1732 1733 valid_compiler = compiler_class(valid_compiler, host_or_target) 1734 1735 def compiler_error(): 1736 raise FatalCheckError( 1737 "Failed compiling a simple %s source with %s" % (language, what) 1738 ) 1739 1740 valid_compiler.try_compile(check_msg="%s works" % what, onerror=compiler_error) 1741 1742 set_config("%s_BASE_FLAGS" % var, valid_compiler.flags) 1743 1744 # Set CPP/CXXCPP for both the build system and old-configure. We don't 1745 # need to check this works for preprocessing, because we already relied 1746 # on $CC -E/$CXX -E doing preprocessing work to validate the compiler 1747 # in the first place. 1748 if host_or_target is target: 1749 pp_var = { 1750 "C": "CPP", 1751 "C++": "CXXCPP", 1752 }[language] 1753 1754 preprocessor = depends_if(valid_compiler)( 1755 lambda x: list(x.wrapper) + [x.compiler, "-E"] + list(x.flags) 1756 ) 1757 1758 set_config(pp_var, preprocessor) 1759 1760 if language == "C": 1761 linker_var = { 1762 target: "LD", 1763 host: "HOST_LD", 1764 }.get(host_or_target) 1765 1766 if linker_var: 1767 1768 @deprecated_option(env=linker_var, nargs=1) 1769 def linker(value): 1770 if value: 1771 return value[0] 1772 1773 @depends(linker) 1774 def unused_linker(linker): 1775 if linker: 1776 log.warning( 1777 "The value of %s is not used by this build system." % linker_var 1778 ) 1779 1780 return valid_compiler 1781 1782 1783 c_compiler = compiler("C", target) 1784 cxx_compiler = compiler("C++", target, c_compiler=c_compiler) 1785 host_c_compiler = compiler("C", host, other_compiler=c_compiler) 1786 host_cxx_compiler = compiler( 1787 "C++", 1788 host, 1789 c_compiler=host_c_compiler, 1790 other_compiler=cxx_compiler, 1791 other_c_compiler=c_compiler, 1792 ) 1793 1794 1795 @template 1796 def windows_abi(host_or_target, c_compiler): 1797 @depends(host_or_target) 1798 def windows_abi(host_or_target): 1799 if host_or_target.os == "WINNT": 1800 return host_or_target.abi 1801 1802 @depends(host_or_target, windows_abi) 1803 def need_windows_abi_from_compiler(host_or_target, windows_abi): 1804 return host_or_target.os == "WINNT" and windows_abi is None 1805 1806 @depends(host_or_target, c_compiler, when=need_windows_abi_from_compiler) 1807 def windows_abi_from_compiler(host_or_target, c_compiler): 1808 if host_or_target.os == "WINNT": 1809 if c_compiler.type == "clang-cl": 1810 return "msvc" 1811 return "mingw" 1812 1813 return windows_abi | windows_abi_from_compiler 1814 1815 1816 target_windows_abi = windows_abi(target, c_compiler) 1817 host_windows_abi = windows_abi(host, host_c_compiler) 1818 1819 1820 # Generic compiler-based conditions. 1821 building_with_gcc = depends(c_compiler)(lambda info: info.type == "gcc") 1822 building_with_gnu_compatible_cc = depends(c_compiler)( 1823 lambda info: info.type != "clang-cl" 1824 ) 1825 1826 1827 @depends(cxx_compiler, ccache_prefix) 1828 @imports("os") 1829 def cxx_is_icecream(info, ccache_prefix): 1830 if ( 1831 os.path.islink(info.compiler) 1832 and os.path.basename(os.readlink(info.compiler)) == "icecc" 1833 ): 1834 return True 1835 if ccache_prefix and os.path.basename(ccache_prefix) == "icecc": 1836 return True 1837 1838 1839 set_config("CXX_IS_ICECREAM", cxx_is_icecream) 1840 1841 1842 # Libstdc++ compatibility hacks 1843 # ============================================================== 1844 # 1845 @depends(target, host) 1846 def target_or_host_is_linux(target, host): 1847 return any(t.os == "GNU" and t.kernel == "Linux" for t in (target, host)) 1848 1849 1850 option( 1851 "--enable-stdcxx-compat", 1852 env="MOZ_STDCXX_COMPAT", 1853 help="Enable compatibility with older libstdc++", 1854 when=target_or_host_is_linux, 1855 ) 1856 1857 1858 @depends("--enable-stdcxx-compat", when=target_or_host_is_linux) 1859 def stdcxx_compat(value): 1860 if value: 1861 return True 1862 1863 1864 set_config("MOZ_STDCXX_COMPAT", True, when=stdcxx_compat) 1865 1866 1867 # Linker detection 1868 # ============================================================== 1869 # The policy is as follows: 1870 # For Windows: 1871 # - the linker is picked via the LINKER environment variable per windows.configure, 1872 # but ought to be lld-link in any case. 1873 # For macOS: 1874 # - the linker is lld if the clang used is >= 15 (per LLVM version, not Xcode version). 1875 # - the linker is also lld on local developer builds if the clang used is >= 13 (per LLVM 1876 # version, not Xcode version) 1877 # - otherwise the linker is ld64, either from XCode on macOS, or from cctools-ports when 1878 # cross-compiling. 1879 # For other OSes: 1880 # - on local developer builds: lld if present and the compiler is clang. Otherwise gold 1881 # is used if present otherwise, whatever the compiler uses by default. 1882 # - on release/official builds: whatever the compiler uses by default, except when the 1883 # compiler is clang, in which case lld is preferred when it's new enough. 1884 @template 1885 def is_not_winnt_or_sunos(host_or_target): 1886 @depends(host_or_target) 1887 def is_not_winnt_or_sunos(host_or_target): 1888 if host_or_target.kernel not in ("WINNT", "SunOS"): 1889 return True 1890 1891 return is_not_winnt_or_sunos 1892 1893 1894 is_linker_option_enabled = is_not_winnt_or_sunos(target) 1895 1896 1897 @deprecated_option("--enable-gold", env="MOZ_FORCE_GOLD", when=is_linker_option_enabled) 1898 def enable_gold(value): 1899 if value: 1900 die("--enable-gold is deprecated, use --enable-linker=gold instead") 1901 else: 1902 die("--disable-gold is deprecated, use --enable-linker=something_else instead") 1903 1904 1905 option( 1906 "--enable-linker", 1907 nargs=1, 1908 help="Select the linker {bfd, gold, ld64, lld, lld-*, mold}", 1909 when=is_linker_option_enabled, 1910 ) 1911 1912 1913 # No-op to enable depending on --enable-linker from default_elfhack in 1914 # toolkit/moz.configure. 1915 @depends("--enable-linker", when=is_linker_option_enabled) 1916 def enable_linker(linker): 1917 return linker 1918 1919 1920 @template 1921 def select_linker_tmpl(host_or_target): 1922 if host_or_target is target: 1923 deps = depends( 1924 "--enable-linker", 1925 c_compiler, 1926 developer_options, 1927 extra_toolchain_flags, 1928 target, 1929 stdcxx_compat, 1930 when=is_linker_option_enabled, 1931 ) 1932 host_or_target_str = "target" 1933 else: 1934 deps = depends( 1935 dependable(None), 1936 host_c_compiler, 1937 developer_options, 1938 dependable(None), 1939 host, 1940 stdcxx_compat, 1941 when=is_not_winnt_or_sunos(host_or_target), 1942 ) 1943 host_or_target_str = "host" 1944 1945 @deps 1946 @checking(f"for {host_or_target_str} linker", lambda x: x.KIND) 1947 @imports("os") 1948 @imports("shutil") 1949 def select_linker( 1950 linker, c_compiler, developer_options, toolchain_flags, target, stdcxx_compat 1951 ): 1952 if linker: 1953 linker = linker[0] 1954 else: 1955 linker = None 1956 1957 def is_valid_linker(linker): 1958 if target.kernel == "Darwin": 1959 valid_linkers = ("ld64", "lld") 1960 else: 1961 valid_linkers = ("bfd", "gold", "lld", "mold") 1962 if linker in valid_linkers: 1963 return True 1964 if "lld" in valid_linkers and linker.startswith("lld-"): 1965 return True 1966 return False 1967 1968 if linker and not is_valid_linker(linker): 1969 # Check that we are trying to use a supported linker 1970 die("Unsupported linker " + linker) 1971 1972 # Check the kind of linker 1973 version_check = ["-Wl,--version"] 1974 cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags 1975 1976 def try_linker(linker): 1977 # Generate the compiler flag 1978 if linker == "ld64": 1979 linker_flag = ["-fuse-ld=ld"] 1980 elif linker: 1981 linker_flag = ["-fuse-ld=" + linker] 1982 else: 1983 linker_flag = [] 1984 cmd = cmd_base + linker_flag + version_check 1985 if toolchain_flags: 1986 cmd += toolchain_flags 1987 1988 # ld64 doesn't have anything to print out a version. It does print out 1989 # "ld64: For information on command line options please use 'man ld'." 1990 # but that would require doing two attempts, one with --version, that 1991 # would fail, and another with --help. 1992 # Instead, abuse its LD_PRINT_OPTIONS feature to detect a message 1993 # specific to it on stderr when it fails to process --version. 1994 env = dict(os.environ) 1995 env["LD_PRINT_OPTIONS"] = "1" 1996 # Some locales might not print out the strings we are looking for, so 1997 # ensure consistent output. 1998 env["LC_ALL"] = "C" 1999 retcode, stdout, stderr = get_cmd_output(*cmd, env=env) 2000 if retcode == 1 and "Logging ld64 options" in stderr: 2001 kind = "ld64" 2002 2003 elif retcode != 0: 2004 return None 2005 2006 elif "mold" in stdout: 2007 kind = "mold" 2008 2009 elif "GNU ld" in stdout: 2010 # We are using the normal linker 2011 kind = "bfd" 2012 2013 elif "GNU gold" in stdout: 2014 kind = "gold" 2015 2016 elif "LLD" in stdout: 2017 kind = "lld" 2018 2019 else: 2020 kind = "unknown" 2021 2022 if kind == "unknown" or is_valid_linker(kind): 2023 return namespace( 2024 KIND=kind, 2025 LINKER_FLAG=linker_flag, 2026 ) 2027 2028 result = None 2029 if linker: 2030 result = try_linker(linker) 2031 if result is None: 2032 die("Could not use {} as linker".format(linker)) 2033 2034 if ( 2035 result is None 2036 and c_compiler.type == "clang" 2037 and ( 2038 ( 2039 target.kernel != "Darwin" 2040 and (developer_options or c_compiler.version >= "15.0") 2041 ) 2042 or ( 2043 target.kernel == "Darwin" 2044 and ( 2045 (developer_options and c_compiler.version >= "13.0") 2046 or c_compiler.version >= "15.0" 2047 ) 2048 ) 2049 ) 2050 ): 2051 result = try_linker("lld") 2052 2053 if result is None and developer_options and not stdcxx_compat: 2054 result = try_linker("gold") 2055 2056 if result is None: 2057 result = try_linker(None) 2058 2059 if result is None: 2060 die("Failed to find an adequate linker") 2061 2062 if stdcxx_compat and result.KIND == "gold": 2063 die("--enable-stdcxx-compat is not compatible with the gold linker") 2064 2065 # If an explicit linker was given, error out if what we found is different. 2066 if linker and not linker.startswith(result.KIND): 2067 die("Could not use {} as linker".format(linker)) 2068 2069 return result 2070 2071 return select_linker 2072 2073 2074 select_linker = select_linker_tmpl(target) 2075 2076 2077 @template 2078 def linker_ldflags_tmpl(host_or_target): 2079 if host_or_target is target: 2080 deps = depends_if( 2081 select_linker, 2082 target, 2083 target_sysroot, 2084 target_multiarch_dir, 2085 android_sysroot, 2086 android_version, 2087 c_compiler, 2088 developer_options, 2089 ) 2090 else: 2091 deps = depends_if( 2092 select_linker_tmpl(host), 2093 host, 2094 host_sysroot, 2095 host_multiarch_dir, 2096 dependable(None), 2097 dependable(None), 2098 host_c_compiler, 2099 developer_options, 2100 ) 2101 2102 @deps 2103 @imports("os") 2104 def linker_ldflags( 2105 linker, 2106 target, 2107 sysroot, 2108 multiarch_dir, 2109 android_sysroot, 2110 android_version, 2111 c_compiler, 2112 developer_options, 2113 ): 2114 flags = list((linker and linker.LINKER_FLAG) or []) 2115 # rpath-link is irrelevant to wasm, see for more info https://github.com/emscripten-core/emscripten/issues/11076. 2116 if sysroot.path and multiarch_dir and target.os != "WASI": 2117 for d in ("lib", "usr/lib"): 2118 multiarch_lib_dir = os.path.join(sysroot.path, d, multiarch_dir) 2119 if os.path.exists(multiarch_lib_dir): 2120 # Non-Debian-patched binutils linkers (both BFD and gold) don't lookup 2121 # in multi-arch directories. 2122 flags.append("-Wl,-rpath-link,%s" % multiarch_lib_dir) 2123 # GCC also needs -L. 2124 if c_compiler.type == "gcc": 2125 flags.append("-L%s" % multiarch_lib_dir) 2126 if ( 2127 c_compiler.type == "gcc" 2128 and sysroot.bootstrapped 2129 and sysroot.stdcxx_version 2130 ): 2131 flags.append( 2132 "-L{}/usr/lib/gcc/{}/{}".format( 2133 sysroot.path, multiarch_dir, sysroot.stdcxx_version 2134 ) 2135 ) 2136 if android_sysroot: 2137 # BFD/gold linkers need a manual --rpath-link for indirect 2138 # dependencies. 2139 flags += [ 2140 "-Wl,--rpath-link={}/usr/lib/{}".format( 2141 android_sysroot, target.toolchain 2142 ), 2143 "-Wl,--rpath-link={}/usr/lib/{}/{}".format( 2144 android_sysroot, target.toolchain, android_version 2145 ), 2146 ] 2147 if ( 2148 developer_options 2149 and linker 2150 and linker.KIND == "lld" 2151 and target.kernel != "WINNT" 2152 ): 2153 flags.append("-Wl,-O0") 2154 return flags 2155 2156 return linker_ldflags 2157 2158 2159 linker_ldflags = linker_ldflags_tmpl(target) 2160 host_linker_ldflags = linker_ldflags_tmpl(host) 2161 2162 2163 # There's a wrinkle with MinGW: linker configuration is not enabled, so 2164 # `select_linker` is never invoked. Hard-code around it. 2165 @depends(select_linker, target, c_compiler) 2166 def gcc_use_gnu_ld(select_linker, target, c_compiler): 2167 if select_linker is not None and target.kernel != "Darwin": 2168 return select_linker.KIND in ("bfd", "gold", "lld", "mold") 2169 if target.kernel == "WINNT" and c_compiler.type == "clang": 2170 return True 2171 return None 2172 2173 2174 # GCC_USE_GNU_LD=1 means the linker is command line compatible with GNU ld. 2175 set_config("GCC_USE_GNU_LD", gcc_use_gnu_ld) 2176 2177 2178 include("compile-checks.configure") 2179 include("arm.configure", when=depends(target.cpu)(lambda cpu: cpu == "arm")) 2180 2181 # Libstdc++ feature detection 2182 # ============================================================== 2183 2184 using_msvc_stl = depends(cxx_compiler)(lambda c: c.type == "clang-cl") 2185 2186 using_msvc_stl_202503_or_newer = try_compile( 2187 language="C++", 2188 includes=["new"], 2189 body="#if !defined(_MSVC_STL_UPDATE) || _MSVC_STL_UPDATE < 202503L\n#error 1\n#endif", 2190 when=using_msvc_stl, 2191 ) 2192 2193 using_libstdcxx = try_compile( 2194 language="C++", 2195 includes=["new"], 2196 body="#ifndef __GLIBCXX__\n#error 1\n#endif", 2197 when=~using_msvc_stl, 2198 ) 2199 2200 using_libcxx = try_compile( 2201 language="C++", 2202 includes=["new"], 2203 body="#ifndef _LIBCPP_VERSION\n#error 1\n#endif", 2204 when=~using_libstdcxx, 2205 ) 2206 2207 using_libcxx_19_or_newer = try_compile( 2208 language="C++", 2209 includes=["new"], 2210 body="#if _LIBCPP_VERSION < 190000\n#error 1\n#endif", 2211 when=using_libcxx, 2212 ) 2213 2214 2215 @depends( 2216 have_64_bit, 2217 try_compile( 2218 body='static_assert(sizeof(void *) == 8, "")', check_msg="for 64-bit OS" 2219 ), 2220 ) 2221 def check_have_64_bit(have_64_bit, compiler_have_64_bit): 2222 if have_64_bit != compiler_have_64_bit: 2223 configure_error( 2224 "The target compiler does not agree with configure " 2225 "about the target bitness." 2226 ) 2227 2228 2229 @depends(cxx_compiler, target) 2230 def needs_libstdcxx_newness_check(cxx_compiler, target): 2231 # We only have to care about this on Linux and MinGW. 2232 if cxx_compiler.type == "clang-cl": 2233 return 2234 2235 if target.kernel not in ("Linux", "WINNT"): 2236 return 2237 2238 if target.os == "Android": 2239 return 2240 2241 return True 2242 2243 2244 def die_on_old_libstdcxx(): 2245 die( 2246 "The libstdc++ in use is not new enough. Please run " 2247 "./mach bootstrap to update your compiler, or update your system " 2248 "libstdc++ installation." 2249 ) 2250 2251 2252 try_compile( 2253 includes=["cstddef"], 2254 body="\n".join( 2255 [ 2256 # _GLIBCXX_RELEASE showed up in libstdc++ 7. 2257 "#if defined(__GLIBCXX__) && !defined(_GLIBCXX_RELEASE)", 2258 "# error libstdc++ not new enough", 2259 "#endif", 2260 "#if defined(_GLIBCXX_RELEASE)", 2261 "# if _GLIBCXX_RELEASE < %d" % minimum_gcc_version().major, 2262 "# error libstdc++ not new enough", 2263 "# else", 2264 " (void) 0", 2265 "# endif", 2266 "#endif", 2267 ] 2268 ), 2269 check_msg="for new enough STL headers from libstdc++", 2270 when=needs_libstdcxx_newness_check, 2271 onerror=die_on_old_libstdcxx, 2272 ) 2273 2274 2275 @depends(c_compiler, target) 2276 def default_debug_flags(compiler_info, target): 2277 # Debug info is ON by default. 2278 if compiler_info.type == "clang-cl": 2279 return ("-Z7",) 2280 elif target.kernel == "WINNT" and compiler_info.type == "clang": 2281 return ("-g", "-gcodeview") 2282 # The oldest versions of supported compilers default to DWARF-4, but 2283 # newer versions may default to DWARF-5 or newer (e.g. clang 14), which 2284 # Valgrind doesn't support. Force-use DWARF-4. 2285 return ("-gdwarf-4",) 2286 2287 2288 option(env="MOZ_DEBUG_FLAGS", nargs=1, help="Debug compiler flags") 2289 2290 imply_option("--enable-debug-symbols", depends_if("--enable-debug")(lambda v: v)) 2291 2292 option( 2293 "--disable-debug-symbols", 2294 nargs="?", 2295 help="Disable debug symbols using the given compiler flags", 2296 ) 2297 2298 set_config("MOZ_DEBUG_SYMBOLS", depends_if("--enable-debug-symbols")(lambda _: True)) 2299 2300 2301 @depends("MOZ_DEBUG_FLAGS", "--enable-debug-symbols", default_debug_flags) 2302 @imports(_from="mozshellutil", _import="split") 2303 def debug_flags(env_debug_flags, enable_debug_flags, default_debug_flags): 2304 # If MOZ_DEBUG_FLAGS is set, and --enable-debug-symbols is set to a value, 2305 # --enable-debug-symbols takes precedence. Note, the value of 2306 # --enable-debug-symbols may be implied by --enable-debug. 2307 if len(enable_debug_flags): 2308 return split(enable_debug_flags[0]) 2309 if env_debug_flags: 2310 return split(env_debug_flags[0]) 2311 return default_debug_flags 2312 2313 2314 set_config("MOZ_DEBUG_FLAGS", debug_flags) 2315 2316 2317 @depends(c_compiler, host) 2318 @imports( 2319 _from="mach.logging", _import="enable_blessed", _as="_enable_ansi_escape_codes" 2320 ) 2321 def color_cflags(info, host): 2322 # We could test compiling with flags. By why incur the overhead when 2323 # color support should always be present in a specific toolchain 2324 # version? 2325 2326 # Code for auto-adding this flag to compiler invocations needs to 2327 # determine if an existing flag isn't already present. That is likely 2328 # using exact string matching on the returned value. So if the return 2329 # value changes to e.g. "<x>=always", exact string match may fail and 2330 # multiple color flags could be added. So examine downstream consumers 2331 # before adding flags to return values. 2332 if info.type == "gcc": 2333 return "-fdiagnostics-color" 2334 elif info.type in ["clang", "clang-cl"]: 2335 if host.os == "WINNT" and _enable_ansi_escape_codes(): 2336 return "-fcolor-diagnostics -fansi-escape-codes" 2337 else: 2338 return "-fcolor-diagnostics" 2339 else: 2340 return "" 2341 2342 2343 set_config("COLOR_CFLAGS", color_cflags) 2344 2345 # Some standard library headers (notably bionic on Android) declare standard 2346 # functions (e.g. getchar()) and also #define macros for those standard 2347 # functions. libc++ deals with this by doing something like the following 2348 # (explanatory comments added): 2349 # 2350 # #ifdef FUNC 2351 # // Capture the definition of FUNC. 2352 # inline _LIBCPP_INLINE_VISIBILITY int __libcpp_FUNC(...) { return FUNC(...); } 2353 # #undef FUNC 2354 # // Use a real inline definition. 2355 # inline _LIBCPP_INLINE_VISIBILITY int FUNC(...) { return _libcpp_FUNC(...); } 2356 # #endif 2357 # 2358 # _LIBCPP_INLINE_VISIBILITY is typically defined as: 2359 # 2360 # __attribute__((__visibility__("hidden"), __always_inline__)) 2361 # 2362 # Unfortunately, this interacts badly with our system header wrappers, as the: 2363 # 2364 # #pragma GCC visibility push(default) 2365 # 2366 # that they do prior to including the actual system header is treated by the 2367 # compiler as an explicit declaration of visibility on every function declared 2368 # in the header. Therefore, when the libc++ code above is encountered, it is 2369 # as though the compiler has effectively seen: 2370 # 2371 # int FUNC(...) __attribute__((__visibility__("default"))); 2372 # int FUNC(...) __attribute__((__visibility__("hidden"))); 2373 # 2374 # and the compiler complains about the mismatched visibility declarations. 2375 # 2376 # However, libc++ will only define _LIBCPP_INLINE_VISIBILITY if there is no 2377 # existing definition. We can therefore define it to the empty string (since 2378 # we are properly managing visibility ourselves) and avoid this whole mess. 2379 # Note that we don't need to do this with gcc, as libc++ detects gcc and 2380 # effectively does the same thing we are doing here. 2381 # 2382 # _LIBCPP_ALWAYS_INLINE needs a similar workarounds, since it too declares 2383 # hidden visibility. 2384 # 2385 # _LIBCPP_HIDE_FROM_ABI is a macro in libc++ versions in NDKs >=r19. It too 2386 # declares hidden visibility, but it also declares functions as excluded from 2387 # explicit instantiation (roughly: the function can be unused in the current 2388 # compilation, but does not then trigger an actual definition of the function; 2389 # it is assumed the real definition comes from elsewhere). We need to replicate 2390 # this setup. 2391 2392 2393 @depends(c_compiler, target) 2394 def libcxx_override_visibility(c_compiler, target): 2395 if c_compiler.type == "clang" and target.os == "Android": 2396 return namespace( 2397 empty="", 2398 hide_from_abi="__attribute__((__exclude_from_explicit_instantiation__))", 2399 ) 2400 2401 2402 set_define("_LIBCPP_INLINE_VISIBILITY", libcxx_override_visibility.empty) 2403 set_define("_LIBCPP_ALWAYS_INLINE", libcxx_override_visibility.empty) 2404 2405 set_define("_LIBCPP_HIDE_FROM_ABI", libcxx_override_visibility.hide_from_abi) 2406 2407 # TODO: remove this once we target C++23, where clang always define it, see bug 1880762 2408 set_define("_LIBCPP_REMOVE_TRANSITIVE_INCLUDES", True, when=using_libcxx) 2409 2410 2411 @depends(target, build_environment) 2412 def visibility_flags(target, env): 2413 if target.os != "WINNT": 2414 if target.kernel in ("Darwin", "FreeBSD", "OpenBSD"): 2415 return ("-fvisibility=hidden", "-fvisibility-inlines-hidden") 2416 return ( 2417 "-I%s/system_wrappers" % os.path.join(env.dist), 2418 "-include", 2419 "%s/config/gcc_hidden.h" % env.topsrcdir, 2420 ) 2421 2422 2423 @depends(target, visibility_flags) 2424 def wrap_system_includes(target, visibility_flags): 2425 if visibility_flags and target.kernel != "Darwin": 2426 return True 2427 2428 2429 set_define( 2430 "HAVE_VISIBILITY_HIDDEN_ATTRIBUTE", 2431 depends(visibility_flags)(lambda v: bool(v) or None), 2432 ) 2433 set_define( 2434 "HAVE_VISIBILITY_ATTRIBUTE", depends(visibility_flags)(lambda v: bool(v) or None) 2435 ) 2436 set_config("WRAP_SYSTEM_INCLUDES", wrap_system_includes) 2437 set_config("VISIBILITY_FLAGS", visibility_flags) 2438 2439 2440 # try harder, when checking for __thread support, see bug 521750 comment #33 and below 2441 # We pass linker_optimize_flags to the linker because if dead_strip is 2442 # enabled, the linker in xcode 4.1 will crash. Without this it would crash when 2443 # linking XUL. 2444 2445 2446 @depends(target, c_compiler) 2447 def check_thread(target, c_compiler): 2448 if target.cpu in ("mips32", "mips64"): 2449 # mips builds fail with TLS variables because of a binutils bug. 2450 # See bug 528687 2451 return False 2452 if target.os == "Android": 2453 # The custom dynamic linker doesn't support TLS variables 2454 return False 2455 if target.kernel == "OpenBSD": 2456 # OpenBSD doesn't have TLS support, and the test succeeds with clang++ 2457 return False 2458 return c_compiler.type != "clang-cl" 2459 2460 2461 set_define( 2462 "HAVE_THREAD_TLS_KEYWORD", 2463 try_link( 2464 body="static __thread bool tlsIsMainThread = false; return tlsIsMainThread;", 2465 flags=linker_optimize_flags.ldflags, 2466 check_msg="for __thread keyword for TLS variables", 2467 when=check_thread, 2468 ), 2469 ) 2470 2471 2472 @template 2473 def depend_cflags(host_or_target_c_compiler): 2474 @depends(host_or_target_c_compiler) 2475 def depend_cflags(host_or_target_c_compiler): 2476 if host_or_target_c_compiler.type != "clang-cl": 2477 return ["-MD", "-MP", "-MF $(MDDEPDIR)/$(@F).pp"] 2478 else: 2479 # clang-cl doesn't accept the normal -MD -MP -MF options that clang 2480 # does, but the underlying cc1 binary understands how to generate 2481 # dependency files. These options are based on analyzing what the 2482 # normal clang driver sends to cc1 when given the "correct" 2483 # dependency options. 2484 return [ 2485 "-Xclang", 2486 "-MP", 2487 "-Xclang", 2488 "-dependency-file", 2489 "-Xclang", 2490 "$(MDDEPDIR)/$(@F).pp", 2491 "-Xclang", 2492 "-MT", 2493 "-Xclang", 2494 "$@", 2495 ] 2496 2497 return depend_cflags 2498 2499 2500 set_config("_DEPEND_CFLAGS", depend_cflags(c_compiler)) 2501 set_config("_HOST_DEPEND_CFLAGS", depend_cflags(host_c_compiler)) 2502 2503 2504 @depends(c_compiler) 2505 def preprocess_option(compiler): 2506 # The uses of PREPROCESS_OPTION depend on the spacing for -o/-Fi. 2507 if compiler.type in ("gcc", "clang"): 2508 return "-E -o " 2509 else: 2510 return "-P -Fi" 2511 2512 2513 set_config("PREPROCESS_OPTION", preprocess_option) 2514 2515 2516 # On Power ISA, determine compiler flags for VMX, VSX and VSX-3. 2517 2518 set_config( 2519 "PPC_VMX_FLAGS", 2520 ["-maltivec"], 2521 when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), 2522 ) 2523 2524 set_config( 2525 "PPC_VSX_FLAGS", 2526 ["-mvsx"], 2527 when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), 2528 ) 2529 2530 set_config( 2531 "PPC_VSX3_FLAGS", 2532 ["-mvsx", "-mcpu=power9"], 2533 when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), 2534 ) 2535 2536 # TARGET_XPCOM_ABI 2537 # ============================================================== 2538 2539 is_arm_eabi = c_compiler.try_compile( 2540 body=""" 2541 #if defined(__ARM_EABI__) 2542 return 0; 2543 #else 2544 #error Not ARM EABI. 2545 #endif""", 2546 check_msg="for ARM EABI", 2547 when=building_with_gnu_compatible_cc 2548 & depends(target.cpu)(lambda cpu: cpu == "arm"), 2549 ) 2550 2551 2552 @depends(target, is_arm_eabi, c_compiler) 2553 def target_xpcom_abi(target, is_arm_eabi, compiler): 2554 if compiler.type == "clang-cl": 2555 return f"{target.cpu}-msvc" 2556 elif target.cpu == "arm": 2557 target_compiler_abi = "eabi" if is_arm_eabi else "oabi" 2558 return f"{target.cpu}-{target_compiler_abi}-gcc3" 2559 else: 2560 return f"{target.cpu}-gcc3" 2561 2562 2563 set_config("TARGET_XPCOM_ABI", target_xpcom_abi) 2564 set_define("TARGET_XPCOM_ABI", depends(target_xpcom_abi)(lambda v: f'"{v}"')) 2565 2566 # ASAN 2567 # ============================================================== 2568 2569 option("--enable-address-sanitizer", help="Enable Address Sanitizer") 2570 2571 2572 @depends(when="--enable-address-sanitizer") 2573 def asan(): 2574 return True 2575 2576 2577 with only_when(asan): 2578 option( 2579 env="MOZ_CLANG_RT_ASAN_LIB_PATH", 2580 nargs=1, 2581 help="Path to clang runtime asan library", 2582 ) 2583 2584 @depends( 2585 c_compiler, 2586 target, 2587 "MOZ_CLANG_RT_ASAN_LIB_PATH", 2588 ) 2589 @imports("os") 2590 @imports("glob") 2591 def clang_rt_asan_lib_path(c_compiler, target, clang_rt_asan_lib): 2592 if clang_rt_asan_lib: 2593 if os.path.exists(clang_rt_asan_lib[0]): 2594 return clang_rt_asan_lib[0] 2595 else: 2596 die( 2597 f"Specified MOZ_CLANG_RT_ASAN_LIB_PATH value '{clang_rt_asan_lib}' doesn't exist. " 2598 ) 2599 2600 # Look for the ASan runtime binary 2601 if c_compiler.type == "clang-cl": 2602 cpu = {"x86": "i386"}.get(target.cpu, target.cpu) 2603 clang_rt_asan_lib = f"clang_rt.asan_dynamic-{cpu}.dll" 2604 subdir = "windows" 2605 elif target.os == "Android": 2606 cpu = {"x86": "i686"}.get(target.cpu, target.cpu) 2607 clang_rt_asan_lib = f"libclang_rt.asan-{cpu}-android.so" 2608 subdir = "linux" 2609 else: 2610 return 2611 2612 search_path = os.path.join( 2613 os.path.dirname(c_compiler.compiler), 2614 "..", 2615 "lib", 2616 "clang", 2617 "*", 2618 "lib", 2619 subdir, 2620 clang_rt_asan_lib, 2621 ) 2622 if candidates := glob.glob(search_path): 2623 return candidates[0] 2624 2625 die( 2626 f"Couldn't find {clang_rt_asan_lib}. " 2627 f"It should be available in the same location as {c_compiler.type}." 2628 ) 2629 2630 set_config("MOZ_CLANG_RT_ASAN_LIB_PATH", clang_rt_asan_lib_path) 2631 2632 2633 @depends( 2634 c_compiler, 2635 target, 2636 compilation_flags, 2637 linker_flags, 2638 build_environment, 2639 when=asan, 2640 ) 2641 def asan_flags(c_compiler, target, compilation_flags, linker_flags, build_env): 2642 if c_compiler.type == "clang-cl": 2643 # Suppressing errors in recompiled code. 2644 if target.os == "WINNT": 2645 flag = f"-fsanitize-blacklist={build_env.topsrcdir}/build/sanitizers/asan_blacklist_win.txt" 2646 compilation_flags.cflags.append(flag) 2647 compilation_flags.cxxflags.append(flag) 2648 2649 asan_flag = "-fsanitize=address" 2650 compilation_flags.cflags.append(asan_flag) 2651 compilation_flags.cxxflags.append(asan_flag) 2652 2653 if c_compiler.type != "clang-cl": 2654 linker_flags.ldflags.extend([asan_flag, "-rdynamic"]) 2655 2656 2657 set_define("MOZ_ASAN", True, when=asan) 2658 set_config("MOZ_ASAN", True, when=asan) 2659 2660 # MSAN 2661 # ============================================================== 2662 2663 option("--enable-memory-sanitizer", help="Enable Memory Sanitizer") 2664 2665 2666 @depends(when="--enable-memory-sanitizer") 2667 def msan(): 2668 return True 2669 2670 2671 @depends(c_compiler, compilation_flags, linker_flags, when=msan) 2672 def msan_flags(c_compiler, compilation_flags, linker_flags): 2673 flags = ["-fsanitize=memory", "-fsanitize-memory-track-origins"] 2674 compilation_flags.cflags.extend(flags) 2675 compilation_flags.cxxflags.extend(flags) 2676 if c_compiler.type != "clang-cl": 2677 linker_flags.ldflags.extend(flags + ["-rdynamic"]) 2678 2679 2680 set_define("MOZ_MSAN", True, when=msan) 2681 set_config("MOZ_MSAN", True, when=msan) 2682 2683 # TSAN 2684 # ============================================================== 2685 2686 option("--enable-thread-sanitizer", help="Enable Thread Sanitizer") 2687 2688 2689 @depends(when="--enable-thread-sanitizer") 2690 def tsan(): 2691 return True 2692 2693 2694 @depends(c_compiler, compilation_flags, linker_flags, when=tsan) 2695 def tsan_flags(c_compiler, compilation_flags, linker_flags): 2696 flag = "-fsanitize=thread" 2697 compilation_flags.cflags.append(flag) 2698 compilation_flags.cxxflags.append(flag) 2699 if c_compiler.type != "clang-cl": 2700 linker_flags.ldflags.extend(["-fsanitize=thread", "-rdynamic"]) 2701 2702 2703 set_define("MOZ_TSAN", True, when=tsan) 2704 set_config("MOZ_TSAN", True, when=tsan) 2705 2706 # UBSAN 2707 # ============================================================== 2708 2709 option( 2710 "--enable-undefined-sanitizer", nargs="*", help="Enable UndefinedBehavior Sanitizer" 2711 ) 2712 2713 2714 @depends("--enable-undefined-sanitizer", moz_optimize) 2715 def ubsan(options, optimize): 2716 if not options: 2717 return 2718 2719 default_checks = [ 2720 "bool", 2721 "bounds", 2722 "enum", 2723 "function", 2724 "integer-divide-by-zero", 2725 "pointer-overflow", 2726 "return", 2727 "vla-bound", 2728 ] 2729 2730 # adding object-size generates a warning if -O0 is set 2731 if optimize: 2732 default_checks.append("object-size") 2733 2734 checks = options if len(options) else default_checks 2735 2736 return checks 2737 2738 2739 @depends( 2740 ubsan, c_compiler, compilation_flags, linker_flags, build_environment, when=ubsan 2741 ) 2742 @imports(_from="__builtin__", _import="open") 2743 @imports(_from="glob", _import="glob") 2744 @imports("shutil") 2745 def ubsan_flags(ubsan_checks, c_compiler, compilation_flags, linker_flags, build_env): 2746 ubsan_txt = os.path.join(build_env.topobjdir, "ubsan_blacklist.txt") 2747 with open(ubsan_txt, "w") as out_fd: 2748 for blacklist in glob( 2749 os.path.join( 2750 build_env.topsrcdir, "build", "sanitizers", "ubsan_*_blacklist.txt" 2751 ) 2752 ): 2753 with open(blacklist) as in_fd: 2754 shutil.copyfileobj(in_fd, out_fd) 2755 2756 joined_ubsan_checks = ",".join(ubsan_checks) 2757 2758 flags = [ 2759 f"-fsanitize={joined_ubsan_checks}", 2760 f"-fno-sanitize-recover={joined_ubsan_checks}", 2761 f"-fsanitize-blacklist={ubsan_txt}", 2762 ] 2763 compilation_flags.cflags.extend(flags) 2764 compilation_flags.cxxflags.extend(flags) 2765 if c_compiler.type != "clang-cl": 2766 linker_flags.ldflags.extend(["-fsanitize=undefined", "-rdynamic"]) 2767 2768 2769 option( 2770 "--enable-signed-overflow-sanitizer", 2771 help="Enable UndefinedBehavior Sanitizer (Signed Integer Overflow Parts)", 2772 ) 2773 2774 2775 @depends(when="--enable-signed-overflow-sanitizer") 2776 def ub_signed_overflow_san(): 2777 return True 2778 2779 2780 @depends( 2781 c_compiler, 2782 compilation_flags, 2783 linker_flags, 2784 build_environment, 2785 when=ub_signed_overflow_san, 2786 ) 2787 def ub_signed_overflow_san_flags( 2788 c_compiler, compilation_flags, linker_flags, build_env 2789 ): 2790 sanitizer_blacklist = os.path.join( 2791 build_env.topsrcdir, 2792 "build", 2793 "sanitizers", 2794 "ubsan_signed_overflow_blacklist.txt", 2795 ) 2796 flags = [ 2797 f"-fsanitize=signed-integer-overflow", 2798 f"-fsanitize-blacklist={sanitizer_blacklist}", 2799 ] 2800 compilation_flags.cflags.extend(flags) 2801 compilation_flags.cxxflags.extend(flags) 2802 if c_compiler.type != "clang-cl": 2803 linker_flags.ldflags.extend(["-fsanitize=signed-integer-overflow", "-rdynamic"]) 2804 2805 2806 option( 2807 "--enable-unsigned-overflow-sanitizer", 2808 help="Enable UndefinedBehavior Sanitizer (Unsigned Integer Overflow Parts)", 2809 ) 2810 2811 2812 @depends(when="--enable-unsigned-overflow-sanitizer") 2813 def ub_unsigned_overflow_san(): 2814 return True 2815 2816 2817 @depends( 2818 c_compiler, 2819 compilation_flags, 2820 linker_flags, 2821 build_environment, 2822 when=ub_unsigned_overflow_san, 2823 ) 2824 def ub_unsigned_overflow_san_flags( 2825 c_compiler, compilation_flags, linker_flags, build_env 2826 ): 2827 sanitizer_blacklist = os.path.join( 2828 build_env.topsrcdir, 2829 "build", 2830 "sanitizers", 2831 "ubsan_unsigned_overflow_blacklist.txt", 2832 ) 2833 flags = [ 2834 f"-fsanitize=unsigned-integer-overflow", 2835 f"-fsanitize-blacklist={sanitizer_blacklist}", 2836 ] 2837 compilation_flags.cflags.extend(flags) 2838 compilation_flags.cxxflags.extend(flags) 2839 if c_compiler.type != "clang-cl": 2840 linker_flags.ldflags.extend( 2841 ["-fsanitize=unsigned-integer-overflow", "-rdynamic"] 2842 ) 2843 2844 2845 # 2846 2847 any_ubsan = ubsan | ub_signed_overflow_san | ub_unsigned_overflow_san 2848 set_define("MOZ_UBSAN", True, when=any_ubsan) 2849 set_config("MOZ_UBSAN", any_ubsan) 2850 2851 2852 # We only want to include windows.configure when we are compiling on 2853 # Windows, or for Windows. 2854 include("windows.configure", when=is_windows) 2855 2856 # Security Hardening 2857 # ============================================================== 2858 2859 option( 2860 "--enable-hardening", 2861 env="MOZ_SECURITY_HARDENING", 2862 help="Enables security hardening compiler options", 2863 ) 2864 2865 2866 # This function is a bit confusing. It adds or removes hardening flags in 2867 # three stuations: if --enable-hardening is passed; if --disable-hardening 2868 # is passed, and if no flag is passed. 2869 # 2870 # At time of this comment writing, all flags are actually added in the 2871 # default no-flag case; making --enable-hardening the same as omitting the 2872 # flag. --disable-hardening will omit the security flags. (However, not all 2873 # possible security flags will be omitted by --disable-hardening, as many are 2874 # compiler-default options we do not explicitly enable.) 2875 @depends( 2876 "--enable-hardening", 2877 "--enable-address-sanitizer", 2878 "--enable-debug", 2879 "--enable-optimize", 2880 c_compiler, 2881 target, 2882 ) 2883 def security_hardening_cflags( 2884 hardening_flag, asan, debug, optimize, c_compiler, target 2885 ): 2886 compiler_is_gccish = c_compiler.type in ("gcc", "clang") 2887 mingw_clang = c_compiler.type == "clang" and target.os == "WINNT" 2888 2889 flags = [] 2890 ldflags = [] 2891 trivial_auto_var_init = [] 2892 2893 # WASI compiler doesn't support security hardening cflags 2894 if target.os == "WASI": 2895 return 2896 2897 # ---------------------------------------------------------- 2898 # If hardening is explicitly enabled, or not explicitly disabled 2899 if hardening_flag.origin == "default" or hardening_flag: 2900 # FORTIFY_SOURCE ------------------------------------ 2901 # Require optimization for FORTIFY_SOURCE. See Bug 1417452 2902 # Also, undefine it before defining it just in case a distro adds it, see Bug 1418398 2903 if compiler_is_gccish and optimize and not asan: 2904 flags.append("-U_FORTIFY_SOURCE") 2905 flags.append("-D_FORTIFY_SOURCE=2") 2906 2907 # fstack-protector ------------------------------------ 2908 # Enable only if hardening is not disabled and ASAN is 2909 # not on as ASAN will catch the crashes for us 2910 if compiler_is_gccish and not asan: 2911 flags.append("-fstack-protector-strong") 2912 ldflags.append("-fstack-protector-strong") 2913 2914 if ( 2915 c_compiler.type == "clang" 2916 and c_compiler.version >= "11.0.1" 2917 and target.os not in ("WINNT", "OSX", "OpenBSD") 2918 and target.cpu in ("x86", "x86_64", "ppc64", "s390x") 2919 ): 2920 flags.append("-fstack-clash-protection") 2921 ldflags.append("-fstack-clash-protection") 2922 2923 # ftrivial-auto-var-init ------------------------------ 2924 # Initialize local variables with a 0xAA pattern in clang builds. 2925 # Linux32 fails some xpcshell tests with -ftrivial-auto-var-init 2926 linux32 = target.kernel == "Linux" and target.cpu == "x86" 2927 if ( 2928 (c_compiler.type == "clang" or c_compiler.type == "clang-cl") 2929 and c_compiler.version >= "8" 2930 and not linux32 2931 ): 2932 if c_compiler.type == "clang-cl": 2933 trivial_auto_var_init.append("-Xclang") 2934 trivial_auto_var_init.append("-ftrivial-auto-var-init=pattern") 2935 # Always enable on debug builds. 2936 if debug: 2937 flags.extend(trivial_auto_var_init) 2938 2939 if (c_compiler.type == "clang" and c_compiler.version >= "16") or ( 2940 c_compiler.type == "gcc" and c_compiler.version >= "13" 2941 ): 2942 # Cannot use level 3 because we have many uses of the [0] GNU syntax. 2943 # Cannot use level 2 because sqlite3 and icu use the [1] GNU syntax. 2944 flags.append("-fstrict-flex-arrays=1") 2945 2946 # ASLR ------------------------------------------------ 2947 # ASLR (dynamicbase) is enabled by default in clang-cl; but the 2948 # mingw-clang build requires it to be explicitly enabled 2949 if mingw_clang: 2950 ldflags.append("-Wl,--dynamicbase") 2951 2952 # Control Flow Guard (CFG) ---------------------------- 2953 if ( 2954 c_compiler.type == "clang-cl" 2955 and c_compiler.version >= "8" 2956 and (target.cpu != "aarch64" or c_compiler.version >= "8.0.1") 2957 ): 2958 if target.cpu == "aarch64" and c_compiler.version >= "10.0.0": 2959 # The added checks in clang 10 make arm64 builds crash. (Bug 1639318) 2960 flags.append("-guard:cf,nochecks") 2961 else: 2962 flags.append("-guard:cf") 2963 # nolongjmp is needed because clang doesn't emit the CFG tables of 2964 # setjmp return addresses https://bugs.llvm.org/show_bug.cgi?id=40057 2965 ldflags.append("-guard:cf,nolongjmp") 2966 2967 # ---------------------------------------------------------- 2968 # If ASAN _is_ on, disable FORTIFY_SOURCE just to be safe 2969 if asan: 2970 flags.append("-D_FORTIFY_SOURCE=0") 2971 2972 # fno-common ----------------------------------------- 2973 # Do not merge variables for ASAN; can detect some subtle bugs 2974 if asan: 2975 # clang-cl does not recognize the flag, it must be passed down to clang 2976 if c_compiler.type == "clang-cl": 2977 flags.append("-Xclang") 2978 flags.append("-fno-common") 2979 2980 return namespace( 2981 flags=flags, 2982 ldflags=ldflags, 2983 trivial_auto_var_init=trivial_auto_var_init, 2984 ) 2985 2986 2987 set_config("MOZ_HARDENING_CFLAGS", security_hardening_cflags.flags) 2988 set_config("MOZ_HARDENING_LDFLAGS", security_hardening_cflags.ldflags) 2989 set_config( 2990 "MOZ_TRIVIAL_AUTO_VAR_INIT", 2991 security_hardening_cflags.trivial_auto_var_init, 2992 ) 2993 2994 2995 @depends(moz_debug, target) 2996 def want_stl_hardening(debug, target): 2997 return debug or target.os == "OSX" 2998 2999 3000 option( 3001 "--enable-stl-hardening", 3002 default=want_stl_hardening, 3003 help="{Enable|Disable} C++ STL hardening", 3004 ) 3005 3006 3007 @depends( 3008 "--enable-stl-hardening", 3009 moz_debug, 3010 using_msvc_stl_202503_or_newer, 3011 using_libstdcxx, 3012 using_libcxx, 3013 using_libcxx_19_or_newer, 3014 ) 3015 def stl_hardening_flags( 3016 enabled, 3017 debug, 3018 using_msvc_stl_202503_or_newer, 3019 using_libstdcxx, 3020 using_libcxx, 3021 using_libcxx_19_or_newer, 3022 ): 3023 if not enabled: 3024 return 3025 3026 if using_msvc_stl_202503_or_newer: 3027 return ["-D_MSVC_STL_HARDENING=1"] 3028 if using_libstdcxx: 3029 return ["-D_GLIBCXX_ASSERTIONS=1"] 3030 if using_libcxx: 3031 if using_libcxx_19_or_newer: 3032 if debug: 3033 return ["-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG"] 3034 else: 3035 return ["-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"] 3036 else: 3037 return ["-D_LIBCPP_ENABLE_ASSERTIONS=1"] 3038 3039 # Debug builds don't ship to users, so it is fine if STL hardening is 3040 # silently skipped, unless it has been explicitly enabled. 3041 if debug and enabled.origin == "default": 3042 return 3043 3044 die("C++ STL does not support hardening") 3045 3046 3047 set_config("MOZ_STL_HARDENING_FLAGS", stl_hardening_flags) 3048 3049 3050 # Intel Control-flow Enforcement Technology 3051 # ============================================================== 3052 # We keep this separate from the hardening flags above, because we want to be 3053 # able to easily remove the flags in the build files for certain executables. 3054 @depends(c_compiler, target) 3055 def cet_ldflags(c_compiler, target): 3056 ldflags = [] 3057 if ( 3058 c_compiler.type == "clang-cl" 3059 and c_compiler.version >= "11" 3060 and target.cpu == "x86_64" 3061 ): 3062 ldflags.append("-CETCOMPAT") 3063 return ldflags 3064 3065 3066 set_config("MOZ_CETCOMPAT_LDFLAGS", cet_ldflags) 3067 3068 3069 # Frame pointers 3070 # ============================================================== 3071 @depends(c_compiler) 3072 def frame_pointer_flags(compiler): 3073 if compiler.type == "clang-cl": 3074 return namespace( 3075 enable=["-Oy-"], 3076 disable=["-Oy"], 3077 ) 3078 return namespace( 3079 enable=["-fno-omit-frame-pointer", "-funwind-tables"], 3080 disable=["-fomit-frame-pointer", "-funwind-tables"], 3081 ) 3082 3083 3084 @depends( 3085 moz_optimize, 3086 moz_debug, 3087 target, 3088 "--enable-memory-sanitizer", 3089 "--enable-address-sanitizer", 3090 "--enable-undefined-sanitizer", 3091 ) 3092 def frame_pointer_default(optimize, debug, target, msan, asan, ubsan): 3093 return bool( 3094 not optimize 3095 or debug 3096 or msan 3097 or asan 3098 or ubsan 3099 or (target.os == "WINNT" and target.cpu in ("x86", "aarch64")) 3100 or target.os == "OSX" 3101 ) 3102 3103 3104 option( 3105 "--enable-frame-pointers", 3106 default=frame_pointer_default, 3107 help="{Enable|Disable} frame pointers", 3108 ) 3109 3110 3111 @depends("--enable-frame-pointers", frame_pointer_flags) 3112 def frame_pointer_flags(enable, flags): 3113 if enable: 3114 return flags.enable 3115 return flags.disable 3116 3117 3118 set_config("MOZ_FRAMEPTR_FLAGS", frame_pointer_flags) 3119 3120 3121 # Stack unwinding without frame pointers 3122 # ============================================================== 3123 3124 3125 have_unwind = check_symbol( 3126 "_Unwind_Backtrace", when=check_header("unwind.h", when=target_is_unix) 3127 ) 3128 3129 3130 # Code Coverage 3131 # ============================================================== 3132 3133 option("--enable-coverage", env="MOZ_CODE_COVERAGE", help="Enable code coverage") 3134 3135 3136 @depends("--enable-coverage") 3137 def code_coverage(value): 3138 if value: 3139 return True 3140 3141 3142 set_config("MOZ_CODE_COVERAGE", code_coverage) 3143 set_define("MOZ_CODE_COVERAGE", code_coverage) 3144 3145 3146 @depends(target, c_compiler, build_environment, when=code_coverage) 3147 @imports("os") 3148 @imports("re") 3149 @imports(_from="__builtin__", _import="open") 3150 def coverage_cflags(target, c_compiler, build_env): 3151 cflags = ["--coverage"] 3152 3153 # clang 11 no longer accepts this flag (its behavior became the default) 3154 if c_compiler.type in ("clang", "clang-cl") and c_compiler.version < "11.0.0": 3155 cflags += [ 3156 "-Xclang", 3157 "-coverage-no-function-names-in-data", 3158 ] 3159 3160 exclude = [] 3161 if target.os == "WINNT" and c_compiler.type == "clang-cl": 3162 # VS files 3163 exclude.append("^.*[vV][sS]20[0-9]{2}.*$") 3164 # Files in fetches directory. 3165 exclude.append("^.*[\\\\/]fetches[\\\\/].*$") 3166 elif target.os == "OSX": 3167 # Files in fetches directory. 3168 exclude.append("^.*/fetches/.*$") 3169 elif target.os == "GNU": 3170 # Files in fetches directory. 3171 exclude.append("^.*/fetches/.*$") 3172 # Files in /usr/ 3173 exclude.append("^/usr/.*$") 3174 3175 if exclude: 3176 exclude = ";".join(exclude) 3177 cflags += [ 3178 f"-fprofile-exclude-files={exclude}", 3179 ] 3180 3181 response_file_path = os.path.join(build_env.topobjdir, "code_coverage_cflags") 3182 3183 with open(response_file_path, "w") as f: 3184 f.write(" ".join(cflags)) 3185 3186 return ["@{}".format(response_file_path)] 3187 3188 3189 set_config("COVERAGE_CFLAGS", coverage_cflags) 3190 add_linker_flag("--coverage", when=code_coverage & building_with_gnu_compatible_cc) 3191 3192 # Assembler detection 3193 # ============================================================== 3194 3195 option(env="AS", nargs=1, help="Path to the assembler") 3196 3197 3198 @depends(target, c_compiler) 3199 def as_info(target, c_compiler): 3200 if c_compiler.type == "clang-cl": 3201 ml = { 3202 "x86": "ml.exe", 3203 "x86_64": "ml64.exe", 3204 "aarch64": "armasm64.exe", 3205 }.get(target.cpu) 3206 return namespace(type="masm", names=(ml,)) 3207 # When building with anything but clang-cl, we just use the C compiler as the assembler. 3208 return namespace(type="gcc", names=(c_compiler.compiler,)) 3209 3210 3211 # One would expect the assembler to be specified merely as a program. But in 3212 # cases where the assembler is passed down into js/, it can be specified in 3213 # the same way as CC: a program + a list of argument flags. We might as well 3214 # permit the same behavior in general, even though it seems somewhat unusual. 3215 # So we have to do the same sort of dance as we did above with 3216 # `provided_compiler`. 3217 provided_assembler = provided_program("AS") 3218 assembler = check_prog( 3219 "_AS", 3220 input=provided_assembler.program, 3221 what="the assembler", 3222 progs=as_info.names, 3223 paths=vc_toolchain_search_path, 3224 ) 3225 3226 3227 @depends(as_info, assembler, provided_assembler, c_compiler) 3228 def as_with_flags(as_info, assembler, provided_assembler, c_compiler): 3229 if provided_assembler: 3230 return provided_assembler.wrapper + [assembler] + provided_assembler.flags 3231 3232 if as_info.type == "masm": 3233 return assembler 3234 3235 assert as_info.type == "gcc" 3236 3237 # Need to add compiler wrappers and flags as appropriate. 3238 return c_compiler.wrapper + [assembler] + c_compiler.flags 3239 3240 3241 set_config("AS", as_with_flags) 3242 3243 3244 @depends(assembler, c_compiler, extra_toolchain_flags) 3245 @imports("subprocess") 3246 @imports(_from="os", _import="devnull") 3247 def gnu_as(assembler, c_compiler, toolchain_flags): 3248 # clang uses a compatible GNU assembler. 3249 if c_compiler.type == "clang": 3250 return True 3251 3252 if c_compiler.type == "gcc": 3253 cmd = [assembler] + c_compiler.flags 3254 if toolchain_flags: 3255 cmd += toolchain_flags 3256 cmd += ["-Wa,--version", "-c", "-o", devnull, "-x", "assembler", "-"] 3257 # We don't actually have to provide any input on stdin, `Popen.communicate` will 3258 # close the stdin pipe. 3259 # clang will error if it uses its integrated assembler for this target, 3260 # so handle failures gracefully. 3261 if "GNU" in check_cmd_output(*cmd, stdin=subprocess.PIPE, onerror=lambda: ""): 3262 return True 3263 3264 3265 set_config("GNU_AS", gnu_as) 3266 3267 3268 @depends(as_info, target) 3269 def as_dash_c_flag(as_info, target): 3270 # armasm64 doesn't understand -c. 3271 if as_info.type == "masm" and target.cpu == "aarch64": 3272 return "" 3273 else: 3274 return "-c" 3275 3276 3277 set_config("AS_DASH_C_FLAG", as_dash_c_flag) 3278 3279 3280 @depends(as_info, target) 3281 def as_outoption(as_info, target): 3282 # The uses of ASOUTOPTION depend on the spacing for -o/-Fo. 3283 if as_info.type == "masm" and target.cpu != "aarch64": 3284 return "-Fo" 3285 3286 return "-o " 3287 3288 3289 set_config("ASOUTOPTION", as_outoption) 3290 3291 # clang plugin handling 3292 # ============================================================== 3293 3294 option( 3295 "--enable-clang-plugin", 3296 env="ENABLE_CLANG_PLUGIN", 3297 help="Enable building with the Clang plugin (gecko specific static analyzers)", 3298 ) 3299 3300 3301 set_config("ENABLE_CLANG_PLUGIN", True, when="--enable-clang-plugin") 3302 set_define("MOZ_CLANG_PLUGIN", True, when="--enable-clang-plugin") 3303 3304 3305 @depends(host_c_compiler, c_compiler, when="--enable-clang-plugin") 3306 def llvm_config(host_c_compiler, c_compiler): 3307 clang = None 3308 for compiler in (host_c_compiler, c_compiler): 3309 if compiler and compiler.type == "clang": 3310 clang = compiler.compiler 3311 break 3312 elif compiler and compiler.type == "clang-cl": 3313 clang = os.path.join(os.path.dirname(compiler.compiler), "clang") 3314 break 3315 3316 if not clang: 3317 die("Cannot --enable-clang-plugin when not building with clang") 3318 llvm_config = "llvm-config" 3319 out = check_cmd_output(clang, "--print-prog-name=llvm-config", onerror=lambda: None) 3320 if out: 3321 llvm_config = out.rstrip() 3322 return (llvm_config,) 3323 3324 3325 llvm_config = check_prog( 3326 "LLVM_CONFIG", 3327 llvm_config, 3328 what="llvm-config", 3329 when="--enable-clang-plugin", 3330 paths=clang_search_path, 3331 ) 3332 3333 3334 @template 3335 def llvm_tool(name): 3336 @depends(host_c_compiler, c_compiler, bindgen_config_paths) 3337 def llvm_tool(host_c_compiler, c_compiler, bindgen_config_paths): 3338 clang = None 3339 for compiler in (host_c_compiler, c_compiler): 3340 if compiler and compiler.type == "clang": 3341 clang = compiler.compiler 3342 break 3343 elif compiler and compiler.type == "clang-cl": 3344 clang = os.path.join(os.path.dirname(compiler.compiler), "clang") 3345 break 3346 3347 if not clang and bindgen_config_paths: 3348 clang = bindgen_config_paths.clang_path 3349 tool = name 3350 if clang: 3351 out = check_cmd_output( 3352 clang, "--print-prog-name=%s" % tool, onerror=lambda: None 3353 ) 3354 if out: 3355 tool = out.rstrip() 3356 return (tool,) 3357 3358 return llvm_tool 3359 3360 3361 llvm_objdump = check_prog( 3362 "LLVM_OBJDUMP", 3363 llvm_tool("llvm-objdump"), 3364 what="llvm-objdump", 3365 when="--enable-compile-environment", 3366 paths=clang_search_path, 3367 ) 3368 3369 3370 # Force clang-cl compiler to treat input as C++ 3371 # ============================================================== 3372 add_flag("-TP", cxx_compiler, when=target_is_windows & ~building_with_gnu_compatible_cc) 3373 3374 # Use the old libstdc++ ABI 3375 # ============================================================== 3376 add_flag( 3377 "-D_GLIBCXX_USE_CXX11_ABI=0", 3378 cxx_compiler, 3379 when=stdcxx_compat, 3380 ) 3381 add_flag( 3382 "-D_GLIBCXX_USE_CXX11_ABI=0", 3383 host_cxx_compiler, 3384 when=stdcxx_compat, 3385 ) 3386 3387 3388 # Always included configuration file 3389 # ============================================================== 3390 @depends(c_compiler, build_environment, build_project) 3391 def defines_cpp_flags(c_compiler, build_environment, build_project): 3392 if build_project == "js": 3393 config_h = "js/src/js-confdefs.h" 3394 else: 3395 config_h = "mozilla-config.h" 3396 3397 if c_compiler.type == "clang-cl": 3398 flag = "-FI" 3399 else: 3400 flag = "-include" 3401 3402 return ["-DMOZILLA_CLIENT", flag, f"{build_environment.topobjdir}/{config_h}"] 3403 3404 3405 set_config("OS_COMPILE_CFLAGS", defines_cpp_flags) 3406 set_config("OS_COMPILE_CXXFLAGS", defines_cpp_flags) 3407 3408 3409 # Support various fuzzing options 3410 # ============================================================== 3411 option("--enable-fuzzing", help="Enable fuzzing support") 3412 3413 3414 @depends(build_project) 3415 def js_build(build_project): 3416 return build_project == "js" 3417 3418 3419 option( 3420 "--enable-js-fuzzilli", 3421 when=js_build, 3422 help="Enable fuzzilli support for the JS engine", 3423 ) 3424 3425 3426 option( 3427 "--enable-snapshot-fuzzing", 3428 help="Enable experimental snapshot fuzzing support", 3429 ) 3430 3431 3432 imply_option("--enable-fuzzing", True, when="--enable-snapshot-fuzzing") 3433 3434 3435 @depends("--enable-snapshot-fuzzing") 3436 def enable_snapshot_fuzzing(value): 3437 if value: 3438 return True 3439 3440 3441 @depends("--enable-fuzzing", enable_snapshot_fuzzing) 3442 def enable_fuzzing(value, snapshot_fuzzing): 3443 if value or snapshot_fuzzing: 3444 return True 3445 3446 3447 @depends("--enable-js-fuzzilli", when=js_build) 3448 def enable_js_fuzzilli(value): 3449 if value: 3450 return True 3451 3452 3453 @depends(enable_fuzzing, enable_snapshot_fuzzing) 3454 def check_aflfuzzer(fuzzing, snapshot_fuzzing): 3455 if fuzzing and not snapshot_fuzzing: 3456 return True 3457 3458 3459 @depends( 3460 try_compile( 3461 body="__AFL_COMPILER;", check_msg="for AFL compiler", when=check_aflfuzzer 3462 ) 3463 ) 3464 def enable_aflfuzzer(afl): 3465 if afl: 3466 return True 3467 3468 3469 @depends(enable_fuzzing, enable_aflfuzzer, enable_snapshot_fuzzing, c_compiler, target) 3470 def enable_libfuzzer(fuzzing, afl, snapshot_fuzzing, c_compiler, target): 3471 if ( 3472 fuzzing 3473 and not afl 3474 and not snapshot_fuzzing 3475 and c_compiler.type == "clang" 3476 and target.os != "Android" 3477 ): 3478 return True 3479 3480 3481 @depends(enable_fuzzing, enable_aflfuzzer, enable_libfuzzer, enable_js_fuzzilli) 3482 def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer, enable_js_fuzzilli): 3483 if fuzzing and (afl or libfuzzer) and not enable_js_fuzzilli: 3484 return True 3485 3486 3487 set_config("FUZZING", enable_fuzzing) 3488 set_define("FUZZING", enable_fuzzing) 3489 3490 set_config("LIBFUZZER", enable_libfuzzer) 3491 set_define("LIBFUZZER", enable_libfuzzer) 3492 3493 set_config("AFLFUZZ", enable_aflfuzzer) 3494 set_define("AFLFUZZ", enable_aflfuzzer) 3495 3496 set_config("FUZZING_INTERFACES", enable_fuzzing_interfaces) 3497 set_define("FUZZING_INTERFACES", enable_fuzzing_interfaces) 3498 3499 set_config("FUZZING_JS_FUZZILLI", enable_js_fuzzilli) 3500 set_define("FUZZING_JS_FUZZILLI", enable_js_fuzzilli) 3501 3502 set_config("FUZZING_SNAPSHOT", enable_snapshot_fuzzing) 3503 set_define("FUZZING_SNAPSHOT", enable_snapshot_fuzzing) 3504 3505 3506 @depends( 3507 c_compiler.try_compile( 3508 flags=["-fsanitize=fuzzer-no-link"], 3509 when=enable_fuzzing, 3510 check_msg="whether the C compiler supports -fsanitize=fuzzer-no-link", 3511 ), 3512 tsan, 3513 enable_js_fuzzilli, 3514 ) 3515 def libfuzzer_flags(value, tsan, enable_js_fuzzilli): 3516 if tsan: 3517 # With ThreadSanitizer, we should not use any libFuzzer instrumentation because 3518 # it is incompatible (e.g. there are races on global sanitizer coverage counters). 3519 # Instead we use an empty set of flags here but still build the fuzzing targets. 3520 # With this setup, we can still run files through these targets in TSan builds, 3521 # e.g. those obtained from regular fuzzing. 3522 # This code can be removed once libFuzzer has been made compatible with TSan. 3523 # 3524 # Also, this code needs to be kept in sync with certain gyp files, currently: 3525 # - dom/media/webrtc/transport/third_party/nICEr/nicer.gyp 3526 return namespace(no_link_flag_supported=False, use_flags=[]) 3527 3528 if enable_js_fuzzilli: 3529 # Fuzzilli comes with its own trace-pc interceptors and flag requirements. 3530 no_link_flag_supported = False 3531 use_flags = ["-fsanitize-coverage=trace-pc-guard", "-g"] 3532 elif value: 3533 no_link_flag_supported = True 3534 # recommended for (and only supported by) clang >= 6 3535 use_flags = ["-fsanitize=fuzzer-no-link"] 3536 else: 3537 no_link_flag_supported = False 3538 use_flags = ["-fsanitize-coverage=trace-pc-guard,trace-cmp"] 3539 3540 return namespace( 3541 no_link_flag_supported=no_link_flag_supported, 3542 use_flags=use_flags, 3543 ) 3544 3545 3546 @depends(libfuzzer_flags, when=enable_libfuzzer) 3547 def sancov(libfuzzer_flags): 3548 return any( 3549 flag.startswith(head) 3550 for head in ("-fsanitize-coverage", "-fsanitize=fuzzer") 3551 for flag in libfuzzer_flags.use_flags 3552 ) 3553 3554 3555 set_config("HAVE_LIBFUZZER_FLAG_FUZZER_NO_LINK", libfuzzer_flags.no_link_flag_supported) 3556 set_config("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags) 3557 3558 3559 # Required for stand-alone (sanitizer-less) libFuzzer. 3560 # ============================================================== 3561 @depends(libfuzzer_flags, linker_flags, when=enable_libfuzzer) 3562 def add_libfuzzer_flags(libfuzzer_flags, linker_flags): 3563 linker_flags.ldflags.extend(libfuzzer_flags.use_flags) 3564 linker_flags.ldflags.append("-rdynamic") 3565 3566 3567 # The LLVM symbolizer is used by all sanitizers 3568 check_prog( 3569 "LLVM_SYMBOLIZER", 3570 ("llvm-symbolizer",), 3571 allow_missing=True, 3572 paths=clang_search_path, 3573 when=asan | msan | tsan | any_ubsan | enable_fuzzing, 3574 ) 3575 3576 3577 # Shared library building 3578 # ============================================================== 3579 3580 3581 # XXX: The use of makefile constructs in these variables is awful. 3582 @depends(target, c_compiler) 3583 def make_shared_library(target, compiler): 3584 if target.os == "WINNT": 3585 if compiler.type == "gcc": 3586 return namespace( 3587 mkshlib=["$(CXX)", "$(DSO_LDOPTS)", "-o", "$@"], 3588 mkcshlib=["$(CC)", "$(DSO_LDOPTS)", "-o", "$@"], 3589 ) 3590 elif compiler.type == "clang": 3591 return namespace( 3592 mkshlib=[ 3593 "$(CXX)", 3594 "$(DSO_LDOPTS)", 3595 "-Wl,-pdb,$(LINK_PDBFILE)", 3596 "-o", 3597 "$@", 3598 ], 3599 mkcshlib=[ 3600 "$(CC)", 3601 "$(DSO_LDOPTS)", 3602 "-Wl,-pdb,$(LINK_PDBFILE)", 3603 "-o", 3604 "$@", 3605 ], 3606 ) 3607 else: 3608 linker = [ 3609 "$(LINKER)", 3610 "-NOLOGO", 3611 "-DLL", 3612 "-OUT:$@", 3613 "-PDB:$(LINK_PDBFILE)", 3614 "$(DSO_LDOPTS)", 3615 ] 3616 return namespace( 3617 mkshlib=linker, 3618 mkcshlib=linker, 3619 ) 3620 3621 cc = ["$(CC)", "$(COMPUTED_C_LDFLAGS)"] 3622 cxx = ["$(CXX)", "$(COMPUTED_CXX_LDFLAGS)"] 3623 flags = ["$(DSO_LDOPTS)"] 3624 output = ["-o", "$@"] 3625 3626 if target.kernel == "Darwin": 3627 soname = [] 3628 elif target.os == "NetBSD": 3629 soname = ["-Wl,-soname,$(DSO_SONAME)"] 3630 else: 3631 assert compiler.type in ("gcc", "clang") 3632 3633 soname = ["-Wl,-h,$(DSO_SONAME)"] 3634 3635 return namespace( 3636 mkshlib=cxx + flags + soname + output, 3637 mkcshlib=cc + flags + soname + output, 3638 ) 3639 3640 3641 set_config("MKSHLIB", make_shared_library.mkshlib) 3642 set_config("MKCSHLIB", make_shared_library.mkcshlib) 3643 3644 3645 @depends(c_compiler, toolchain_prefix, when=target_is_windows) 3646 def rc_names(c_compiler, toolchain_prefix): 3647 if c_compiler.type in ("gcc", "clang"): 3648 return tuple("%s%s" % (p, "windres") for p in ("",) + (toolchain_prefix or ())) 3649 return ("llvm-rc",) 3650 3651 3652 check_prog("RC", rc_names, paths=clang_search_path, when=target_is_windows) 3653 3654 3655 @template 3656 def ar_config(c_compiler, toolchain_prefix=None): 3657 if not toolchain_prefix: 3658 toolchain_prefix = dependable(None) 3659 3660 @depends(toolchain_prefix, c_compiler) 3661 def ar_config(toolchain_prefix, c_compiler): 3662 if c_compiler.type == "clang-cl": 3663 return namespace( 3664 names=("llvm-lib",), 3665 flags=("-llvmlibthin", "-out:$@"), 3666 ) 3667 3668 names = tuple("%s%s" % (p, "ar") for p in (toolchain_prefix or ()) + ("",)) 3669 if c_compiler.type == "clang": 3670 # Get the llvm-ar path as per the output from clang --print-prog-name=llvm-ar 3671 # so that we directly get the one under the clang directory, rather than one 3672 # that might be in /usr/bin and that might point to one from a different version 3673 # of clang. 3674 out = check_cmd_output( 3675 c_compiler.compiler, "--print-prog-name=llvm-ar", onerror=lambda: None 3676 ) 3677 llvm_ar = out.rstrip() if out else "llvm-ar" 3678 names = (llvm_ar,) + names 3679 3680 return namespace( 3681 names=names, 3682 flags=("crs", "$@"), 3683 ) 3684 3685 return ar_config 3686 3687 3688 target_ar_config = ar_config(c_compiler, toolchain_prefix) 3689 3690 target_ar = check_prog("AR", target_ar_config.names, paths=clang_search_path) 3691 3692 set_config("AR_FLAGS", target_ar_config.flags) 3693 3694 3695 @depends(c_compiler, extra_toolchain_flags, target_ar, target_ar_config) 3696 @checking("whether ar supports response files") 3697 @imports("os") 3698 @imports(_from="__builtin__", _import="FileNotFoundError") 3699 @imports(_from="__builtin__", _import="open") 3700 @imports(_from="mozbuild.configure.util", _import="LineIO") 3701 def ar_supports_response_files(c_compiler, extra_toolchain_flags, ar, ar_config): 3702 lib_path = list_path = None 3703 with create_temporary_file(suffix=".o") as obj_path: 3704 if ( 3705 try_invoke_compiler( 3706 # No configure_cache because it would not create the 3707 # expected output file. 3708 None, 3709 [c_compiler.compiler] + c_compiler.flags, 3710 c_compiler.language, 3711 "void foo() {}", 3712 ["-c", "-o", obj_path] + (extra_toolchain_flags or []), 3713 wrapper=c_compiler.wrapper, 3714 onerror=lambda: None, 3715 ) 3716 is not None 3717 ): 3718 with create_temporary_file(suffix=".list") as list_path: 3719 with open(list_path, "w") as fd: 3720 fd.write(obj_path) 3721 3722 log.debug("Creating `%s` with content:", list_path) 3723 log.debug("| %s", obj_path) 3724 3725 with create_temporary_file(suffix=".a") as lib_path: 3726 os.remove(lib_path) 3727 ar_command = ( 3728 [ar] 3729 + [x.replace("$@", lib_path) for x in ar_config.flags] 3730 + ["@" + list_path] 3731 ) 3732 result = check_cmd_output(*ar_command, onerror=lambda: None) 3733 return result is not None 3734 3735 3736 set_config("AR_SUPPORTS_RESPONSE_FILE", True, when=ar_supports_response_files) 3737 3738 host_ar_config = ar_config(host_c_compiler) 3739 3740 check_prog("HOST_AR", host_ar_config.names, paths=clang_search_path) 3741 3742 3743 @depends(toolchain_prefix, c_compiler) 3744 def nm_names(toolchain_prefix, c_compiler): 3745 names = tuple("%s%s" % (p, "nm") for p in (toolchain_prefix or ()) + ("",)) 3746 if c_compiler.type == "clang": 3747 # Get the llvm-nm path as per the output from clang --print-prog-name=llvm-nm 3748 # so that we directly get the one under the clang directory, rather than one 3749 # that might be in /usr/bin and that might point to one from a different version 3750 # of clang. 3751 out = check_cmd_output( 3752 c_compiler.compiler, "--print-prog-name=llvm-nm", onerror=lambda: None 3753 ) 3754 llvm_nm = out.rstrip() if out else "llvm-nm" 3755 names = (llvm_nm,) + names 3756 3757 return names 3758 3759 3760 check_prog("NM", nm_names, paths=clang_search_path, when=target_has_linux_kernel) 3761 3762 3763 # We don't use it in the code, but it can be useful for debugging, so give 3764 # the user the option of enabling it. 3765 option("--enable-cpp-rtti", help="Enable C++ RTTI") 3766 3767 3768 @depends(compilation_flags, c_compiler, "--enable-cpp-rtti") 3769 def enable_cpp_rtti(compilation_flags, c_compiler, enable_rtti): 3770 if enable_rtti: 3771 return 3772 if c_compiler.type == "clang-cl": 3773 compilation_flags.cxxflags.append("-GR-") 3774 else: 3775 compilation_flags.cxxflags.append("-fno-rtti") 3776 3777 3778 option( 3779 "--enable-path-remapping", 3780 nargs="*", 3781 choices=("c", "rust"), 3782 help="Enable remapping source and object paths in compiled outputs", 3783 ) 3784 3785 3786 @depends("--enable-path-remapping") 3787 def path_remapping(value): 3788 if len(value): 3789 return value 3790 if bool(value): 3791 return ["c", "rust"] 3792 return [] 3793 3794 3795 @depends( 3796 target, 3797 build_environment, 3798 target_sysroot.path, 3799 valid_windows_sdk_dir, 3800 vc_path, 3801 when="--enable-path-remapping", 3802 ) 3803 def path_remappings(target, build_env, sysroot_path, windows_sdk_dir, vc_path): 3804 win = target.kernel == "WINNT" 3805 3806 # The prefix maps are processed in the order they're specified on the 3807 # command line. Therefore, to accommodate object directories in the source 3808 # directory, it's important that we map the topobjdir before the topsrcdir, 3809 # 'cuz we might have /src/obj/=/o/ and /src/=/s/. The various other 3810 # directories might be subdirectories of topsrcdir as well, so they come 3811 # earlier still. 3812 3813 path_remappings = [] 3814 3815 # We will have only one sysroot or SDK, so all can have the same mnemonic: K 3816 # for "kit" (since S is taken for "source"). See 3817 # https://blog.llvm.org/2019/11/deterministic-builds-with-clang-and-lld.html 3818 # for how to use the Windows `subst` command to map these in debuggers and 3819 # IDEs. 3820 if sysroot_path: 3821 path_remappings.append((sysroot_path, "k:/" if win else "/sysroot/")) 3822 if windows_sdk_dir: 3823 path_remappings.append( 3824 (windows_sdk_dir.path, "k:/" if win else "/windows_sdk/") 3825 ) 3826 if vc_path: 3827 path_remappings.append((vc_path, "v:/" if win else "/vc/")) 3828 3829 path_remappings += [ 3830 (build_env.topobjdir, "o:/" if win else "/topobjdir/"), 3831 (build_env.topsrcdir, "s:/" if win else "/topsrcdir/"), 3832 ] 3833 3834 path_remappings = [ 3835 (normsep(old).rstrip("/") + "/", new) for old, new in path_remappings 3836 ] 3837 3838 # It is tempting to sort these, but we want the order to be the same across 3839 # machines so that we can share cache hits. Therefore we reject bad 3840 # configurations rather than trying to make the configuration good. 3841 for i in range(len(path_remappings) - 1): 3842 p = path_remappings[i][0] 3843 for q, _ in path_remappings[i + 1 :]: 3844 if q.startswith(p): 3845 die(f"Cannot remap paths because {p} is an ancestor of {q}") 3846 3847 return path_remappings 3848 3849 3850 @depends(target) 3851 def is_intel_target(target): 3852 return target.cpu in ("x86", "x86_64") 3853 3854 3855 @depends(target) 3856 def is_aarch64_target(target): 3857 return target.cpu == "aarch64" 3858 3859 3860 set_config("MMX_FLAGS", ["-mmmx"]) 3861 set_config("SSE_FLAGS", ["-msse"]) 3862 set_config("SSE2_FLAGS", ["-msse2"]) 3863 set_config("SSSE3_FLAGS", ["-mssse3"]) 3864 set_config("SSE4_2_FLAGS", ["-msse4.2"]) 3865 set_config("FMA_FLAGS", ["-mfma"]) 3866 set_config("AVX2_FLAGS", ["-mavx2"]) 3867 set_config( 3868 "AVXVNNI_FLAGS", 3869 ["-mavxvnni"], 3870 try_compile( 3871 check_msg="for -mavxvnni support", flags=["-mavxvnni"], when=is_intel_target 3872 ), 3873 ) 3874 set_config( 3875 "AVX512BW_FLAGS", 3876 ["-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"], 3877 try_compile( 3878 check_msg="for -mavx512bw support", 3879 flags=["-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"], 3880 when=is_intel_target, 3881 ), 3882 ) 3883 3884 # AVX512VNNI can be based on either avx512bw or avx512vbmi. We choose the 3885 # former. 3886 set_config( 3887 "AVX512VNNI_FLAGS", 3888 ["-mavx512vnni", "-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"], 3889 try_compile( 3890 check_msg="for -mavx512vnni support", 3891 flags=["-mavx512vnni", "-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"], 3892 when=is_intel_target, 3893 ), 3894 ) 3895 3896 3897 set_config( 3898 "NEON_I8MM_FLAGS", 3899 ["-march=armv8.2-a+i8mm"], 3900 try_compile( 3901 check_msg="for i8mm target feature", 3902 flags=["-march=armv8.2-a+i8mm"], 3903 when=is_aarch64_target, 3904 ), 3905 ) 3906 3907 set_config( 3908 "SVE2_FLAGS", 3909 ["-march=armv9-a+sve2"], 3910 try_compile( 3911 check_msg="for ARM SVE2 target feature", 3912 flags=["-march=armv9-a+sve2"], 3913 when=is_aarch64_target, 3914 ), 3915 ) 3916 3917 set_config( 3918 "DOTPROD_FLAGS", 3919 ["-march=armv8.2-a+dotprod"], 3920 try_compile( 3921 check_msg="for ARM dotprod target feature", 3922 flags=["-march=armv8.2-a+dotprod"], 3923 when=is_aarch64_target, 3924 ), 3925 ) 3926 3927 3928 @depends(target, c_compiler) 3929 def htmlaccel_config(target, c_compiler): 3930 # Keep this is sync with the mozilla::htmlaccel::htmlaccelEnabled function. 3931 # 3932 # The code compiles on SSSE3, but AVX+BMI generates better code 3933 # and has been available for 12 years at the time of landing this, 3934 # so let's give the best code to users with reasonably recent hardware. 3935 # 3936 # Not enabled on 32-bit x86, due to lack of insight into what hardware is 3937 # representative at this point in time and due to lack of such hardware 3938 # for testing to see what config would actually be an optimization. 3939 # 3940 # aarch64 does not need extra flags. 3941 # 3942 # clang-cl doesn't tolerate -flax-vector-conversions but GCC requires it. 3943 # 3944 # -mavx2 doesn't change codegen vs. -mavx. AVX2 and BMI always co-occur 3945 # in Intel CPUs, but there are AMD CPUs that have AVX and BMI without 3946 # AVX2. 3947 if target.cpu != "x86_64": 3948 return [] 3949 if c_compiler.type == "gcc": 3950 return ["-mavx", "-mbmi", "-flax-vector-conversions"] 3951 return ["-mavx", "-mbmi"] 3952 3953 3954 set_config("HTML_ACCEL_FLAGS", htmlaccel_config) 3955 3956 3957 @depends(c_compiler, compilation_flags, coverage_cflags, linker_flags) 3958 @imports(_from="mozshellutil", _import="split", _as="shellsplit") 3959 def extra_linker_flags(compiler, compilation_flags, coverage_cflags, linker_flags): 3960 if compiler.type != "clang-cl": 3961 return 3962 with create_temporary_file(suffix=".c") as path: 3963 # On clang-cl builds only, we run the linker via separate commands rather 3964 # than invoking it through the compiler. We want to figure out what flags 3965 # the compiler would add to the linker command line based on the extra 3966 # compiler flags we use (notably, for sanitizer) 3967 # The -### argument to clang/clang-cl tells us what commands it would run 3968 # internally, and the last of them is going to be the linker. 3969 # What we want to do is compare the linker commands the compiler would 3970 # invoke with a limited set of flags vs. all of them. 3971 # Typically, that will give us some extra flags, including extra libs for 3972 # the clang runtime. 3973 3974 path_stem = os.path.basename(path)[: -len(".c")] 3975 3976 def is_in_out_arg(arg): 3977 return arg.startswith("-out:") or os.path.basename(arg).startswith( 3978 path_stem 3979 ) 3980 3981 cmd = [ 3982 compiler.compiler, 3983 path, 3984 "-fuse-ld=lld", 3985 "-MD", 3986 "-###", 3987 ] + compiler.flags 3988 retcode, stdout, stderr = get_cmd_output(*cmd) 3989 if retcode != 0: 3990 die("Error while running %s:\n%s" % (" ".join(cmd), stderr)) 3991 linker_command = shellsplit(stderr.splitlines()[-1]) 3992 linker_args = set(arg for arg in linker_command if not is_in_out_arg(arg)) 3993 cmd += compilation_flags.cflags + (coverage_cflags or []) 3994 retcode, stdout, stderr = get_cmd_output(*cmd) 3995 if retcode != 0: 3996 die("Error while running %s:\n%s" % (" ".join(cmd), stderr)) 3997 linker_flags.ldflags.extend( 3998 arg 3999 for arg in shellsplit(stderr.splitlines()[-1]) 4000 if not is_in_out_arg(arg) and arg not in linker_args 4001 )