Source code
Revision control
Copy as Markdown
Other Tools
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# Generates a test program and attempts to compile it. In case of failure, the
# resulting check will return None. If the test program succeeds, it will return
# the output of the test program.
# - `includes` are the includes (as file names) that will appear at the top of
#   the generated test program.
# - `body` is the code that will appear in the main function of the generated
#   test program. `return 0;` is appended to the function body automatically.
# - `language` is the language selection, so that the appropriate compiler is
#   used.
# - `flags` are the flags to be passed to the compiler, in addition to `-c`.
# - `check_msg` is the message to be printed to accompany compiling the test
#   program.
@template
def try_compile(
    includes=None,
    body="",
    language="C++",
    flags=None,
    check_msg=None,
    when=None,
    onerror=lambda: None,
):
    compiler = {
        "C": c_compiler,
        "C++": cxx_compiler,
    }[language]
    return compiler.try_compile(
        includes, body, flags, check_msg, when=when, onerror=onerror
    )
# Generates a test program and attempts to link it. In case of failure, the
# resulting check will return None. If the link succeeds, it will return
# True
# - `includes` are the includes (as file names) that will appear at the top of
#   the generated test program.
# - `body` is the code that will appear in the main function of the generated
#   test program. `return 0;` is appended to the function body automatically.
# - `language` is the language selection, so that the appropriate compiler is
#   used.
# - `flags` are the flags to be passed to the compiler.
# - `check_msg` is the message to be printed to accompany compiling the test
#   program.
@template
def try_link(
    includes=None,
    body="",
    language="C++",
    flags=None,
    check_msg=None,
    when=None,
    onerror=lambda: None,
):
    compiler = {
        "C": c_compiler,
        "C++": cxx_compiler,
    }[language]
    return compiler.try_link(
        linker_ldflags,
        includes,
        body,
        flags,
        check_msg,
        when=when,
        onerror=onerror,
    )
# Checks for the presence of the given header on the target system by compiling
# a test program including that header. The return value of the template is a
# check function returning True if the header is present, and None if it is not.
# The value of this check function is also used to set a variable (with set_define)
# corresponding to the checked header. For instance, HAVE_MALLOC_H will be set in
# defines if check_header if called with 'malloc.h' as input and malloc.h is
# present on the target.
# - `header` is the header, as a file name, to check for.
# - `language` is the language selection, so that the appropriate compiler is
#   used.
# - `flags` are the flags to be passed to the compiler, in addition to `-c`.
# - `includes` are additional includes, as file names, to appear before the
#   header checked for.
# - `when` is a depends function that if present will make performing the check
#   conditional on the value of that function.
@template
def check_header(
    header,
    language="C++",
    body="",
    flags=None,
    includes=None,
    when=None,
    onerror=lambda: None,
):
    if when is None:
        when = always
    if includes:
        includes = includes[:]
    else:
        includes = []
    includes.append(header)
    have_header = try_compile(
        includes=includes,
        body=body,
        language=language,
        flags=flags,
        check_msg="for %s" % header,
        when=when,
        onerror=onerror,
    )
    header_var = "HAVE_%s" % (
        header.upper().replace("-", "_").replace("/", "_").replace(".", "_")
    )
    set_define(header_var, have_header)
    return have_header
# A convenience wrapper for check_header for checking multiple headers.
# returns an array of the resulting checks in order corresponding to the
# provided headers.
# - `headers` are the headers to be checked.
# - `kwargs` are keyword arguments passed verbatim to check_header.
@template
def check_headers(*headers, **kwargs):
    checks = []
    for header in headers:
        checks.append(check_header(header, **kwargs))
    return checks
@depends(linker_ldflags, target.kernel)
def check_symbol_flags(linker_ldflags, kernel):
    if kernel == "WINNT":
        # The build doesn't use the compiler to link things as of writing,
        # but some compilation checks do. When using clang-cl, the only
        # linker we really support is lld.link, but clang-cl defaults to
        # link.exe (even when cross-compiling). So we force the use of
        # lld.link for the linkage checks.
        return ["-fuse-ld=lld"]
    return linker_ldflags
