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# Code optimization
8# ==============================================================
9
10option("--disable-optimize", nargs="?", help="Disable optimizations via compiler flags")
11
12
13@depends("--enable-optimize")
14def moz_optimize(option):
15    flags = None
16
17    if len(option):
18        val = "2"
19        flags = option[0]
20    elif option:
21        val = "1"
22    else:
23        val = None
24
25    return namespace(
26        optimize=val,
27        flags=flags,
28    )
29
30
31set_config("MOZ_OPTIMIZE", moz_optimize.optimize)
32add_old_configure_assignment("MOZ_OPTIMIZE", moz_optimize.optimize)
33add_old_configure_assignment("MOZ_CONFIGURE_OPTIMIZE_FLAGS", moz_optimize.flags)
34
35# Android NDK
36# ==============================================================
37
38
39@depends("--disable-compile-environment", target)
40def compiling_android(compile_env, target):
41    return compile_env and target.os == "Android"
42
43
44include("android-ndk.configure", when=compiling_android)
45
46with only_when(target_is_osx):
47    # MacOS deployment target version
48    # ==============================================================
49    # This needs to happen before any compilation test is done.
50
51    option(
52        "--enable-macos-target",
53        env="MACOSX_DEPLOYMENT_TARGET",
54        nargs=1,
55        default=depends(target)(lambda t: "11.0" if t.cpu == "aarch64" else "10.12"),
56        help="Set the minimum MacOS version needed at runtime{|}",
57    )
58
59    @depends("--enable-macos-target")
60    @imports(_from="os", _import="environ")
61    def macos_target(value):
62        if value:
63            # Ensure every compiler process we spawn uses this value.
64            environ["MACOSX_DEPLOYMENT_TARGET"] = value[0]
65            return value[0]
66
67    set_config("MACOSX_DEPLOYMENT_TARGET", macos_target)
68    add_old_configure_assignment("MACOSX_DEPLOYMENT_TARGET", macos_target)
69
70
71@depends(host)
72def host_is_osx(host):
73    if host.os == "OSX":
74        return True
75
76
77with only_when(host_is_osx | target_is_osx):
78    # MacOS SDK
79    # =========
80    option(
81        "--with-macos-sdk",
82        env="MACOS_SDK_DIR",
83        nargs=1,
84        help="Location of platform SDK to use",
85    )
86
87    @depends("--with-macos-sdk", host)
88    @imports(_from="__builtin__", _import="open")
89    @imports(_from="os.path", _import="isdir")
90    @imports("plistlib")
91    def macos_sdk(sdk, host):
92        # When we change the SDK we build with, please update the manual SDK
93        # installation docs:
94        # https://firefox-source-docs.mozilla.org/setup/macos_build.html#macos-sdk-is-unsupported
95        sdk_min_version = Version("10.12")
96
97        if sdk:
98            sdk = sdk[0]
99        elif host.os == "OSX":
100            sdk = check_cmd_output(
101                "xcrun", "--show-sdk-path", onerror=lambda: ""
102            ).rstrip()
103            if not sdk:
104                die(
105                    "Could not find the macOS SDK. Please use --with-macos-sdk to give "
106                    "the path to a macOS SDK."
107                )
108        else:
109            die(
110                "Need a macOS SDK when targeting macOS. Please use --with-macos-sdk "
111                "to give the path to a macOS SDK."
112            )
113
114        if not isdir(sdk):
115            die(
116                "SDK not found in %s. When using --with-macos-sdk, you must specify a "
117                "valid SDK. SDKs are installed when the optional cross-development "
118                "tools are selected during the Xcode/Developer Tools installation."
119                % sdk
120            )
121        with open(os.path.join(sdk, "SDKSettings.plist"), "rb") as plist:
122            obj = plistlib.load(plist)
123        if not obj:
124            die("Error parsing SDKSettings.plist in the SDK directory: %s" % sdk)
125        if "Version" not in obj:
126            die(
127                "Error finding Version information in SDKSettings.plist from the SDK: %s"
128                % sdk
129            )
130        version = Version(obj["Version"])
131        sdk_installation_docs_url = "https://firefox-source-docs.mozilla.org/setup/macos_build.html#macos-sdk-is-unsupported"
132        if version < sdk_min_version:
133            die(
134                'SDK version "%s" is too old. Please upgrade to at least %s. Try '
135                "updating your system Xcode. If that's not sufficient, see the manual "
136                "SDK installation docs: %s"
137                % (version, sdk_min_version, sdk_installation_docs_url)
138            )
139        return sdk
140
141    set_config("MACOS_SDK_DIR", macos_sdk)
142
143
144with only_when(target_is_osx):
145    with only_when(cross_compiling):
146        option(
147            "--with-macos-private-frameworks",
148            env="MACOS_PRIVATE_FRAMEWORKS_DIR",
149            nargs=1,
150            help="Location of private frameworks to use",
151        )
152
153        @depends_if("--with-macos-private-frameworks")
154        @imports(_from="os.path", _import="isdir")
155        def macos_private_frameworks(value):
156            if value and not isdir(value[0]):
157                die(
158                    "PrivateFrameworks not found not found in %s. When using "
159                    "--with-macos-private-frameworks, you must specify a valid "
160                    "directory",
161                    value[0],
162                )
163            return value[0]
164
165    @depends(macos_private_frameworks, macos_sdk)
166    def macos_private_frameworks(value, sdk):
167        if value:
168            return value
169        return os.path.join(sdk or "/", "System/Library/PrivateFrameworks")
170
171    set_config("MACOS_PRIVATE_FRAMEWORKS_DIR", macos_private_frameworks)
172
173
174# GC rooting and hazard analysis.
175# ==============================================================
176option(env="MOZ_HAZARD", help="Build for the GC rooting hazard analysis")
177
178
179@depends("MOZ_HAZARD")
180def hazard_analysis(value):
181    if value:
182        return True
183
184
185set_config("MOZ_HAZARD", hazard_analysis)
186
187
188# Cross-compilation related things.
189# ==============================================================
190option(
191    "--with-toolchain-prefix",
192    env="TOOLCHAIN_PREFIX",
193    nargs=1,
194    help="Prefix for the target toolchain",
195)
196
197
198@depends("--with-toolchain-prefix", host, target, cross_compiling)
199def toolchain_prefix(value, host, target, cross_compiling):
200    if value:
201        return tuple(value)
202    # We don't want a toolchain prefix by default when building on mac for mac.
203    if cross_compiling and not (target.os == "OSX" and host.os == "OSX"):
204        return ("%s-" % target.toolchain, "%s-" % target.alias)
205
206
207@depends(toolchain_prefix, target)
208def first_toolchain_prefix(toolchain_prefix, target):
209    # Pass TOOLCHAIN_PREFIX down to the build system if it was given from the
210    # command line/environment (in which case there's only one value in the tuple),
211    # or when cross-compiling for Android or OSX.
212    if toolchain_prefix and (
213        target.os in ("Android", "OSX") or len(toolchain_prefix) == 1
214    ):
215        return toolchain_prefix[0]
216
217
218set_config("TOOLCHAIN_PREFIX", first_toolchain_prefix)
219add_old_configure_assignment("TOOLCHAIN_PREFIX", first_toolchain_prefix)
220
221
222# Compilers
223# ==============================================================
224include("compilers-util.configure")
225
226
227def try_preprocess(compiler, language, source, onerror=None):
228    return try_invoke_compiler(compiler, language, source, ["-E"], onerror)
229
230
231@imports(_from="mozbuild.configure.constants", _import="CompilerType")
232@imports(_from="mozbuild.configure.constants", _import="CPU_preprocessor_checks")
233@imports(_from="mozbuild.configure.constants", _import="kernel_preprocessor_checks")
234@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
235@imports(_from="six", _import="iteritems")
236@imports(_from="textwrap", _import="dedent")
237@imports(_from="__builtin__", _import="Exception")
238def get_compiler_info(compiler, language):
239    """Returns information about the given `compiler` (command line in the
240    form of a list or tuple), in the given `language`.
241
242    The returned information includes:
243    - the compiler type (clang-cl, clang or gcc)
244    - the compiler version
245    - the compiler supported language
246    - the compiler supported language version
247    """
248    # Xcode clang versions are different from the underlying llvm version (they
249    # instead are aligned with the Xcode version). Fortunately, we can tell
250    # apart plain clang from Xcode clang, and convert the Xcode clang version
251    # into the more or less corresponding plain clang version.
252    check = dedent(
253        """\
254        #if defined(_MSC_VER) && defined(__clang__) && defined(_MT)
255        %COMPILER "clang-cl"
256        %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
257        #elif defined(__clang__)
258        %COMPILER "clang"
259        %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
260        #  ifdef __apple_build_version__
261        %XCODE 1
262        #  endif
263        #elif defined(__GNUC__) && !defined(__MINGW32__)
264        %COMPILER "gcc"
265        %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
266        #endif
267
268        #if __cplusplus
269        %cplusplus __cplusplus
270        #elif __STDC_VERSION__
271        %STDC_VERSION __STDC_VERSION__
272        #endif
273    """
274    )
275
276    # While we're doing some preprocessing, we might as well do some more
277    # preprocessor-based tests at the same time, to check the toolchain
278    # matches what we want.
279    for name, preprocessor_checks in (
280        ("CPU", CPU_preprocessor_checks),
281        ("KERNEL", kernel_preprocessor_checks),
282        ("OS", OS_preprocessor_checks),
283    ):
284        for n, (value, condition) in enumerate(iteritems(preprocessor_checks)):
285            check += dedent(
286                """\
287                #%(if)s %(condition)s
288                %%%(name)s "%(value)s"
289            """
290                % {
291                    "if": "elif" if n else "if",
292                    "condition": condition,
293                    "name": name,
294                    "value": value,
295                }
296            )
297        check += "#endif\n"
298
299    # Also check for endianness. The advantage of living in modern times is
300    # that all the modern compilers we support now have __BYTE_ORDER__ defined
301    # by the preprocessor.
302    check += dedent(
303        """\
304        #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
305        %ENDIANNESS "little"
306        #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
307        %ENDIANNESS "big"
308        #endif
309    """
310    )
311
312    result = try_preprocess(compiler, language, check)
313
314    if not result:
315        raise FatalCheckError("Unknown compiler or compiler not supported.")
316
317    # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may
318    # have non-ASCII characters. Treat the output as bytearray.
319    data = {}
320    for line in result.splitlines():
321        if line.startswith("%"):
322            k, _, v = line.partition(" ")
323            k = k.lstrip("%")
324            data[k] = v.replace(" ", "").lstrip('"').rstrip('"')
325            log.debug("%s = %s", k, data[k])
326
327    try:
328        type = CompilerType(data["COMPILER"])
329    except Exception:
330        raise FatalCheckError("Unknown compiler or compiler not supported.")
331
332    cplusplus = int(data.get("cplusplus", "0L").rstrip("L"))
333    stdc_version = int(data.get("STDC_VERSION", "0L").rstrip("L"))
334
335    version = data.get("VERSION")
336    if version:
337        version = Version(version)
338        if data.get("XCODE"):
339            # Derived from https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
340            # with enough granularity for major.minor version checks further
341            # down the line
342            if version < "9.1":
343                version = Version("4.0.0.or.less")
344            elif version < "10.0":
345                version = Version("5.0.2")
346            elif version < "10.0.1":
347                version = Version("6.0.1")
348            elif version < "11.0":
349                version = Version("7.0.0")
350            elif version < "11.0.3":
351                version = Version("8.0.0")
352            elif version < "12.0":
353                version = Version("9.0.0")
354            elif version < "12.0.1":
355                version = Version("10.0.0")
356            else:
357                version = Version("10.0.0.or.more")
358
359    return namespace(
360        type=type,
361        version=version,
362        cpu=data.get("CPU"),
363        kernel=data.get("KERNEL"),
364        endianness=data.get("ENDIANNESS"),
365        os=data.get("OS"),
366        language="C++" if cplusplus else "C",
367        language_version=cplusplus if cplusplus else stdc_version,
368        xcode=bool(data.get("XCODE")),
369    )
370
371
372def same_arch_different_bits():
373    return (
374        ("x86", "x86_64"),
375        ("ppc", "ppc64"),
376        ("sparc", "sparc64"),
377    )
378
379
380@imports(_from="mozbuild.shellutil", _import="quote")
381@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
382def check_compiler(compiler, language, target):
383    info = get_compiler_info(compiler, language)
384
385    flags = []
386
387    # Check language standards
388    # --------------------------------------------------------------------
389    if language != info.language:
390        raise FatalCheckError(
391            "`%s` is not a %s compiler." % (quote(*compiler), language)
392        )
393
394    # Note: We do a strict version check because there sometimes are backwards
395    # incompatible changes in the standard, and not all code that compiles as
396    # C99 compiles as e.g. C11 (as of writing, this is true of libnestegg, for
397    # example)
398    if info.language == "C" and info.language_version != 199901:
399        if info.type == "clang-cl":
400            flags.append("-Xclang")
401        flags.append("-std=gnu99")
402
403    cxx17_version = 201703
404    if info.language == "C++":
405        if info.language_version != cxx17_version:
406            # MSVC headers include C++17 features, but don't guard them
407            # with appropriate checks.
408            if info.type == "clang-cl":
409                flags.append("-Xclang")
410                flags.append("-std=c++17")
411            else:
412                flags.append("-std=gnu++17")
413
414    # Check compiler target
415    # --------------------------------------------------------------------
416    has_target = False
417    if info.type == "clang":
418        # Add the target explicitly when the target is aarch64 macosx, because
419        # the Xcode clang target is named differently, and we need to work around
420        # https://github.com/rust-lang/rust-bindgen/issues/1871 and
421        # https://github.com/alexcrichton/cc-rs/issues/542 so we always want
422        # the target on the command line, even if the compiler would default to
423        # that.
424        if info.xcode and target.os == "OSX" and target.cpu == "aarch64":
425            if "--target=arm64-apple-darwin" not in compiler:
426                flags.append("--target=arm64-apple-darwin")
427            has_target = True
428
429        elif (
430            not info.kernel
431            or info.kernel != target.kernel
432            or not info.endianness
433            or info.endianness != target.endianness
434        ):
435            flags.append("--target=%s" % target.toolchain)
436            has_target = True
437
438        # Add target flag when there is an OS mismatch (e.g. building for Android on
439        # Linux). However, only do this if the target OS is in our whitelist, to
440        # keep things the same on other platforms.
441        elif target.os in OS_preprocessor_checks and (
442            not info.os or info.os != target.os
443        ):
444            flags.append("--target=%s" % target.toolchain)
445            has_target = True
446
447    if not has_target and (not info.cpu or info.cpu != target.cpu):
448        same_arch = same_arch_different_bits()
449        if (target.cpu, info.cpu) in same_arch:
450            flags.append("-m32")
451        elif (info.cpu, target.cpu) in same_arch:
452            flags.append("-m64")
453        elif info.type == "clang-cl" and target.cpu == "aarch64":
454            flags.append("--target=%s" % target.toolchain)
455        elif info.type == "clang":
456            flags.append("--target=%s" % target.toolchain)
457
458    return namespace(
459        type=info.type,
460        version=info.version,
461        target_cpu=info.cpu,
462        target_kernel=info.kernel,
463        target_endianness=info.endianness,
464        target_os=info.os,
465        flags=flags,
466    )
467
468
469@imports(_from="__builtin__", _import="open")
470@imports("json")
471@imports("os")
472def get_vc_paths(topsrcdir):
473    def vswhere(args):
474        program_files = os.environ.get("PROGRAMFILES(X86)") or os.environ.get(
475            "PROGRAMFILES"
476        )
477        if not program_files:
478            return []
479        vswhere = os.path.join(
480            program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe"
481        )
482        if not os.path.exists(vswhere):
483            return []
484        return json.loads(check_cmd_output(vswhere, "-format", "json", *args))
485
486    for install in vswhere(
487        [
488            "-products",
489            "*",
490            "-requires",
491            "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
492        ]
493    ):
494        path = install["installationPath"]
495        tools_version = (
496            open(
497                os.path.join(
498                    path, r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"
499                ),
500                "r",
501            )
502            .read()
503            .strip()
504        )
505        tools_path = os.path.join(path, r"VC\Tools\MSVC", tools_version)
506        yield (Version(install["installationVersion"]), tools_path)
507
508
509@depends(host)
510def host_is_windows(host):
511    if host.kernel == "WINNT":
512        return True
513
514
515option(
516    "--with-visual-studio-version",
517    nargs=1,
518    choices=("2017",),
519    when=host_is_windows,
520    help="Select a specific Visual Studio version to use",
521)
522
523
524@depends("--with-visual-studio-version", when=host_is_windows)
525def vs_major_version(value):
526    if value:
527        return {"2017": 15}[value[0]]
528
529
530option(
531    env="VC_PATH",
532    nargs=1,
533    when=host_is_windows,
534    help="Path to the Microsoft Visual C/C++ compiler",
535)
536
537
538@depends(
539    host,
540    vs_major_version,
541    check_build_environment,
542    "VC_PATH",
543    "--with-visual-studio-version",
544    when=host_is_windows,
545)
546@imports(_from="operator", _import="itemgetter")
547def vc_compiler_paths_for_version(
548    host, vs_major_version, env, vc_path, vs_release_name
549):
550    if vc_path and vs_release_name:
551        die("VC_PATH and --with-visual-studio-version cannot be used together.")
552    if vc_path:
553        # Use an arbitrary version, it doesn't matter.
554        all_versions = [(Version("15"), vc_path[0])]
555    else:
556        all_versions = sorted(get_vc_paths(env.topsrcdir), key=itemgetter(0))
557    if not all_versions:
558        return
559    if vs_major_version:
560        versions = [d for (v, d) in all_versions if v.major == vs_major_version]
561        if not versions:
562            die("Visual Studio %s could not be found!" % vs_release_name)
563        path = versions[0]
564    else:
565        # Choose the newest version.
566        path = all_versions[-1][1]
567    host_dir = {
568        "x86_64": "HostX64",
569        "x86": "HostX86",
570    }.get(host.cpu)
571    if host_dir:
572        path = os.path.join(path, "bin", host_dir)
573        return {
574            "x64": [os.path.join(path, "x64")],
575            # The cross toolchains require DLLs from the native x64 toolchain.
576            "x86": [os.path.join(path, "x86"), os.path.join(path, "x64")],
577            "arm64": [os.path.join(path, "arm64"), os.path.join(path, "x64")],
578        }
579
580
581@depends(target, vc_compiler_paths_for_version, when=host_is_windows)
582def vc_compiler_path(target, paths):
583    vc_target = {
584        "x86": "x86",
585        "x86_64": "x64",
586        "arm": "arm",
587        "aarch64": "arm64",
588    }.get(target.cpu)
589    if not paths:
590        return
591    return paths.get(vc_target)
592
593
594@depends(vc_compiler_path, original_path)
595@imports("os")
596@imports(_from="os", _import="environ")
597def vc_toolchain_search_path(vc_compiler_path, original_path):
598    result = list(original_path)
599
600    if vc_compiler_path:
601        # The second item, if there is one, is necessary to have in $PATH for
602        # Windows to load the required DLLs from there.
603        if len(vc_compiler_path) > 1:
604            environ["PATH"] = os.pathsep.join(result + vc_compiler_path[1:])
605
606        # The first item is where the programs are going to be
607        result.append(vc_compiler_path[0])
608
609    return result
610
611
612clang_search_path = bootstrap_search_path("clang/bin")
613
614
615@depends(
616    bootstrap_search_path("rustc/bin", when="MOZ_AUTOMATION"),
617    bootstrap_search_path_order,
618    original_path,
619)
620@imports("os")
621@imports(_from="os", _import="environ")
622def rust_search_path(rust_path, search_order, original_path):
623    result = list(rust_path or original_path)
624    # Also add the rustup install directory for cargo/rustc.
625    cargo_home = environ.get("CARGO_HOME", "")
626    if cargo_home:
627        cargo_home = os.path.abspath(cargo_home)
628    else:
629        cargo_home = os.path.expanduser(os.path.join("~", ".cargo"))
630    rustup_path = os.path.join(cargo_home, "bin")
631    if search_order == "prepend":
632        result.insert(0, rustup_path)
633    else:
634        result.append(rustup_path)
635    return result
636
637
638# As a workaround until bug 1516228 and bug 1516253 are fixed, set the PATH
639# variable for the build to contain the toolchain search path.
640@depends(vc_toolchain_search_path)
641@imports("os")
642@imports(_from="os", _import="environ")
643def altered_path(vc_toolchain_search_path):
644    path = environ["PATH"].split(os.pathsep)
645    altered_path = list(vc_toolchain_search_path)
646    for p in path:
647        if p not in altered_path:
648            altered_path.append(p)
649    return os.pathsep.join(altered_path)
650
651
652set_config("PATH", altered_path)
653
654
655# Compiler wrappers
656# ==============================================================
657option(
658    "--with-compiler-wrapper",
659    env="COMPILER_WRAPPER",
660    nargs=1,
661    help="Enable compiling with wrappers such as distcc and ccache",
662)
663
664option("--with-ccache", env="CCACHE", nargs="?", help="Enable compiling with ccache")
665
666
667@depends_if("--with-ccache")
668def ccache(value):
669    if len(value):
670        return value
671    # If --with-ccache was given without an explicit value, we default to
672    # 'ccache'.
673    return "ccache"
674
675
676ccache = check_prog(
677    "CCACHE",
678    progs=(),
679    input=ccache,
680    paths=bootstrap_search_path(
681        "sccache", when=depends("CCACHE")(lambda c: len(c) and c[0] == "sccache")
682    ),
683    allow_missing=True,
684)
685
686option(env="CCACHE_PREFIX", nargs=1, help="Compiler prefix to use when using ccache")
687
688ccache_prefix = depends_if("CCACHE_PREFIX")(lambda prefix: prefix[0])
689set_config("CCACHE_PREFIX", ccache_prefix)
690
691# Distinguish ccache from sccache.
692
693
694@depends_if(ccache)
695def ccache_is_sccache(ccache):
696    return check_cmd_output(ccache, "--version").startswith("sccache")
697
698
699@depends(ccache, ccache_is_sccache)
700def using_ccache(ccache, ccache_is_sccache):
701    return ccache and not ccache_is_sccache
702
703
704@depends_if(ccache, ccache_is_sccache)
705def using_sccache(ccache, ccache_is_sccache):
706    return ccache and ccache_is_sccache
707
708
709option(env="RUSTC_WRAPPER", nargs=1, help="Wrap rust compilation with given tool")
710
711
712@depends(ccache, ccache_is_sccache, "RUSTC_WRAPPER")
713@imports(_from="textwrap", _import="dedent")
714@imports("os")
715def check_sccache_version(ccache, ccache_is_sccache, rustc_wrapper):
716    sccache_min_version = Version("0.2.13")
717
718    def check_version(path):
719        out = check_cmd_output(path, "--version")
720        version = Version(out.rstrip().split()[-1])
721        if version < sccache_min_version:
722            die(
723                dedent(
724                    """\
725            sccache %s or later is required. sccache in use at %s has
726            version %s.
727
728            Please upgrade or acquire a new version with |./mach bootstrap|.
729            """
730                ),
731                sccache_min_version,
732                path,
733                version,
734            )
735
736    if ccache and ccache_is_sccache:
737        check_version(ccache)
738
739    if rustc_wrapper and (
740        os.path.splitext(os.path.basename(rustc_wrapper[0]))[0].lower() == "sccache"
741    ):
742        check_version(rustc_wrapper[0])
743
744
745set_config("MOZ_USING_CCACHE", using_ccache)
746set_config("MOZ_USING_SCCACHE", using_sccache)
747
748option(env="SCCACHE_VERBOSE_STATS", help="Print verbose sccache stats after build")
749
750
751@depends(using_sccache, "SCCACHE_VERBOSE_STATS")
752def sccache_verbose_stats(using_sccache, verbose_stats):
753    return using_sccache and bool(verbose_stats)
754
755
756set_config("SCCACHE_VERBOSE_STATS", sccache_verbose_stats)
757
758
759@depends("--with-compiler-wrapper", ccache)
760@imports(_from="mozbuild.shellutil", _import="split", _as="shell_split")
761def compiler_wrapper(wrapper, ccache):
762    if wrapper:
763        raw_wrapper = wrapper[0]
764        wrapper = shell_split(raw_wrapper)
765        wrapper_program = find_program(wrapper[0])
766        if not wrapper_program:
767            die(
768                "Cannot find `%s` from the given compiler wrapper `%s`",
769                wrapper[0],
770                raw_wrapper,
771            )
772        wrapper[0] = wrapper_program
773
774    if ccache:
775        if wrapper:
776            return tuple([ccache] + wrapper)
777        else:
778            return (ccache,)
779    elif wrapper:
780        return tuple(wrapper)
781
782
783@depends_if(compiler_wrapper)
784def using_compiler_wrapper(compiler_wrapper):
785    return True
786
787
788set_config("MOZ_USING_COMPILER_WRAPPER", using_compiler_wrapper)
789
790
791@template
792def default_c_compilers(host_or_target, other_c_compiler=None):
793    """Template defining the set of default C compilers for the host and
794    target platforms.
795    `host_or_target` is either `host` or `target` (the @depends functions
796    from init.configure.
797    `other_c_compiler` is the `target` C compiler when `host_or_target` is `host`.
798    """
799    assert host_or_target in {host, target}
800
801    other_c_compiler = () if other_c_compiler is None else (other_c_compiler,)
802
803    @depends(host_or_target, target, toolchain_prefix, *other_c_compiler)
804    def default_c_compilers(
805        host_or_target, target, toolchain_prefix, *other_c_compiler
806    ):
807        if host_or_target.kernel == "WINNT":
808            supported = types = ("clang-cl", "clang")
809        elif host_or_target.kernel == "Darwin":
810            types = ("clang",)
811            supported = ("clang", "gcc")
812        else:
813            supported = types = ("clang", "gcc")
814
815        info = other_c_compiler[0] if other_c_compiler else None
816        if info and info.type in supported:
817            # When getting default C compilers for the host, we prioritize the
818            # same compiler as the target C compiler.
819            prioritized = info.compiler
820            if info.type == "gcc":
821                same_arch = same_arch_different_bits()
822                if (
823                    target.cpu != host_or_target.cpu
824                    and (target.cpu, host_or_target.cpu) not in same_arch
825                    and (host_or_target.cpu, target.cpu) not in same_arch
826                ):
827                    # If the target C compiler is GCC, and it can't be used with
828                    # -m32/-m64 for the host, it's probably toolchain-prefixed,
829                    # so we prioritize a raw 'gcc' instead.
830                    prioritized = info.type
831
832            types = [prioritized] + [t for t in types if t != info.type]
833
834        gcc = ("gcc",)
835        if toolchain_prefix and host_or_target is target:
836            gcc = tuple("%sgcc" % p for p in toolchain_prefix) + gcc
837
838        result = []
839        for type in types:
840            if type == "gcc":
841                result.extend(gcc)
842            else:
843                result.append(type)
844
845        return tuple(result)
846
847    return default_c_compilers
848
849
850@template
851def default_cxx_compilers(c_compiler, other_c_compiler=None, other_cxx_compiler=None):
852    """Template defining the set of default C++ compilers for the host and
853    target platforms.
854    `c_compiler` is the @depends function returning a Compiler instance for
855    the desired platform.
856
857    Because the build system expects the C and C++ compilers to be from the
858    same compiler suite, we derive the default C++ compilers from the C
859    compiler that was found if none was provided.
860
861    We also factor in the target C++ compiler when getting the default host
862    C++ compiler, using the target C++ compiler if the host and target C
863    compilers are the same.
864    """
865
866    assert (other_c_compiler is None) == (other_cxx_compiler is None)
867    if other_c_compiler is not None:
868        other_compilers = (other_c_compiler, other_cxx_compiler)
869    else:
870        other_compilers = ()
871
872    @depends(c_compiler, *other_compilers)
873    def default_cxx_compilers(c_compiler, *other_compilers):
874        if other_compilers:
875            other_c_compiler, other_cxx_compiler = other_compilers
876            if other_c_compiler.compiler == c_compiler.compiler:
877                return (other_cxx_compiler.compiler,)
878
879        dir = os.path.dirname(c_compiler.compiler)
880        file = os.path.basename(c_compiler.compiler)
881
882        if c_compiler.type == "gcc":
883            return (os.path.join(dir, file.replace("gcc", "g++")),)
884
885        if c_compiler.type == "clang":
886            return (os.path.join(dir, file.replace("clang", "clang++")),)
887
888        return (c_compiler.compiler,)
889
890    return default_cxx_compilers
891
892
893@template
894def provided_program(env_var, when=None):
895    """Template handling cases where a program can be specified either as a
896    path or as a path with applicable arguments.
897    """
898
899    @depends_if(env_var, when=when)
900    @imports(_from="itertools", _import="takewhile")
901    @imports(_from="mozbuild.shellutil", _import="split", _as="shell_split")
902    def provided(cmd):
903        # Assume the first dash-prefixed item (and any subsequent items) are
904        # command-line options, the item before the dash-prefixed item is
905        # the program we're looking for, and anything before that is a wrapper
906        # of some kind (e.g. sccache).
907        cmd = shell_split(cmd[0])
908
909        without_flags = list(takewhile(lambda x: not x.startswith("-"), cmd))
910
911        return namespace(
912            wrapper=without_flags[:-1],
913            program=without_flags[-1],
914            flags=cmd[len(without_flags) :],
915        )
916
917    return provided
918
919
920option(
921    "--with-sysroot",
922    env="SYSROOT",
923    nargs=1,
924    when=target_is_linux_or_wasi,
925    help="Build using the given sysroot directory",
926)
927
928sysroot_input = depends("--with-sysroot", when=target_is_linux_or_wasi)(lambda x: x)
929bootstrap_sysroot = depends(target_is_linux, sysroot_input)(
930    lambda linux, input: linux and not input and input.origin == "default"
931)
932
933# We'll re-evaluate later, but for now, don't use the sysroot automatically
934# even if it exists, unless --enable-bootstrap was passed explicitly, or when
935# building on automation.
936@depends(
937    sysroot_input,
938    bootstrap_path("sysroot", context=target, when=bootstrap_sysroot),
939    "--enable-bootstrap",
940    "MOZ_AUTOMATION",
941)
942def sysroot_path(sysroot_input, path, bootstrap, automation):
943    if sysroot_input:
944        path = sysroot_input[0]
945    elif sysroot_input is not None and sysroot_input.origin != "default":
946        return
947    if sysroot_input or (bootstrap and bootstrap.origin != "default") or automation:
948        if path:
949            log.info("Using sysroot in %s", path)
950        return path
951
952
953@template
954def sysroot_flags(host_or_target):
955    @depends(
956        host_or_target, macos_sdk, sysroot_path if host_or_target is target else never
957    )
958    def sysroot_flags(host_or_target, macos_sdk, sysroot_path):
959        if macos_sdk and host_or_target.os == "OSX":
960            return ["-isysroot", macos_sdk]
961        if sysroot_path:
962            return ["--sysroot", sysroot_path]
963        return []
964
965    return sysroot_flags
966
967
968host_sysroot_flags = sysroot_flags(host)
969target_sysroot_flags = sysroot_flags(target)
970
971
972@depends(target, when=sysroot_path)
973def multiarch_dir(target):
974    if target.cpu == "x86":
975        # Turn e.g. i686-linux-gnu into i386-linux-gnu
976        return target.toolchain.replace(target.raw_cpu, "i386")
977    return target.toolchain
978
979
980def minimum_gcc_version():
981    return Version("7.1.0")
982
983
984@template
985def compiler(
986    language,
987    host_or_target,
988    c_compiler=None,
989    other_compiler=None,
990    other_c_compiler=None,
991):
992    """Template handling the generic base checks for the compiler for the
993    given `language` on the given platform (`host_or_target`).
994    `host_or_target` is either `host` or `target` (the @depends functions
995    from init.configure.
996    When the language is 'C++', `c_compiler` is the result of the `compiler`
997    template for the language 'C' for the same `host_or_target`.
998    When `host_or_target` is `host`, `other_compiler` is the result of the
999    `compiler` template for the same `language` for `target`.
1000    When `host_or_target` is `host` and the language is 'C++',
1001    `other_c_compiler` is the result of the `compiler` template for the
1002    language 'C' for `target`.
1003    """
1004    assert host_or_target in {host, target}
1005    assert language in ("C", "C++")
1006    assert language == "C" or c_compiler is not None
1007    assert host_or_target is target or other_compiler is not None
1008    assert language == "C" or host_or_target is target or other_c_compiler is not None
1009
1010    host_or_target_str = {
1011        host: "host",
1012        target: "target",
1013    }[host_or_target]
1014
1015    sysroot_flags = {
1016        host: host_sysroot_flags,
1017        target: target_sysroot_flags,
1018    }[host_or_target]
1019
1020    var = {
1021        ("C", target): "CC",
1022        ("C++", target): "CXX",
1023        ("C", host): "HOST_CC",
1024        ("C++", host): "HOST_CXX",
1025    }[language, host_or_target]
1026
1027    default_compilers = {
1028        "C": lambda: default_c_compilers(host_or_target, other_compiler),
1029        "C++": lambda: default_cxx_compilers(
1030            c_compiler, other_c_compiler, other_compiler
1031        ),
1032    }[language]()
1033
1034    what = "the %s %s compiler" % (host_or_target_str, language)
1035
1036    option(env=var, nargs=1, help="Path to %s" % what)
1037
1038    # Handle the compiler given by the user through one of the CC/CXX/HOST_CC/
1039    # HOST_CXX variables.
1040    provided_compiler = provided_program(var)
1041
1042    # Normally, we'd use `var` instead of `_var`, but the interaction with
1043    # old-configure complicates things, and for now, we a) can't take the plain
1044    # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
1045    # old-configure AC_SUBST it (because it's autoconf doing it, not us)
1046    compiler = check_prog(
1047        "_%s" % var,
1048        what=what,
1049        progs=default_compilers,
1050        input=provided_compiler.program,
1051        paths=clang_search_path,
1052    )
1053
1054    @depends(
1055        compiler, provided_compiler, compiler_wrapper, host_or_target, sysroot_flags
1056    )
1057    @checking("whether %s can be used" % what, lambda x: bool(x))
1058    @imports(_from="mozbuild.shellutil", _import="quote")
1059    def valid_compiler(
1060        compiler, provided_compiler, compiler_wrapper, host_or_target, sysroot_flags
1061    ):
1062        wrapper = list(compiler_wrapper or ())
1063        flags = list(sysroot_flags or ())
1064        if provided_compiler:
1065            provided_wrapper = list(provided_compiler.wrapper)
1066            # When doing a subconfigure, the compiler is set by old-configure
1067            # and it contains the wrappers from --with-compiler-wrapper and
1068            # --with-ccache.
1069            if provided_wrapper[: len(wrapper)] == wrapper:
1070                provided_wrapper = provided_wrapper[len(wrapper) :]
1071            wrapper.extend(provided_wrapper)
1072            flags.extend(provided_compiler.flags)
1073
1074        info = check_compiler(wrapper + [compiler] + flags, language, host_or_target)
1075
1076        # Check that the additional flags we got are enough to not require any
1077        # more flags. If we get an exception, just ignore it; it's liable to be
1078        # invalid command-line flags, which means the compiler we're checking
1079        # doesn't support those command-line flags and will fail one or more of
1080        # the checks below.
1081        try:
1082            if info.flags:
1083                flags += info.flags
1084                info = check_compiler(
1085                    wrapper + [compiler] + flags, language, host_or_target
1086                )
1087        except FatalCheckError:
1088            pass
1089
1090        if not info.target_cpu or info.target_cpu != host_or_target.cpu:
1091            raise FatalCheckError(
1092                "%s %s compiler target CPU (%s) does not match --%s CPU (%s)"
1093                % (
1094                    host_or_target_str.capitalize(),
1095                    language,
1096                    info.target_cpu or "unknown",
1097                    host_or_target_str,
1098                    host_or_target.raw_cpu,
1099                )
1100            )
1101
1102        if not info.target_kernel or (info.target_kernel != host_or_target.kernel):
1103            raise FatalCheckError(
1104                "%s %s compiler target kernel (%s) does not match --%s kernel (%s)"
1105                % (
1106                    host_or_target_str.capitalize(),
1107                    language,
1108                    info.target_kernel or "unknown",
1109                    host_or_target_str,
1110                    host_or_target.kernel,
1111                )
1112            )
1113
1114        if not info.target_endianness or (
1115            info.target_endianness != host_or_target.endianness
1116        ):
1117            raise FatalCheckError(
1118                "%s %s compiler target endianness (%s) does not match --%s "
1119                "endianness (%s)"
1120                % (
1121                    host_or_target_str.capitalize(),
1122                    language,
1123                    info.target_endianness or "unknown",
1124                    host_or_target_str,
1125                    host_or_target.endianness,
1126                )
1127            )
1128
1129        # Compiler version checks
1130        # ===================================================
1131        # Check the compiler version here instead of in `compiler_version` so
1132        # that the `checking` message doesn't pretend the compiler can be used
1133        # to then bail out one line later.
1134        if info.type == "gcc":
1135            if host_or_target.os == "Android":
1136                raise FatalCheckError(
1137                    "GCC is not supported on Android.\n"
1138                    "Please use clang from the Android NDK instead."
1139                )
1140            gcc_version = minimum_gcc_version()
1141            if info.version < gcc_version:
1142                raise FatalCheckError(
1143                    "Only GCC %d.%d or newer is supported (found version %s)."
1144                    % (gcc_version.major, gcc_version.minor, info.version)
1145                )
1146
1147        if info.type == "clang-cl":
1148            if info.version < "8.0.0":
1149                raise FatalCheckError(
1150                    "Only clang-cl 8.0 or newer is supported (found version %s)"
1151                    % info.version
1152                )
1153
1154        # If you want to bump the version check here ensure the version
1155        # is known for Xcode in get_compiler_info.
1156        if info.type == "clang" and info.version < "5.0":
1157            raise FatalCheckError(
1158                "Only clang/llvm 5.0 or newer is supported (found version %s)."
1159                % info.version
1160            )
1161
1162        if info.flags:
1163            raise FatalCheckError("Unknown compiler or compiler not supported.")
1164
1165        return namespace(
1166            wrapper=wrapper,
1167            compiler=compiler,
1168            flags=flags,
1169            type=info.type,
1170            version=info.version,
1171            language=language,
1172        )
1173
1174    @depends(valid_compiler)
1175    @checking("%s version" % what)
1176    def compiler_version(compiler):
1177        return compiler.version
1178
1179    if language == "C++":
1180
1181        @depends(valid_compiler, c_compiler)
1182        def valid_compiler(compiler, c_compiler):
1183            if compiler.type != c_compiler.type:
1184                die(
1185                    "The %s C compiler is %s, while the %s C++ compiler is "
1186                    "%s. Need to use the same compiler suite.",
1187                    host_or_target_str,
1188                    c_compiler.type,
1189                    host_or_target_str,
1190                    compiler.type,
1191                )
1192
1193            if compiler.version != c_compiler.version:
1194                die(
1195                    "The %s C compiler is version %s, while the %s C++ "
1196                    "compiler is version %s. Need to use the same compiler "
1197                    "version.",
1198                    host_or_target_str,
1199                    c_compiler.version,
1200                    host_or_target_str,
1201                    compiler.version,
1202                )
1203            return compiler
1204
1205    # Set CC/CXX/HOST_CC/HOST_CXX for old-configure, which needs the wrapper
1206    # and the flags that were part of the user input for those variables to
1207    # be provided.
1208    add_old_configure_assignment(
1209        var,
1210        depends_if(valid_compiler)(
1211            lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)
1212        ),
1213    )
1214
1215    if host_or_target is target:
1216        add_old_configure_assignment(
1217            "ac_cv_prog_%s" % var,
1218            depends_if(valid_compiler)(
1219                lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)
1220            ),
1221        )
1222        # We check that it works in python configure already.
1223        add_old_configure_assignment("ac_cv_prog_%s_works" % var.lower(), "yes")
1224        add_old_configure_assignment(
1225            "ac_cv_prog_%s_cross" % var.lower(),
1226            depends(cross_compiling)(lambda x: "yes" if x else "no"),
1227        )
1228        gcc_like = depends(valid_compiler.type)(
1229            lambda x: "yes" if x in ("gcc", "clang") else "no"
1230        )
1231        add_old_configure_assignment("ac_cv_prog_%s_g" % var.lower(), gcc_like)
1232        if language == "C":
1233            add_old_configure_assignment("ac_cv_prog_gcc", gcc_like)
1234        if language == "C++":
1235            add_old_configure_assignment("ac_cv_prog_gxx", gcc_like)
1236
1237    # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
1238    # old-configure to do some of its still existing checks.
1239    if language == "C":
1240        set_config("%s_TYPE" % var, valid_compiler.type)
1241        add_old_configure_assignment("%s_TYPE" % var, valid_compiler.type)
1242        set_config(
1243            "%s_VERSION" % var, depends(valid_compiler.version)(lambda v: str(v))
1244        )
1245
1246    valid_compiler = compiler_class(valid_compiler, host_or_target)
1247
1248    def compiler_error():
1249        raise FatalCheckError(
1250            "Failed compiling a simple %s source with %s" % (language, what)
1251        )
1252
1253    valid_compiler.try_compile(check_msg="%s works" % what, onerror=compiler_error)
1254
1255    set_config("%s_BASE_FLAGS" % var, valid_compiler.flags)
1256
1257    # Set CPP/CXXCPP for both the build system and old-configure. We don't
1258    # need to check this works for preprocessing, because we already relied
1259    # on $CC -E/$CXX -E doing preprocessing work to validate the compiler
1260    # in the first place.
1261    if host_or_target is target:
1262        pp_var = {
1263            "C": "CPP",
1264            "C++": "CXXCPP",
1265        }[language]
1266
1267        preprocessor = depends_if(valid_compiler)(
1268            lambda x: list(x.wrapper) + [x.compiler, "-E"] + list(x.flags)
1269        )
1270
1271        set_config(pp_var, preprocessor)
1272        add_old_configure_assignment(pp_var, preprocessor)
1273
1274    if language == "C":
1275        linker_var = {
1276            target: "LD",
1277            host: "HOST_LD",
1278        }[host_or_target]
1279
1280        @deprecated_option(env=linker_var, nargs=1)
1281        def linker(value):
1282            if value:
1283                return value[0]
1284
1285        @depends(linker)
1286        def unused_linker(linker):
1287            if linker:
1288                log.warning(
1289                    "The value of %s is not used by this build system." % linker_var
1290                )
1291
1292    return valid_compiler
1293
1294
1295c_compiler = compiler("C", target)
1296cxx_compiler = compiler("C++", target, c_compiler=c_compiler)
1297host_c_compiler = compiler("C", host, other_compiler=c_compiler)
1298host_cxx_compiler = compiler(
1299    "C++",
1300    host,
1301    c_compiler=host_c_compiler,
1302    other_compiler=cxx_compiler,
1303    other_c_compiler=c_compiler,
1304)
1305
1306# Generic compiler-based conditions.
1307building_with_gcc = depends(c_compiler)(lambda info: info.type == "gcc")
1308
1309
1310@depends(cxx_compiler, ccache_prefix)
1311@imports("os")
1312def cxx_is_icecream(info, ccache_prefix):
1313    if (
1314        os.path.islink(info.compiler)
1315        and os.path.basename(os.readlink(info.compiler)) == "icecc"
1316    ):
1317        return True
1318    if ccache_prefix and os.path.basename(ccache_prefix) == "icecc":
1319        return True
1320
1321
1322set_config("CXX_IS_ICECREAM", cxx_is_icecream)
1323
1324
1325@depends(c_compiler)
1326def msvs_version(info):
1327    # clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to
1328    # be set for GYP on Windows.
1329    if info.type == "clang-cl":
1330        return "2017"
1331
1332    return ""
1333
1334
1335set_config("MSVS_VERSION", msvs_version)
1336
1337include("compile-checks.configure")
1338include("arm.configure", when=depends(target.cpu)(lambda cpu: cpu == "arm"))
1339
1340
1341@depends(host, host_os_kernel_major_version, target)
1342def needs_macos_sdk_headers_check(host, version, target):
1343    # Only an issue on Mac OS X 10.14 (and probably above).
1344    if host.kernel != "Darwin" or target.kernel != "Darwin" or version < "18":
1345        return
1346
1347    return True
1348
1349
1350@depends(
1351    cxx_compiler.try_run(
1352        header="#include_next <inttypes.h>",
1353        check_msg="for macOS SDK headers",
1354        when=needs_macos_sdk_headers_check,
1355    ),
1356    when=needs_macos_sdk_headers_check,
1357)
1358def check_have_mac_10_14_sdk(value):
1359    if value:
1360        return
1361
1362    die(
1363        "System inttypes.h not found. Please try running "
1364        "`open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg` "
1365        "and following the instructions to install the necessary headers"
1366    )
1367
1368
1369@depends(
1370    have_64_bit,
1371    try_compile(
1372        body='static_assert(sizeof(void *) == 8, "")', check_msg="for 64-bit OS"
1373    ),
1374)
1375def check_have_64_bit(have_64_bit, compiler_have_64_bit):
1376    if have_64_bit != compiler_have_64_bit:
1377        configure_error(
1378            "The target compiler does not agree with configure "
1379            "about the target bitness."
1380        )
1381
1382
1383@depends(cxx_compiler, target)
1384def needs_libstdcxx_newness_check(cxx_compiler, target):
1385    # We only have to care about this on Linux and MinGW.
1386    if cxx_compiler.type == "clang-cl":
1387        return
1388
1389    if target.kernel not in ("Linux", "WINNT"):
1390        return
1391
1392    if target.os == "Android":
1393        return
1394
1395    return True
1396
1397
1398def die_on_old_libstdcxx():
1399    die(
1400        "The libstdc++ in use is not new enough.  Please run "
1401        "./mach bootstrap to update your compiler, or update your system "
1402        "libstdc++ installation."
1403    )
1404
1405
1406try_compile(
1407    includes=["cstddef"],
1408    body="\n".join(
1409        [
1410            # _GLIBCXX_RELEASE showed up in libstdc++ 7.
1411            "#if defined(__GLIBCXX__) && !defined(_GLIBCXX_RELEASE)",
1412            "#  error libstdc++ not new enough",
1413            "#endif",
1414            "#if defined(_GLIBCXX_RELEASE)",
1415            "#  if _GLIBCXX_RELEASE < %d" % minimum_gcc_version().major,
1416            "#    error libstdc++ not new enough",
1417            "#  else",
1418            "     (void) 0",
1419            "#  endif",
1420            "#endif",
1421        ]
1422    ),
1423    check_msg="for new enough STL headers from libstdc++",
1424    when=needs_libstdcxx_newness_check,
1425    onerror=die_on_old_libstdcxx,
1426)
1427
1428
1429@depends(c_compiler, target)
1430def default_debug_flags(compiler_info, target):
1431    # Debug info is ON by default.
1432    if compiler_info.type == "clang-cl":
1433        return "-Z7"
1434    elif target.kernel == "WINNT" and compiler_info.type == "clang":
1435        return "-g -gcodeview"
1436    return "-g"
1437
1438
1439option(env="MOZ_DEBUG_FLAGS", nargs=1, help="Debug compiler flags")
1440
1441imply_option("--enable-debug-symbols", depends_if("--enable-debug")(lambda v: v))
1442
1443option(
1444    "--disable-debug-symbols",
1445    nargs="?",
1446    help="Disable debug symbols using the given compiler flags",
1447)
1448
1449set_config("MOZ_DEBUG_SYMBOLS", depends_if("--enable-debug-symbols")(lambda _: True))
1450
1451
1452@depends("MOZ_DEBUG_FLAGS", "--enable-debug-symbols", default_debug_flags)
1453def debug_flags(env_debug_flags, enable_debug_flags, default_debug_flags):
1454    # If MOZ_DEBUG_FLAGS is set, and --enable-debug-symbols is set to a value,
1455    # --enable-debug-symbols takes precedence. Note, the value of
1456    # --enable-debug-symbols may be implied by --enable-debug.
1457    if len(enable_debug_flags):
1458        return enable_debug_flags[0]
1459    if env_debug_flags:
1460        return env_debug_flags[0]
1461    return default_debug_flags
1462
1463
1464set_config("MOZ_DEBUG_FLAGS", debug_flags)
1465add_old_configure_assignment("MOZ_DEBUG_FLAGS", debug_flags)
1466
1467
1468@depends(c_compiler)
1469def color_cflags(info):
1470    # We could test compiling with flags. By why incur the overhead when
1471    # color support should always be present in a specific toolchain
1472    # version?
1473
1474    # Code for auto-adding this flag to compiler invocations needs to
1475    # determine if an existing flag isn't already present. That is likely
1476    # using exact string matching on the returned value. So if the return
1477    # value changes to e.g. "<x>=always", exact string match may fail and
1478    # multiple color flags could be added. So examine downstream consumers
1479    # before adding flags to return values.
1480    if info.type == "gcc":
1481        return "-fdiagnostics-color"
1482    elif info.type == "clang":
1483        return "-fcolor-diagnostics"
1484    else:
1485        return ""
1486
1487
1488set_config("COLOR_CFLAGS", color_cflags)
1489
1490# Some standard library headers (notably bionic on Android) declare standard
1491# functions (e.g. getchar()) and also #define macros for those standard
1492# functions.  libc++ deals with this by doing something like the following
1493# (explanatory comments added):
1494#
1495#   #ifdef FUNC
1496#   // Capture the definition of FUNC.
1497#   inline _LIBCPP_INLINE_VISIBILITY int __libcpp_FUNC(...) { return FUNC(...); }
1498#   #undef FUNC
1499#   // Use a real inline definition.
1500#   inline _LIBCPP_INLINE_VISIBILITY int FUNC(...) { return _libcpp_FUNC(...); }
1501#   #endif
1502#
1503# _LIBCPP_INLINE_VISIBILITY is typically defined as:
1504#
1505#   __attribute__((__visibility__("hidden"), __always_inline__))
1506#
1507# Unfortunately, this interacts badly with our system header wrappers, as the:
1508#
1509#   #pragma GCC visibility push(default)
1510#
1511# that they do prior to including the actual system header is treated by the
1512# compiler as an explicit declaration of visibility on every function declared
1513# in the header.  Therefore, when the libc++ code above is encountered, it is
1514# as though the compiler has effectively seen:
1515#
1516#   int FUNC(...) __attribute__((__visibility__("default")));
1517#   int FUNC(...) __attribute__((__visibility__("hidden")));
1518#
1519# and the compiler complains about the mismatched visibility declarations.
1520#
1521# However, libc++ will only define _LIBCPP_INLINE_VISIBILITY if there is no
1522# existing definition.  We can therefore define it to the empty string (since
1523# we are properly managing visibility ourselves) and avoid this whole mess.
1524# Note that we don't need to do this with gcc, as libc++ detects gcc and
1525# effectively does the same thing we are doing here.
1526#
1527# _LIBCPP_ALWAYS_INLINE needs a similar workarounds, since it too declares
1528# hidden visibility.
1529#
1530# _LIBCPP_HIDE_FROM_ABI is a macro in libc++ versions in NDKs >=r19.  It too
1531# declares hidden visibility, but it also declares functions as excluded from
1532# explicit instantiation (roughly: the function can be unused in the current
1533# compilation, but does not then trigger an actual definition of the function;
1534# it is assumed the real definition comes from elsewhere).  We need to replicate
1535# this setup.
1536
1537
1538@depends(c_compiler, target)
1539def libcxx_override_visibility(c_compiler, target):
1540    if c_compiler.type == "clang" and target.os == "Android":
1541        return namespace(
1542            empty="",
1543            hide_from_abi="__attribute__((__exclude_from_explicit_instantiation__))",
1544        )
1545
1546
1547set_define("_LIBCPP_INLINE_VISIBILITY", libcxx_override_visibility.empty)
1548set_define("_LIBCPP_ALWAYS_INLINE", libcxx_override_visibility.empty)
1549
1550set_define("_LIBCPP_HIDE_FROM_ABI", libcxx_override_visibility.hide_from_abi)
1551
1552
1553@depends(target, check_build_environment)
1554def visibility_flags(target, env):
1555    if target.os != "WINNT":
1556        if target.kernel == "Darwin":
1557            return ("-fvisibility=hidden", "-fvisibility-inlines-hidden")
1558        return (
1559            "-I%s/system_wrappers" % os.path.join(env.dist),
1560            "-include",
1561            "%s/config/gcc_hidden.h" % env.topsrcdir,
1562        )
1563
1564
1565@depends(target, visibility_flags)
1566def wrap_system_includes(target, visibility_flags):
1567    if visibility_flags and target.kernel != "Darwin":
1568        return True
1569
1570
1571set_define(
1572    "HAVE_VISIBILITY_HIDDEN_ATTRIBUTE",
1573    depends(visibility_flags)(lambda v: bool(v) or None),
1574)
1575set_define(
1576    "HAVE_VISIBILITY_ATTRIBUTE", depends(visibility_flags)(lambda v: bool(v) or None)
1577)
1578set_config("WRAP_SYSTEM_INCLUDES", wrap_system_includes)
1579set_config("VISIBILITY_FLAGS", visibility_flags)
1580
1581
1582@template
1583def depend_cflags(host_or_target_c_compiler):
1584    @depends(host_or_target_c_compiler)
1585    def depend_cflags(host_or_target_c_compiler):
1586        if host_or_target_c_compiler.type != "clang-cl":
1587            return ["-MD", "-MP", "-MF $(MDDEPDIR)/$(@F).pp"]
1588        else:
1589            # clang-cl doesn't accept the normal -MD -MP -MF options that clang
1590            # does, but the underlying cc1 binary understands how to generate
1591            # dependency files.  These options are based on analyzing what the
1592            # normal clang driver sends to cc1 when given the "correct"
1593            # dependency options.
1594            return [
1595                "-Xclang",
1596                "-MP",
1597                "-Xclang",
1598                "-dependency-file",
1599                "-Xclang",
1600                "$(MDDEPDIR)/$(@F).pp",
1601                "-Xclang",
1602                "-MT",
1603                "-Xclang",
1604                "$@",
1605            ]
1606
1607    return depend_cflags
1608
1609
1610set_config("_DEPEND_CFLAGS", depend_cflags(c_compiler))
1611set_config("_HOST_DEPEND_CFLAGS", depend_cflags(host_c_compiler))
1612
1613
1614@depends(c_compiler)
1615def preprocess_option(compiler):
1616    # The uses of PREPROCESS_OPTION depend on the spacing for -o/-Fi.
1617    if compiler.type in ("gcc", "clang"):
1618        return "-E -o "
1619    else:
1620        return "-P -Fi"
1621
1622
1623set_config("PREPROCESS_OPTION", preprocess_option)
1624
1625
1626# We only want to include windows.configure when we are compiling on
1627# Windows, or for Windows.
1628
1629
1630@depends(target, host)
1631def is_windows(target, host):
1632    return host.kernel == "WINNT" or target.kernel == "WINNT"
1633
1634
1635include("windows.configure", when=is_windows)
1636
1637
1638# On Power ISA, determine compiler flags for VMX, VSX and VSX-3.
1639
1640set_config(
1641    "PPC_VMX_FLAGS",
1642    ["-maltivec"],
1643    when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")),
1644)
1645
1646set_config(
1647    "PPC_VSX_FLAGS",
1648    ["-mvsx"],
1649    when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")),
1650)
1651
1652set_config(
1653    "PPC_VSX3_FLAGS",
1654    ["-mvsx", "-mcpu=power9"],
1655    when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")),
1656)
1657
1658# ASAN
1659# ==============================================================
1660
1661option("--enable-address-sanitizer", help="Enable Address Sanitizer")
1662
1663
1664@depends(when="--enable-address-sanitizer")
1665def asan():
1666    return True
1667
1668
1669add_old_configure_assignment("MOZ_ASAN", asan)
1670
1671# MSAN
1672# ==============================================================
1673
1674option("--enable-memory-sanitizer", help="Enable Memory Sanitizer")
1675
1676
1677@depends(when="--enable-memory-sanitizer")
1678def msan():
1679    return True
1680
1681
1682add_old_configure_assignment("MOZ_MSAN", msan)
1683
1684# TSAN
1685# ==============================================================
1686
1687option("--enable-thread-sanitizer", help="Enable Thread Sanitizer")
1688
1689
1690@depends(when="--enable-thread-sanitizer")
1691def tsan():
1692    return True
1693
1694
1695add_old_configure_assignment("MOZ_TSAN", tsan)
1696
1697# UBSAN
1698# ==============================================================
1699
1700option(
1701    "--enable-undefined-sanitizer", nargs="*", help="Enable UndefinedBehavior Sanitizer"
1702)
1703
1704
1705@depends_if("--enable-undefined-sanitizer")
1706def ubsan(options):
1707    default_checks = [
1708        "bool",
1709        "bounds",
1710        "enum",
1711        "integer-divide-by-zero",
1712        "object-size",
1713        "pointer-overflow",
1714        "return",
1715        "vla-bound",
1716    ]
1717
1718    checks = options if len(options) else default_checks
1719
1720    return ",".join(checks)
1721
1722
1723add_old_configure_assignment("MOZ_UBSAN_CHECKS", ubsan)
1724
1725
1726option(
1727    "--enable-signed-overflow-sanitizer",
1728    help="Enable UndefinedBehavior Sanitizer (Signed Integer Overflow Parts)",
1729)
1730
1731
1732@depends(when="--enable-signed-overflow-sanitizer")
1733def ub_signed_overflow_san():
1734    return True
1735
1736
1737add_old_configure_assignment("MOZ_SIGNED_OVERFLOW_SANITIZE", ub_signed_overflow_san)
1738
1739
1740option(
1741    "--enable-unsigned-overflow-sanitizer",
1742    help="Enable UndefinedBehavior Sanitizer (Unsigned Integer Overflow Parts)",
1743)
1744
1745
1746@depends(when="--enable-unsigned-overflow-sanitizer")
1747def ub_unsigned_overflow_san():
1748    return True
1749
1750
1751add_old_configure_assignment("MOZ_UNSIGNED_OVERFLOW_SANITIZE", ub_unsigned_overflow_san)
1752
1753# Security Hardening
1754# ==============================================================
1755
1756option(
1757    "--enable-hardening",
1758    env="MOZ_SECURITY_HARDENING",
1759    help="Enables security hardening compiler options",
1760)
1761
1762
1763# This function is a bit confusing. It adds or removes hardening flags in
1764# three stuations: if --enable-hardening is passed; if --disable-hardening
1765# is passed, and if no flag is passed.
1766#
1767# At time of this comment writing, all flags are actually added in the
1768# default no-flag case; making --enable-hardening the same as omitting the
1769# flag. --disable-hardening will omit the security flags. (However, not all
1770# possible security flags will be omitted by --disable-hardening, as many are
1771# compiler-default options we do not explicitly enable.)
1772@depends(
1773    "--enable-hardening",
1774    "--enable-address-sanitizer",
1775    "--enable-debug",
1776    "--enable-optimize",
1777    c_compiler,
1778    target,
1779)
1780def security_hardening_cflags(
1781    hardening_flag, asan, debug, optimize, c_compiler, target
1782):
1783    compiler_is_gccish = c_compiler.type in ("gcc", "clang")
1784    mingw_clang = c_compiler.type == "clang" and target.os == "WINNT"
1785
1786    flags = []
1787    ldflags = []
1788    js_flags = []
1789    js_ldflags = []
1790
1791    # WASI compiler doesn't support security hardening cflags
1792    if target.os == "WASI":
1793        return
1794
1795    # ----------------------------------------------------------
1796    # If hardening is explicitly enabled, or not explicitly disabled
1797    if hardening_flag.origin == "default" or hardening_flag:
1798        # FORTIFY_SOURCE ------------------------------------
1799        # Require optimization for FORTIFY_SOURCE. See Bug 1417452
1800        # Also, undefine it before defining it just in case a distro adds it, see Bug 1418398
1801        if compiler_is_gccish and optimize and not asan:
1802            # Don't enable FORTIFY_SOURCE on Android on the top-level, but do enable in js/
1803            if target.os != "Android":
1804                flags.append("-U_FORTIFY_SOURCE")
1805                flags.append("-D_FORTIFY_SOURCE=2")
1806            js_flags.append("-U_FORTIFY_SOURCE")
1807            js_flags.append("-D_FORTIFY_SOURCE=2")
1808            if mingw_clang:
1809                # mingw-clang needs to link in ssp which is not done by default
1810                ldflags.append("-lssp")
1811                js_ldflags.append("-lssp")
1812
1813        # fstack-protector ------------------------------------
1814        # Enable only if hardening is not disabled and ASAN is
1815        # not on as ASAN will catch the crashes for us
1816        if compiler_is_gccish and not asan:
1817            flags.append("-fstack-protector-strong")
1818            ldflags.append("-fstack-protector-strong")
1819            js_flags.append("-fstack-protector-strong")
1820            js_ldflags.append("-fstack-protector-strong")
1821
1822            if (
1823                c_compiler.type == "clang"
1824                and c_compiler.version >= "11.0.1"
1825                and target.os not in ("WINNT", "OSX")
1826                and target.cpu in ("x86", "x86_64", "ppc64", "s390x")
1827            ):
1828                flags.append("-fstack-clash-protection")
1829                ldflags.append("-fstack-clash-protection")
1830                js_flags.append("-fstack-clash-protection")
1831                js_ldflags.append("-fstack-clash-protection")
1832
1833        # ftrivial-auto-var-init ------------------------------
1834        # Initialize local variables with a 0xAA pattern in clang debug builds.
1835        # Linux32 fails some xpcshell tests with -ftrivial-auto-var-init
1836        linux32 = target.kernel == "Linux" and target.cpu == "x86"
1837        if (
1838            (c_compiler.type == "clang" or c_compiler.type == "clang-cl")
1839            and c_compiler.version >= "8"
1840            and debug
1841            and not linux32
1842        ):
1843            if c_compiler.type == "clang-cl":
1844                flags.append("-Xclang")
1845                js_flags.append("-Xclang")
1846            flags.append("-ftrivial-auto-var-init=pattern")
1847            js_flags.append("-ftrivial-auto-var-init=pattern")
1848
1849        # ASLR ------------------------------------------------
1850        # ASLR (dynamicbase) is enabled by default in clang-cl; but the
1851        # mingw-clang build requires it to be explicitly enabled
1852        if mingw_clang:
1853            ldflags.append("-Wl,--dynamicbase")
1854            js_ldflags.append("-Wl,--dynamicbase")
1855
1856        # Control Flow Guard (CFG) ----------------------------
1857        if (
1858            c_compiler.type == "clang-cl"
1859            and c_compiler.version >= "8"
1860            and (target.cpu != "aarch64" or c_compiler.version >= "8.0.1")
1861        ):
1862            if target.cpu == "aarch64" and c_compiler.version >= "10.0.0":
1863                # The added checks in clang 10 make arm64 builds crash. (Bug 1639318)
1864                flags.append("-guard:cf,nochecks")
1865                js_flags.append("-guard:cf,nochecks")
1866            else:
1867                flags.append("-guard:cf")
1868                js_flags.append("-guard:cf")
1869            # nolongjmp is needed because clang doesn't emit the CFG tables of
1870            # setjmp return addresses https://bugs.llvm.org/show_bug.cgi?id=40057
1871            ldflags.append("-guard:cf,nolongjmp")
1872            js_ldflags.append("-guard:cf,nolongjmp")
1873
1874    # ----------------------------------------------------------
1875    # If ASAN _is_ on, undefine FORTIFY_SOURCE just to be safe
1876    if asan:
1877        flags.append("-U_FORTIFY_SOURCE")
1878        js_flags.append("-U_FORTIFY_SOURCE")
1879
1880    # fno-common -----------------------------------------
1881    # Do not merge variables for ASAN; can detect some subtle bugs
1882    if asan:
1883        # clang-cl does not recognize the flag, it must be passed down to clang
1884        if c_compiler.type == "clang-cl":
1885            flags.append("-Xclang")
1886        flags.append("-fno-common")
1887
1888    return namespace(
1889        flags=flags,
1890        ldflags=ldflags,
1891        js_flags=js_flags,
1892        js_ldflags=js_ldflags,
1893    )
1894
1895
1896set_config("MOZ_HARDENING_CFLAGS", security_hardening_cflags.flags)
1897set_config("MOZ_HARDENING_LDFLAGS", security_hardening_cflags.ldflags)
1898set_config("MOZ_HARDENING_CFLAGS_JS", security_hardening_cflags.js_flags)
1899set_config("MOZ_HARDENING_LDFLAGS_JS", security_hardening_cflags.js_ldflags)
1900
1901
1902# Frame pointers
1903# ==============================================================
1904@depends(c_compiler)
1905def frame_pointer_flags(compiler):
1906    if compiler.type == "clang-cl":
1907        return namespace(
1908            enable=["-Oy-"],
1909            disable=["-Oy"],
1910        )
1911    return namespace(
1912        enable=["-fno-omit-frame-pointer", "-funwind-tables"],
1913        disable=["-fomit-frame-pointer", "-funwind-tables"],
1914    )
1915
1916
1917@depends(
1918    moz_optimize.optimize,
1919    moz_debug,
1920    target,
1921    "--enable-memory-sanitizer",
1922    "--enable-address-sanitizer",
1923    "--enable-undefined-sanitizer",
1924)
1925def frame_pointer_default(optimize, debug, target, msan, asan, ubsan):
1926    return bool(
1927        not optimize
1928        or debug
1929        or msan
1930        or asan
1931        or ubsan
1932        or (target.os == "WINNT" and target.cpu in ("x86", "aarch64"))
1933    )
1934
1935
1936option(
1937    "--enable-frame-pointers",
1938    default=frame_pointer_default,
1939    help="{Enable|Disable} frame pointers",
1940)
1941
1942
1943@depends("--enable-frame-pointers", frame_pointer_flags)
1944def frame_pointer_flags(enable, flags):
1945    if enable:
1946        return flags.enable
1947    return flags.disable
1948
1949
1950set_config("MOZ_FRAMEPTR_FLAGS", frame_pointer_flags)
1951
1952
1953# Code Coverage
1954# ==============================================================
1955
1956option("--enable-coverage", env="MOZ_CODE_COVERAGE", help="Enable code coverage")
1957
1958
1959@depends("--enable-coverage")
1960def code_coverage(value):
1961    if value:
1962        return True
1963
1964
1965set_config("MOZ_CODE_COVERAGE", code_coverage)
1966set_define("MOZ_CODE_COVERAGE", code_coverage)
1967
1968
1969@depends(target, c_compiler, check_build_environment, when=code_coverage)
1970@imports("os")
1971@imports("re")
1972@imports(_from="__builtin__", _import="open")
1973def coverage_cflags(target, c_compiler, build_env):
1974    cflags = ["--coverage"]
1975
1976    # clang 11 no longer accepts this flag (its behavior became the default)
1977    if c_compiler.type in ("clang", "clang-cl") and c_compiler.version < "11.0.0":
1978        cflags += [
1979            "-Xclang",
1980            "-coverage-no-function-names-in-data",
1981        ]
1982
1983    exclude = []
1984    if target.os == "WINNT" and c_compiler.type == "clang-cl":
1985        # VS files
1986        exclude.append("^.*[vV][sS]20[0-9]{2}.*$")
1987        # Files in fetches directory.
1988        exclude.append("^.*[\\\\/]fetches[\\\\/].*$")
1989    elif target.os == "OSX":
1990        # Files in fetches directory.
1991        exclude.append("^.*/fetches/.*$")
1992    elif target.os == "GNU":
1993        # Files in fetches directory.
1994        exclude.append("^.*/fetches/.*$")
1995        # Files in /usr/
1996        exclude.append("^/usr/.*$")
1997
1998    if exclude:
1999        exclude = ";".join(exclude)
2000        cflags += [
2001            f"-fprofile-exclude-files={exclude}",
2002        ]
2003
2004    response_file_path = os.path.join(build_env.topobjdir, "code_coverage_cflags")
2005
2006    with open(response_file_path, "w") as f:
2007        f.write(" ".join(cflags))
2008
2009    return ["@{}".format(response_file_path)]
2010
2011
2012set_config("COVERAGE_CFLAGS", coverage_cflags)
2013
2014# Linker detection
2015# ==============================================================
2016# The policy is as follows:
2017# For Windows:
2018# - the linker is picked via the LINKER environment variable per windows.configure,
2019#   but ought to be llvm-lld in any case.
2020# For macOS:
2021# - the linker is ld64, either from XCode on macOS, or from cctools-ports when
2022#   cross-compiling. lld can be enabled manually, but as of writing, mach-o support
2023#   for lld is incomplete.
2024# For other OSes:
2025# - on local developer builds: lld is used if present. Otherwise gold is used if present
2026#   otherwise, BFD ld is used.
2027# - on release/official builds: whatever "ld" resolves to is used, except on Android x86/x86_64
2028#   where BFD ld is used. Usually, "ld" resolves to BFD ld, except with the Android NDK,
2029#   where it resolves to gold. lld is not used by default on Linux and Android because
2030#   it introduces layout changes that prevent elfhack from working. See e.g.
2031#   https://bugzilla.mozilla.org/show_bug.cgi?id=1563654#c2.
2032@depends(target)
2033def is_linker_option_enabled(target):
2034    if target.kernel not in ("WINNT", "SunOS"):
2035        return True
2036
2037
2038option(
2039    "--enable-gold",
2040    env="MOZ_FORCE_GOLD",
2041    help="Enable GNU Gold Linker when it is not already the default",
2042    when=is_linker_option_enabled,
2043)
2044
2045imply_option("--enable-linker", "gold", when="--enable-gold")
2046
2047
2048@depends(target, developer_options)
2049def enable_linker_default(target, developer_options):
2050    # x86-64 gold has bugs in how it lays out .note.* sections. See bug 1573820.
2051    # x86-32 gold has a bug when assembly files are built. See bug 1651699.
2052    # lld is faster, so prefer that for developer builds.
2053    if target.os == "Android" and target.cpu in ("x86", "x86_64"):
2054        return "lld" if developer_options else "bfd"
2055
2056
2057option(
2058    "--enable-linker",
2059    nargs=1,
2060    help="Select the linker {bfd, gold, ld64, lld, lld-*}{|}",
2061    default=enable_linker_default,
2062    when=is_linker_option_enabled,
2063)
2064
2065
2066# No-op to enable depending on --enable-linker from default_elfhack in
2067# toolkit/moz.configure.
2068@depends("--enable-linker", when=is_linker_option_enabled)
2069def enable_linker(linker):
2070    return linker
2071
2072
2073@depends(
2074    "--enable-linker",
2075    c_compiler,
2076    developer_options,
2077    "--enable-gold",
2078    extra_toolchain_flags,
2079    target,
2080    when=is_linker_option_enabled,
2081)
2082@checking("for linker", lambda x: x.KIND)
2083@imports("os")
2084@imports("shutil")
2085def select_linker(
2086    linker, c_compiler, developer_options, enable_gold, toolchain_flags, target
2087):
2088
2089    if linker:
2090        linker = linker[0]
2091    else:
2092        linker = None
2093
2094    def is_valid_linker(linker):
2095        if target.kernel == "Darwin":
2096            valid_linkers = ("ld64", "lld")
2097        else:
2098            valid_linkers = ("bfd", "gold", "lld")
2099        if linker in valid_linkers:
2100            return True
2101        if "lld" in valid_linkers and linker.startswith("lld-"):
2102            return True
2103        return False
2104
2105    if linker and not is_valid_linker(linker):
2106        # Check that we are trying to use a supported linker
2107        die("Unsupported linker " + linker)
2108
2109    # Check the kind of linker
2110    version_check = ["-Wl,--version"]
2111    cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags
2112
2113    def try_linker(linker):
2114        # Generate the compiler flag
2115        if linker == "ld64":
2116            linker_flag = ["-fuse-ld=ld"]
2117        elif linker:
2118            linker_flag = ["-fuse-ld=" + linker]
2119        else:
2120            linker_flag = []
2121        cmd = cmd_base + linker_flag + version_check
2122        if toolchain_flags:
2123            cmd += toolchain_flags
2124
2125        # ld64 doesn't have anything to print out a version. It does print out
2126        # "ld64: For information on command line options please use 'man ld'."
2127        # but that would require doing two attempts, one with --version, that
2128        # would fail, and another with --help.
2129        # Instead, abuse its LD_PRINT_OPTIONS feature to detect a message
2130        # specific to it on stderr when it fails to process --version.
2131        env = dict(os.environ)
2132        env["LD_PRINT_OPTIONS"] = "1"
2133        # Some locales might not print out the strings we are looking for, so
2134        # ensure consistent output.
2135        env["LC_ALL"] = "C"
2136        retcode, stdout, stderr = get_cmd_output(*cmd, env=env)
2137        if retcode == 1 and "Logging ld64 options" in stderr:
2138            kind = "ld64"
2139
2140        elif retcode != 0:
2141            return None
2142
2143        elif "GNU ld" in stdout:
2144            # We are using the normal linker
2145            kind = "bfd"
2146
2147        elif "GNU gold" in stdout:
2148            kind = "gold"
2149
2150        elif "LLD" in stdout:
2151            kind = "lld"
2152
2153        else:
2154            kind = "unknown"
2155
2156        return namespace(
2157            KIND=kind,
2158            LINKER_FLAG=linker_flag,
2159        )
2160
2161    result = try_linker(linker)
2162    if result is None and linker:
2163        die("Could not use {} as linker".format(linker))
2164
2165    if (
2166        linker is None
2167        and enable_gold.origin == "default"
2168        and developer_options
2169        and (result is None or result.KIND in ("bfd", "gold"))
2170    ):
2171        # try and use lld if available.
2172        tried = try_linker("lld")
2173        if (result is None or result.KIND != "gold") and (
2174            tried is None or tried.KIND != "lld"
2175        ):
2176            tried = try_linker("gold")
2177            if tried is None or tried.KIND != "gold":
2178                tried = None
2179        if tried:
2180            result = tried
2181
2182    if result is None:
2183        die("Failed to find a linker")
2184
2185    # If an explicit linker was given, error out if what we found is different.
2186    if linker and not linker.startswith(result.KIND):
2187        die("Could not use {} as linker".format(linker))
2188
2189    return result
2190
2191
2192set_config("LINKER_KIND", select_linker.KIND)
2193
2194
2195@depends_if(select_linker, target, macos_sdk, sysroot_path, multiarch_dir)
2196@imports("os")
2197def linker_ldflags(linker, target, macos_sdk, sysroot_path, multiarch_dir):
2198    flags = list((linker and linker.LINKER_FLAG) or [])
2199    if target.kernel == "Darwin":
2200        if linker and linker.KIND == "ld64":
2201            flags.append("-Wl,-syslibroot,%s" % macos_sdk)
2202        else:
2203            flags.append("-Wl,--sysroot=%s" % macos_sdk)
2204
2205    # rpath-link is irrelevant to wasm, see for more info https://github.com/emscripten-core/emscripten/issues/11076.
2206    if sysroot_path and multiarch_dir and target.os != "WASI":
2207        for d in ("lib", "usr/lib"):
2208            multiarch_lib_dir = os.path.join(sysroot_path, d, multiarch_dir)
2209            if os.path.exists(multiarch_lib_dir):
2210                # Non-Debian-patched binutils linkers (both BFD and gold) don't lookup
2211                # in multi-arch directories.
2212                flags.append("-Wl,-rpath-link,%s" % multiarch_lib_dir)
2213    return flags
2214
2215
2216add_old_configure_assignment("LINKER_LDFLAGS", linker_ldflags)
2217
2218
2219# There's a wrinkle with MinGW: linker configuration is not enabled, so
2220# `select_linker` is never invoked.  Hard-code around it.
2221@depends(select_linker, target, c_compiler)
2222def gcc_use_gnu_ld(select_linker, target, c_compiler):
2223    if select_linker is not None:
2224        return select_linker.KIND in ("bfd", "gold", "lld")
2225    if target.kernel == "WINNT" and c_compiler.type == "clang":
2226        return True
2227    return None
2228
2229
2230# GCC_USE_GNU_LD=1 means the linker is command line compatible with GNU ld.
2231set_config("GCC_USE_GNU_LD", gcc_use_gnu_ld)
2232add_old_configure_assignment("GCC_USE_GNU_LD", gcc_use_gnu_ld)
2233
2234# Assembler detection
2235# ==============================================================
2236
2237option(env="AS", nargs=1, help="Path to the assembler")
2238
2239
2240@depends(target, c_compiler)
2241def as_info(target, c_compiler):
2242    if c_compiler.type == "clang-cl":
2243        ml = {
2244            "x86": "ml.exe",
2245            "x86_64": "ml64.exe",
2246            "aarch64": "armasm64.exe",
2247        }.get(target.cpu)
2248        return namespace(type="masm", names=(ml,))
2249    # When building with anything but clang-cl, we just use the C compiler as the assembler.
2250    return namespace(type="gcc", names=(c_compiler.compiler,))
2251
2252
2253# One would expect the assembler to be specified merely as a program.  But in
2254# cases where the assembler is passed down into js/, it can be specified in
2255# the same way as CC: a program + a list of argument flags.  We might as well
2256# permit the same behavior in general, even though it seems somewhat unusual.
2257# So we have to do the same sort of dance as we did above with
2258# `provided_compiler`.
2259provided_assembler = provided_program("AS")
2260assembler = check_prog(
2261    "_AS",
2262    input=provided_assembler.program,
2263    what="the assembler",
2264    progs=as_info.names,
2265    paths=vc_toolchain_search_path,
2266)
2267
2268
2269@depends(as_info, assembler, provided_assembler, c_compiler)
2270def as_with_flags(as_info, assembler, provided_assembler, c_compiler):
2271    if provided_assembler:
2272        return provided_assembler.wrapper + [assembler] + provided_assembler.flags
2273
2274    if as_info.type == "masm":
2275        return assembler
2276
2277    assert as_info.type == "gcc"
2278
2279    # Need to add compiler wrappers and flags as appropriate.
2280    return c_compiler.wrapper + [assembler] + c_compiler.flags
2281
2282
2283add_old_configure_assignment("AS", as_with_flags)
2284add_old_configure_assignment("ac_cv_prog_AS", as_with_flags)
2285
2286
2287@depends(assembler, c_compiler, extra_toolchain_flags)
2288@imports("subprocess")
2289@imports(_from="os", _import="devnull")
2290def gnu_as(assembler, c_compiler, toolchain_flags):
2291    # clang uses a compatible GNU assembler.
2292    if c_compiler.type == "clang":
2293        return True
2294
2295    if c_compiler.type == "gcc":
2296        cmd = [assembler] + c_compiler.flags
2297        if toolchain_flags:
2298            cmd += toolchain_flags
2299        cmd += ["-Wa,--version", "-c", "-o", devnull, "-x", "assembler", "-"]
2300        # We don't actually have to provide any input on stdin, `Popen.communicate` will
2301        # close the stdin pipe.
2302        # clang will error if it uses its integrated assembler for this target,
2303        # so handle failures gracefully.
2304        if "GNU" in check_cmd_output(*cmd, stdin=subprocess.PIPE, onerror=lambda: ""):
2305            return True
2306
2307
2308set_config("GNU_AS", gnu_as)
2309add_old_configure_assignment("GNU_AS", gnu_as)
2310
2311
2312@depends(as_info, target)
2313def as_dash_c_flag(as_info, target):
2314    # armasm64 doesn't understand -c.
2315    if as_info.type == "masm" and target.cpu == "aarch64":
2316        return ""
2317    else:
2318        return "-c"
2319
2320
2321set_config("AS_DASH_C_FLAG", as_dash_c_flag)
2322
2323
2324@depends(as_info, target)
2325def as_outoption(as_info, target):
2326    # The uses of ASOUTOPTION depend on the spacing for -o/-Fo.
2327    if as_info.type == "masm" and target.cpu != "aarch64":
2328        return "-Fo"
2329
2330    return "-o "
2331
2332
2333set_config("ASOUTOPTION", as_outoption)
2334
2335# clang plugin handling
2336# ==============================================================
2337
2338option(
2339    "--enable-clang-plugin",
2340    env="ENABLE_CLANG_PLUGIN",
2341    help="Enable building with the Clang plugin (gecko specific static analyzers)",
2342)
2343
2344add_old_configure_assignment(
2345    "ENABLE_CLANG_PLUGIN", depends_if("--enable-clang-plugin")(lambda _: True)
2346)
2347
2348
2349@depends(host_c_compiler, c_compiler, when="--enable-clang-plugin")
2350def llvm_config(host_c_compiler, c_compiler):
2351    clang = None
2352    for compiler in (host_c_compiler, c_compiler):
2353        if compiler and compiler.type == "clang":
2354            clang = compiler.compiler
2355            break
2356        elif compiler and compiler.type == "clang-cl":
2357            clang = os.path.join(os.path.dirname(compiler.compiler), "clang")
2358            break
2359
2360    if not clang:
2361        die("Cannot --enable-clang-plugin when not building with clang")
2362    llvm_config = "llvm-config"
2363    out = check_cmd_output(clang, "--print-prog-name=llvm-config", onerror=lambda: None)
2364    if out:
2365        llvm_config = out.rstrip()
2366    return (llvm_config,)
2367
2368
2369llvm_config = check_prog(
2370    "LLVM_CONFIG",
2371    llvm_config,
2372    what="llvm-config",
2373    when="--enable-clang-plugin",
2374    paths=clang_search_path,
2375)
2376
2377add_old_configure_assignment("LLVM_CONFIG", llvm_config)
2378
2379
2380option(
2381    "--enable-clang-plugin-alpha",
2382    env="ENABLE_CLANG_PLUGIN_ALPHA",
2383    help="Enable static analysis with clang-plugin alpha checks.",
2384)
2385
2386
2387@depends("--enable-clang-plugin", "--enable-clang-plugin-alpha")
2388def check_clang_plugin_alpha(enable_clang_plugin, enable_clang_plugin_alpha):
2389    if enable_clang_plugin_alpha:
2390        if enable_clang_plugin:
2391            return True
2392        die("Cannot enable clang-plugin alpha checkers without --enable-clang-plugin.")
2393
2394
2395add_old_configure_assignment("ENABLE_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha)
2396set_define("MOZ_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha)
2397
2398option(
2399    "--enable-mozsearch-plugin",
2400    env="ENABLE_MOZSEARCH_PLUGIN",
2401    help="Enable building with the mozsearch indexer plugin",
2402)
2403
2404add_old_configure_assignment(
2405    "ENABLE_MOZSEARCH_PLUGIN", depends_if("--enable-mozsearch-plugin")(lambda _: True)
2406)
2407
2408# Libstdc++ compatibility hacks
2409# ==============================================================
2410#
2411option(
2412    "--enable-stdcxx-compat",
2413    env="MOZ_STDCXX_COMPAT",
2414    help="Enable compatibility with older libstdc++",
2415)
2416
2417
2418@template
2419def libstdcxx_version(var, compiler):
2420    @depends(compiler, when="--enable-stdcxx-compat")
2421    @checking(var, lambda v: v and "GLIBCXX_%s" % v.dotted)
2422    @imports(_from="mozbuild.configure.libstdcxx", _import="find_version")
2423    @imports(_from="__builtin__", _import="Exception")
2424    def version(compiler):
2425        try:
2426            result = find_version(
2427                compiler.wrapper + [compiler.compiler] + compiler.flags
2428            )
2429        except Exception:
2430            die("Couldn't determine libstdc++ version")
2431        if result:
2432            return namespace(
2433                dotted=result[0],
2434                encoded=str(result[1]),
2435            )
2436
2437    set_config(var, version.encoded)
2438    return version
2439
2440
2441add_gcc_flag(
2442    "-D_GLIBCXX_USE_CXX11_ABI=0",
2443    cxx_compiler,
2444    when=libstdcxx_version("MOZ_LIBSTDCXX_TARGET_VERSION", cxx_compiler),
2445)
2446add_gcc_flag(
2447    "-D_GLIBCXX_USE_CXX11_ABI=0",
2448    host_cxx_compiler,
2449    when=libstdcxx_version("MOZ_LIBSTDCXX_HOST_VERSION", host_cxx_compiler),
2450)
2451
2452
2453# Support various fuzzing options
2454# ==============================================================
2455option("--enable-fuzzing", help="Enable fuzzing support")
2456
2457
2458@depends(build_project)
2459def js_build(build_project):
2460    return build_project == "js"
2461
2462
2463option(
2464    "--enable-js-fuzzilli",
2465    when=js_build,
2466    help="Enable fuzzilli support for the JS engine",
2467)
2468
2469
2470@depends("--enable-fuzzing")
2471def enable_fuzzing(value):
2472    if value:
2473        return True
2474
2475
2476@depends("--enable-js-fuzzilli", when=js_build)
2477def enable_js_fuzzilli(value):
2478    if value:
2479        return True
2480
2481
2482@depends(
2483    try_compile(
2484        body="__AFL_COMPILER;", check_msg="for AFL compiler", when="--enable-fuzzing"
2485    )
2486)
2487def enable_aflfuzzer(afl):
2488    if afl:
2489        return True
2490
2491
2492@depends(enable_fuzzing, enable_aflfuzzer, c_compiler, target)
2493def enable_libfuzzer(fuzzing, afl, c_compiler, target):
2494    if fuzzing and not afl and c_compiler.type == "clang" and target.os != "Android":
2495        return True
2496
2497
2498@depends(enable_fuzzing, enable_aflfuzzer, enable_libfuzzer, enable_js_fuzzilli)
2499def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer, enable_js_fuzzilli):
2500    if fuzzing and (afl or libfuzzer) and not enable_js_fuzzilli:
2501        return True
2502
2503
2504set_config("FUZZING", enable_fuzzing)
2505set_define("FUZZING", enable_fuzzing)
2506
2507set_config("LIBFUZZER", enable_libfuzzer)
2508set_define("LIBFUZZER", enable_libfuzzer)
2509add_old_configure_assignment("LIBFUZZER", enable_libfuzzer)
2510
2511set_config("FUZZING_INTERFACES", enable_fuzzing_interfaces)
2512set_define("FUZZING_INTERFACES", enable_fuzzing_interfaces)
2513add_old_configure_assignment("FUZZING_INTERFACES", enable_fuzzing_interfaces)
2514
2515set_config("FUZZING_JS_FUZZILLI", enable_js_fuzzilli)
2516set_define("FUZZING_JS_FUZZILLI", enable_js_fuzzilli)
2517
2518
2519@depends(
2520    c_compiler.try_compile(
2521        flags=["-fsanitize=fuzzer-no-link"],
2522        when=enable_fuzzing,
2523        check_msg="whether the C compiler supports -fsanitize=fuzzer-no-link",
2524    ),
2525    tsan,
2526    enable_js_fuzzilli,
2527)
2528def libfuzzer_flags(value, tsan, enable_js_fuzzilli):
2529    if tsan:
2530        # With ThreadSanitizer, we should not use any libFuzzer instrumentation because
2531        # it is incompatible (e.g. there are races on global sanitizer coverage counters).
2532        # Instead we use an empty set of flags here but still build the fuzzing targets.
2533        # With this setup, we can still run files through these targets in TSan builds,
2534        # e.g. those obtained from regular fuzzing.
2535        # This code can be removed once libFuzzer has been made compatible with TSan.
2536        #
2537        # Also, this code needs to be kept in sync with certain gyp files, currently:
2538        #   - dom/media/webrtc/transport/third_party/nICEr/nicer.gyp
2539        return namespace(no_link_flag_supported=False, use_flags=[])
2540
2541    if enable_js_fuzzilli:
2542        # Fuzzilli comes with its own trace-pc interceptors and flag requirements.
2543        no_link_flag_supported = False
2544        use_flags = ["-fsanitize-coverage=trace-pc-guard", "-g"]
2545    elif value:
2546        no_link_flag_supported = True
2547        # recommended for (and only supported by) clang >= 6
2548        use_flags = ["-fsanitize=fuzzer-no-link"]
2549    else:
2550        no_link_flag_supported = False
2551        use_flags = ["-fsanitize-coverage=trace-pc-guard,trace-cmp"]
2552
2553    return namespace(
2554        no_link_flag_supported=no_link_flag_supported,
2555        use_flags=use_flags,
2556    )
2557
2558
2559set_config("HAVE_LIBFUZZER_FLAG_FUZZER_NO_LINK", libfuzzer_flags.no_link_flag_supported)
2560set_config("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags)
2561add_old_configure_assignment("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags)
2562
2563# Shared library building
2564# ==============================================================
2565
2566# XXX: The use of makefile constructs in these variables is awful.
2567@depends(target, c_compiler)
2568def make_shared_library(target, compiler):
2569    if target.os == "WINNT":
2570        if compiler.type == "gcc":
2571            return namespace(
2572                mkshlib=["$(CXX)", "$(DSO_LDOPTS)", "-o", "$@"],
2573                mkcshlib=["$(CC)", "$(DSO_LDOPTS)", "-o", "$@"],
2574            )
2575        elif compiler.type == "clang":
2576            return namespace(
2577                mkshlib=[
2578                    "$(CXX)",
2579                    "$(DSO_LDOPTS)",
2580                    "-Wl,-pdb,$(LINK_PDBFILE)",
2581                    "-o",
2582                    "$@",
2583                ],
2584                mkcshlib=[
2585                    "$(CC)",
2586                    "$(DSO_LDOPTS)",
2587                    "-Wl,-pdb,$(LINK_PDBFILE)",
2588                    "-o",
2589                    "$@",
2590                ],
2591            )
2592        else:
2593            linker = [
2594                "$(LINKER)",
2595                "-NOLOGO",
2596                "-DLL",
2597                "-OUT:$@",
2598                "-PDB:$(LINK_PDBFILE)",
2599                "$(DSO_LDOPTS)",
2600            ]
2601            return namespace(
2602                mkshlib=linker,
2603                mkcshlib=linker,
2604            )
2605
2606    cc = ["$(CC)", "$(COMPUTED_C_LDFLAGS)"]
2607    cxx = ["$(CXX)", "$(COMPUTED_CXX_LDFLAGS)"]
2608    flags = ["$(PGO_CFLAGS)", "$(DSO_LDOPTS)"]
2609    output = ["-o", "$@"]
2610
2611    if target.kernel == "Darwin":
2612        soname = []
2613    elif target.os == "NetBSD":
2614        soname = ["-Wl,-soname,$(DSO_SONAME)"]
2615    else:
2616        assert compiler.type in ("gcc", "clang")
2617
2618        soname = ["-Wl,-h,$(DSO_SONAME)"]
2619
2620    return namespace(
2621        mkshlib=cxx + flags + soname + output,
2622        mkcshlib=cc + flags + soname + output,
2623    )
2624
2625
2626set_config("MKSHLIB", make_shared_library.mkshlib)
2627set_config("MKCSHLIB", make_shared_library.mkcshlib)
2628
2629
2630@depends(c_compiler, toolchain_prefix, when=target_is_windows)
2631def rc_names(c_compiler, toolchain_prefix):
2632    if c_compiler.type in ("gcc", "clang"):
2633        return tuple("%s%s" % (p, "windres") for p in ("",) + (toolchain_prefix or ()))
2634    return ("llvm-rc",)
2635
2636
2637check_prog("RC", rc_names, paths=clang_search_path, when=target_is_windows)
2638
2639
2640@depends(toolchain_prefix, c_compiler)
2641def ar_config(toolchain_prefix, c_compiler):
2642    if c_compiler.type == "clang-cl":
2643        return namespace(
2644            names=("llvm-lib",),
2645            flags=("-llvmlibthin", "-out:$@"),
2646        )
2647
2648    names = tuple("%s%s" % (p, "ar") for p in (toolchain_prefix or ()) + ("",))
2649    if c_compiler.type == "clang":
2650        # Get the llvm-ar path as per the output from clang --print-prog-name=llvm-ar
2651        # so that we directly get the one under the clang directory, rather than one
2652        # that might be in /usr/bin and that might point to one from a different version
2653        # of clang.
2654        out = check_cmd_output(
2655            c_compiler.compiler, "--print-prog-name=llvm-ar", onerror=lambda: None
2656        )
2657        llvm_ar = out.rstrip() if out else "llvm-ar"
2658        names = (llvm_ar,) + names
2659
2660    return namespace(
2661        names=names,
2662        flags=("crs", "$@"),
2663    )
2664
2665
2666ar = check_prog("AR", ar_config.names, paths=clang_search_path)
2667
2668add_old_configure_assignment("AR", ar)
2669
2670set_config("AR_FLAGS", ar_config.flags)
2671
2672
2673@depends(toolchain_prefix, c_compiler)
2674def nm_names(toolchain_prefix, c_compiler):
2675    names = tuple("%s%s" % (p, "nm") for p in (toolchain_prefix or ()) + ("",))
2676    if c_compiler.type == "clang":
2677        # Get the llvm-nm path as per the output from clang --print-prog-name=llvm-nm
2678        # so that we directly get the one under the clang directory, rather than one
2679        # that might be in /usr/bin and that might point to one from a different version
2680        # of clang.
2681        out = check_cmd_output(
2682            c_compiler.compiler, "--print-prog-name=llvm-nm", onerror=lambda: None
2683        )
2684        llvm_nm = out.rstrip() if out else "llvm-nm"
2685        names = (llvm_nm,) + names
2686
2687    return names
2688
2689
2690check_prog("NM", nm_names, paths=clang_search_path, when=target_is_linux)
2691
2692
2693option("--enable-cpp-rtti", help="Enable C++ RTTI")
2694
2695add_old_configure_assignment("_MOZ_USE_RTTI", "1", when="--enable-cpp-rtti")
2696
2697
2698option(
2699    "--enable-path-remapping",
2700    nargs="*",
2701    choices=("c", "rust"),
2702    help="Enable remapping source and object paths in compiled outputs.",
2703)
2704
2705
2706@depends("--enable-path-remapping")
2707def path_remapping(value):
2708    if len(value):
2709        return value
2710    if bool(value):
2711        return ["c", "rust"]
2712    return []
2713
2714
2715@depends(
2716    target,
2717    check_build_environment,
2718    sysroot_path,
2719    macos_sdk,
2720    windows_sdk_dir,
2721    vc_path,
2722    when="--enable-path-remapping",
2723)
2724def path_remappings(
2725    target, build_env, sysroot_path, macos_sdk, windows_sdk_dir, vc_path
2726):
2727    win = target.kernel == "WINNT"
2728
2729    # The prefix maps are processed in the order they're specified on the
2730    # command line.  Therefore, to accommodate object directories in the source
2731    # directory, it's important that we map the topobjdir before the topsrcdir,
2732    # 'cuz we might have /src/obj/=/o/ and /src/=/s/.  The various other
2733    # directories might be subdirectories of topsrcdir as well, so they come
2734    # earlier still.
2735
2736    path_remappings = []
2737
2738    # We will have only one sysroot or SDK, so all can have the same mnemonic: K
2739    # for "kit" (since S is taken for "source").  See
2740    # https://blog.llvm.org/2019/11/deterministic-builds-with-clang-and-lld.html
2741    # for how to use the Windows `subst` command to map these in debuggers and
2742    # IDEs.
2743    if sysroot_path:
2744        path_remappings.append((sysroot_path, "k:/" if win else "/sysroot/"))
2745    if macos_sdk:
2746        path_remappings.append((macos_sdk, "k:/" if win else "/sysroot/"))
2747    if windows_sdk_dir:
2748        path_remappings.append((windows_sdk_dir, "k:/" if win else "/windows_sdk/"))
2749    if vc_path:
2750        path_remappings.append((vc_path, "v:/" if win else "/vc/"))
2751
2752    path_remappings += [
2753        (build_env.topobjdir, "o:/" if win else "/topobjdir/"),
2754        (build_env.topsrcdir, "s:/" if win else "/topsrcdir/"),
2755    ]
2756
2757    path_remappings = [
2758        (normsep(old).rstrip("/") + "/", new) for old, new in path_remappings
2759    ]
2760
2761    # It is tempting to sort these, but we want the order to be the same across
2762    # machines so that we can share cache hits.  Therefore we reject bad
2763    # configurations rather than trying to make the configuration good.
2764    for i in range(len(path_remappings) - 1):
2765        p = path_remappings[i][0]
2766        for q, _ in path_remappings[i + 1 :]:
2767            if q.startswith(p):
2768                die(f"Cannot remap paths because {p} is an ancestor of {q}")
2769
2770    return path_remappings
2771