tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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     )