# Checks for the presence of the given symbol on the target system by compiling
# a test program. The return value of the template is a check function
# returning True if the symbol can be found, and None if it is not.
@template
def check_symbol(
    symbol, language="C", flags=None, msg_extra="", when=None, onerror=lambda: None
):
    if when is None:
        when = always
    compiler, extern_c = {
        "C": (c_compiler, ""),
        "C++": (cxx_compiler, 'extern "C" '),
    }[language]
    # Stolen from autoconf 2.13 ; might be irrelevant now, but it doesn't hurt to
    # keep using a char return type.
    comment = [
        "/* Override any gcc2 internal prototype to avoid an error.  */",
        "/* We use char because int might match the return type of a gcc2",
        "    builtin and then its argument prototype would still apply.  */",
    ]
    if flags:
        @depends(check_symbol_flags, dependable(flags))
        def flags(base_flags, extra_flags):
            if base_flags and extra_flags:
                return base_flags + list(extra_flags)
            if extra_flags:
                return extra_flags
            return base_flags
    else:
        flags = check_symbol_flags
    return compiler.try_run(
        header=comment + ["%schar %s();" % (extern_c, symbol)],
        body="%s();" % symbol,
        flags=flags,
        check_msg="for %s%s" % (symbol, msg_extra),
        when=when,
        onerror=onerror,
    )
# Checks for the presence of the given symbol in the given library on the
# target system by compiling a test program. The return value of the template
# is a check function returning True if the symbol can be found, and None if it
# is not.
@template
def check_symbol_in_lib(libname, symbol, language="C", when=None, onerror=lambda: None):
    flag = f"-l{libname}"
    have_symbol = check_symbol(
        symbol,
        flags=[flag],
        msg_extra=f" in {flag}",
        language=language,
        when=when,
        onerror=onerror,
    )
    return have_symbol
# Same as check_symbol_in_lib but iteratively try libraries in the given libnames until it
# finds one that contains the given symbol.
# Returns None if not found, empty tuple if None is in the given libnames and
# the symbol is present without library linked, a singleton containing the
# library name if found in that library.
@template
def check_symbol_in_libs(
    libnames, symbol, language="C", when=None, onerror=lambda: None
):
    have_symbol = never
    found_lib = dependable(namespace(found=False, lib=None))
    kwargs = {
        "symbol": symbol,
        "language": language,
        "when": when or always,
        "onerror": onerror,
    }
    for libname in libnames:
        kwargs["when"] &= ~have_symbol
        if libname:
            have_symbol = check_symbol_in_lib(libname, **kwargs)
        else:
            have_symbol = check_symbol(**kwargs)
        add_lib = namespace(found=True, lib=libname)
        found_lib = depends(have_symbol, found_lib)(lambda h, f: add_lib if h else f)
    return found_lib
# Determine whether to add a given flag to the given lists of flags for C or
# C++ compilation.
# - `flag` is the flag to test
# - `flags_collection` is a @depends function for a namespace of lists of
#    C/C++ compiler flags to add to.
# - `test_flags` is a list of flags to pass to the compiler instead of merely
#   passing `flag`. This is especially useful for checking warning flags. If
#   this list is empty, `flag` will be passed on its own.
# - `compiler` (optional) is the compiler to test against (c_compiler or
#   cxx_compiler, from toolchain.configure). When omitted, both compilers
#   are tested; the list of flags added to is dependent on the compiler tested.
# - `when` (optional) is a @depends function or option name conditioning
#   when the warning flag is wanted.
# - `check`, when not set, skips checking whether the flag is supported and
#   adds it to the list of flags unconditionally.
# - `mode`, can be "compile", "link" or "assemble"
@template
def check_and_add_flags(
    flag,
    flags_collection,
    test_flags=(),
    compiler=None,
    when=None,
    check=True,
    mode="compile",
):
    assert mode in ("compile", "link", "assemble")
    if compiler is not None:
        compilers = (compiler,)
    elif mode in ("link", "assemble"):
        compilers = (c_compiler,)
    else:
        compilers = (c_compiler, cxx_compiler)
    if when is None:
        when = always
    results = []
    if test_flags:
        flags = test_flags
    else:
        flags = [flag]
    for c in compilers:
        assert c in {c_compiler, cxx_compiler, host_c_compiler, host_cxx_compiler}
        if mode == "compile":
            lang, list_of_flags = {
                c_compiler: ("C", flags_collection.cflags),
                cxx_compiler: ("C++", flags_collection.cxxflags),
                host_c_compiler: ("host C", flags_collection.host_cflags),
                host_cxx_compiler: ("host C++", flags_collection.host_cxxflags),
            }[c]
        elif mode == "assemble":
            lang, list_of_flags = {
                c_compiler: ("C", flags_collection.asflags),
            }[c]
        elif mode == "link":
            lang, list_of_flags = {
                c_compiler: ("C", flags_collection.ldflags),
                cxx_compiler: ("C++", flags_collection.ldflags),
                host_c_compiler: ("host C", flags_collection.host_ldflags),
                host_cxx_compiler: ("host C++", flags_collection.host_ldflags),
            }[c]
        result = when
        if check:
            @depends(c, dependable(flags))
            def flags(c, flags):
                # Don't error out just because clang complains about other things.
                if c.type in ("clang", "clang-cl"):
                    flags += ["-Wno-error=unused-command-line-argument"]
                return flags
            if mode == "link":
                def runner(*args, **kwargs):
                    if c in (c_compiler, cxx_compiler):
                        return c.try_link(linker_ldflags, *args, **kwargs)
                    else:
                        return c.try_link(host_linker_ldflags, *args, **kwargs)
                tool = "linker"
            elif mode == "assemble":
                runner = c.try_compile
                tool = "assembler"
            else:
                runner = c.try_compile
                tool = "compiler"
            result = runner(
                flags=flags,
                when=result,
                check_msg="whether the %s %s supports %s" % (lang, tool, flag),
            )
        @depends(result, list_of_flags)
        def maybe_add_flag(result, list_of_flags):
            if result:
                list_of_flags.append(flag)
        results.append(result)
    return tuple(results)
@dependable
def warnings_flags():
    return namespace(cflags=[], cxxflags=[], host_cflags=[], host_cxxflags=[])
# Tests whether GCC or clang support the given warning flag, and if it is,
# add it to the list of warning flags for the build.
# - `warning` is the warning flag (e.g. -Wfoo)
# - `compiler` (optional) is the compiler to test against (c_compiler or
#   cxx_compiler, from toolchain.configure). When omitted, both compilers
#   are tested.
# - `when` (optional) is a @depends function or option name conditioning
#   when the warning flag is wanted.
# - `check`, when not set, skips checking whether the flag is supported and
#   adds it to the list of warning flags unconditionally. This is only meant
#   for add_warning().
@template
def check_and_add_warning_impl(warning, compiler=None, when=None, check=True):
    # GCC and clang will fail if given an unknown warning option like
    # -Wfoobar. But later versions won't fail if given an unknown negated
    # warning option like -Wno-foobar. So when we are checking for support
    # of a negated warning option, we actually test the positive form, but
    # add the negated form to the flags variable.
    if warning.startswith("-Wno-") and not warning.startswith("-Wno-error="):
        flags = ["-Werror", "-W" + warning[5:]]
    elif warning.startswith("-Werror="):
        flags = [warning]
    else:
        flags = ["-Werror", warning]
    return check_and_add_flags(
        warning, warnings_flags, flags, compiler=compiler, when=when, check=check
    )
# When changing this version, please check that no warning support have been
# removed.
clang_latest_release = dependable("21.1.0")
@depends(c_compiler, clang_latest_release, when=moz_automation)
def check_latest_release_version(compiler, clang_latest_release):
    if (
        compiler.type in ("clang", "clang-cl")
        and compiler.version > clang_latest_release
    ):
        configure_error("latest clang release version must be updated")
# Add the given warning to the list of warning flags for the build.
# - `warning` is the warning flag (e.g. -Wfoo)
# - `compiler` (optional) is the compiler to add the flag for (c_compiler or
#   cxx_compiler, from toolchain.configure). When omitted, the warning flag
#   is added for both compilers.
# - `when` (optional) is a @depends function or option name conditioning
#   when the warning flag is wanted.
@template
def add_warning(warning, compiler=None, when=None):
    check_and_add_warning_impl(warning, compiler, when, check=False)
# Add the given warning to the list of warning flags for the build.
# - `warning` is the warning flag (e.g. -Wfoo)
# - `compiler` (optional) is the compiler to add the flag for (c_compiler or
#   cxx_compiler, from toolchain.configure). When omitted, the warning flag
#   is added for both compilers.
# - `when` (optional) is a @depends function or option name conditioning
#   when the warning flag is wanted.
# - if min_clang_version is set, do not run the check for clang compiler, use
#   know result instead. Possibly rely on max_clang_version in that case to
#   bound the version check.
@template
def check_and_add_warning(
    warning, compiler=None, when=None, max_clang_version=None, min_clang_version=None
):
    if when is None:
        when = always
    if min_clang_version is None and max_clang_version is None:
        return check_and_add_warning_impl(warning, compiler, when, check=True)
    min_clang_version = min_clang_version or "0.0.0"
    max_clang_version = (
        dependable(max_clang_version) if max_clang_version else clang_latest_release
    )
    is_compatible_clang = depends(c_compiler, max_clang_version)(
        lambda c, max_clang_version: c.type in ("clang", "clang-cl")
        and min_clang_version <= c.version <= max_clang_version
    )
    is_clang = depends(c_compiler.type)(lambda t: t in ("clang", "clang-cl"))
    res_clang = check_and_add_warning_impl(
        warning, compiler, when & is_compatible_clang, check=False
    )
    res_other = check_and_add_warning_impl(
        warning, compiler, when & (building_with_gcc | ~is_compatible_clang), check=True
    )
    return res_clang + res_other
# Like the warning checks above, but for general compilation flags.
@dependable
def compilation_flags():
    return namespace(
        cflags=[],
        cxxflags=[],
        host_cflags=[],
        host_cxxflags=[],
        cmflags=[],
        cmmflags=[],
    )
# Tests whether GCC or clang support the given compilation flag; if the flag
# is supported, add it to the list of compilation flags for the build.
# - `flag` is the flag to test
# - `compiler` (optional) is the compiler to test against (c_compiler or
#   cxx_compiler, from toolchain.configure). When omitted, both compilers
#   are tested.
# - `when` (optional) is a @depends function or option name conditioning
#   when the warning flag is wanted.
# - `check`, when not set, skips checking whether the flag is supported and
#   adds it to the list of flags unconditionally. This is only meant for
#   add_flag().
@template
def check_and_add_flag(flag, compiler=None, when=None, check=True):
    flags = ["-Werror", flag]
    return check_and_add_flags(
        flag, compilation_flags, flags, compiler=compiler, when=when, check=check
    )
# Add the given flag to the list of flags for the build.
# - `flag` is the flag (e.g. -fno-sized-deallocation)
# - `compiler` (optional) is the compiler to add the flag for (c_compiler or
#   cxx_compiler, from toolchain.configure). When omitted, the flag is added
#   for both compilers.
# - `when` (optional) is a @depends function or option name conditioning
#   when the flag is wanted.
@template
def add_flag(warning, compiler=None, when=None):
    check_and_add_flag(warning, compiler, when, check=False)
# Like the compilation checks above, but for asm flags.
@dependable
def asm_flags():
    return namespace(asflags=[])
# Tests the given assembler flag is supported; if the flag
# is supported, add it to the list of compilation flags for the build.
# - `flag` is the flag to test
# - `when` (optional) is a @depends function or option name conditioning
#   when the warning flag is wanted.
# - `check`, when not set, skips checking whether the flag is supported and
#   adds it to the list of flags unconditionally. This is only meant for
#   add_flag().
@template
def check_and_add_asm_flag(flag, when=None, check=True):
    return check_and_add_flags(
        flag,
        asm_flags,
        [flag],
        when=when,
        check=check,
        mode="assemble",
    )
# Like the compilation checks above, but for linker flags.
@dependable
def linker_flags():
    return namespace(ldflags=[], host_ldflags=[])
@dependable
def dso_flags():
    return namespace(ldopts=[])
# Tests the given linker flag is supported; if the flag
# is supported, add it to the list of compilation flags for the build.
# - `flag` is the flag to test
# - `when` (optional) is a @depends function or option name conditioning
#   when the warning flag is wanted.
# - `check`, when not set, skips checking whether the flag is supported and
#   adds it to the list of flags unconditionally. This is only meant for
#   add_flag().
@template
def check_and_add_linker_flag(flag, compiler=None, when=None, check=True):
    return check_and_add_flags(
        flag,
        linker_flags,
        [flag],
        when=when,
        check=check,
        mode="link",
    )
# Like the compilation checks above, but for linker optimization flags.
@dependable
def linker_optimize_flags():
    return namespace(ldflags=[])
@template
def check_and_add_linker_optimize_flag(flag, compiler=None, when=None, check=True):
    return check_and_add_flags(
        flag,
        linker_optimize_flags,
        [flag],
        when=when,
        check=check,
        mode="link",
    )
# Add the given flag to the list of linker flags for the build.
# - `flag` is the flag (e.g. -fno-sized-deallocation)
# - `when` (optional) is a @depends function or option name conditioning
#   when the flag is wanted.
@template
def add_linker_flag(flag, compiler=None, when=None):
    check_and_add_linker_flag(flag, compiler, when, check=False)