# -*- 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
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# We support C++14, but we don't want to enable the sized deallocation
# facilities in C++14 yet.
check_and_add_flag("-fno-sized-deallocation", compiler=cxx_compiler)
# Likewise for C++17 and aligned allocation.  It's not immediately obvious
# from the clang and GCC documentation, but they both support this.
check_and_add_flag("-fno-aligned-new", compiler=cxx_compiler)


with only_when(building_with_gnu_compatible_cc):
    with only_when(~developer_options):
        add_flag("-ffunction-sections")
        add_flag("-fdata-sections")
    add_flag("-fno-math-errno")
    add_flag("-fno-exceptions", compiler=cxx_compiler)


# Please keep these last in this file.
add_old_configure_assignment("_COMPILATION_CFLAGS", compilation_flags.cflags)
add_old_configure_assignment("_COMPILATION_CXXFLAGS", compilation_flags.cxxflags)
add_old_configure_assignment("_COMPILATION_HOST_CFLAGS", compilation_flags.host_cflags)
add_old_configure_assignment(
    "_COMPILATION_HOST_CXXFLAGS", compilation_flags.host_cxxflags
)


option(
    "--disable-new-pass-manager",
    help="Use the legacy LLVM pass manager in clang builds",
)


@depends(
    "--enable-new-pass-manager",
    c_compiler,
    host,
    target,
    "MOZ_PGO",
    enable_fuzzing,
    ubsan,
)
def pass_manager(enabled, compiler, host, target, pgo, enable_fuzzing, ubsan):
    if compiler.type not in ("clang", "clang-cl"):
        return None

    # As of clang 13, the default pass manager is the new one.
    if compiler.version >= "13.0.0":
        if enabled:
            return namespace(flags=None, enabled=True)
        if compiler.type == "clang":
            return namespace(flags=["-flegacy-pass-manager"], enabled=False)
        if compiler.type == "clang-cl":
            return namespace(flags=["-Xclang", "-flegacy-pass-manager"], enabled=False)

    if not enabled:
        if compiler.version >= "15.0.0":
            die("--disable-new-pass-manager is only supported with clang < 15")
        return None
    if compiler.version < "9.0.0":
        if enabled.origin != "default":
            die("--enable-new-pass-manager is only supported with clang >= 9")
        return None

    if host.os == "OSX":
        # Some native Mac builds hang with the new pass manager. Given the
        # inability to test in CI, don't take the risk of further breakage.
        if enabled.origin != "default":
            die(
                "--enable-new-pass-manager causes problems on mac hosts with clang < 13"
            )
        return None
    if target.os == "OSX" and not pgo:
        # Also disable when cross-compiling to Mac, because plain-ish opt
        # builds hang. Variants like asan and ccov work fine, but it would be
        # too tedious to test them all here. PGO is the only thing that matters
        # enough to make an exception for.
        if enabled.origin != "default":
            die(
                "--enable-new-pass-manager causes problems on mac builds with clang < 13"
            )
        return None
    if enable_fuzzing and compiler.version < "10.0.0":
        # Clang 9 does not seem to play well with libFuzzer
        if enabled.origin != "default":
            die(
                "--enable-new-pass-manager causes problems on fuzzing builds with clang < 10"
            )
        return None
    if ubsan and compiler.version == "10.0.0":
        # Clang 10.0.0 hangs with some ubsan-inserted code constructs.
        # This was fixed in 10.0.1 (https://llvm.org/pr45835)
        if enabled.origin != "default":
            die(
                "--enable-new-pass-manager causes problems with ubsan builds with clang 10.0.0"
            )
        return None
    if compiler.type == "clang":
        return namespace(flags=["-fexperimental-new-pass-manager"], enabled=True)
    elif compiler.type == "clang-cl":
        return namespace(
            flags=["-Xclang", "-fexperimental-new-pass-manager"], enabled=True
        )


set_config("MOZ_PASS_MANAGER_FLAGS", pass_manager.flags)

# Debugging options
##


@depends(moz_debug, asan, target, "--enable-jemalloc")
def moz_no_debug_rtl(moz_debug, asan, target, enable_jemalloc):
    if not moz_debug or asan:
        return True
    if enable_jemalloc and target.kernel == "WINNT":
        return True


set_config("MOZ_NO_DEBUG_RTL", moz_no_debug_rtl)


@depends(
    try_compile(
        includes=["stdio.h"],
        body='puts("Hello World");',
        check_msg="for valid debug flags",
        flags=debug_flags,
        when=moz_debug,
    ),
    debug_flags,
    when=moz_debug,
)
@imports(_from="mozbuild.shellutil", _import="quote")
def check_debug_flags(check, flags):
    if not check:
        die(f"These compiler flags are invalid: {quote(*flags)}")


# Try to make builds more reproducible and allow sharing built artifacts across
# source and object directories by using -ffile-prefix-map and friends.  To
# "unwind" the prefix maps, use:
#
# (gdb) set substitute-path /topsrcdir/ $topsrcdir/
#
# (lldb) settings set target.source-map /topobjdir/ $topobjdir/
#
# See, for example, https://lldb.llvm.org/use/map.html.
@depends(
    path_remapping,
    path_remappings,
    c_compiler,
)
@imports(_from="os", _import="sep")
def file_prefix_map_flags(path_remapping, path_remappings, compiler):
    if "c" not in path_remapping:
        return []

    if (compiler.type == "gcc" and compiler.version < "8.1") or (
        compiler.type in ("clang", "clang-cl") and compiler.version < "10.0.0"
    ):
        die(
            f"Compiler of type {compiler.type} and version {compiler.version} "
            "does not support --enable-path-remapping."
        )

    flags = []
    for old, new in path_remappings:
        # We would prefer to use just -ffile-prefix-map, but clang-cl doesn't
        # seem to recognize it.
        for flag in ("-fdebug-prefix-map", "-fmacro-prefix-map"):
            flag = f"{flag}={old}={new}"
            if compiler.type in ("gcc", "clang"):
                flags.append(flag)
            elif compiler.type == "clang-cl":
                flags.extend(["-Xclang", flag])

    return flags


set_config("MOZ_FILE_PREFIX_MAP_FLAGS", file_prefix_map_flags)


@depends(developer_options, when=building_with_gnu_compatible_cc)
def check_build_id_uuid(developer_options):
    return developer_options


@depends(developer_options, when=building_with_gnu_compatible_cc)
def check_build_id_sha1(developer_options):
    return not developer_options


check_and_add_flag("-pipe", when=building_with_gcc)

check_and_add_linker_flag("-Wl,--build-id=uuid", when=check_build_id_uuid)
check_and_add_linker_flag("-Wl,--build-id=sha1", when=check_build_id_sha1)

check_and_add_asm_flag("-Wa,--noexecstack", when=building_with_gnu_compatible_cc)
check_and_add_linker_flag("-Wl,-z,noexecstack", when=building_with_gnu_compatible_cc)
check_and_add_linker_flag("-Wl,-z,text", when=building_with_gnu_compatible_cc)
check_and_add_linker_flag("-Wl,-z,relro", when=building_with_gnu_compatible_cc)
check_and_add_linker_flag("-Wl,-z,now", when=building_with_gnu_compatible_cc)
check_and_add_linker_flag("-Wl,-z,nocopyreloc", when=building_with_gnu_compatible_cc)

check_and_add_linker_optimize_flag("-Wl,-dead_strip", when=target_is_darwin & ~dtrace)

have_linker_support_ignore_unresolved = try_link(
    flags=["-Wl,--ignore-unresolved-symbol,environ"],
    check_msg="for --ignore-unresolved-symbol option to the linker",
    when=building_with_gnu_compatible_cc & gcc_use_gnu_ld,
)
add_old_configure_assignment(
    "HAVE_LINKER_SUPPORT_IGNORE_UNRESOLVED", have_linker_support_ignore_unresolved
)


@depends("--enable-address-sanitizer", building_with_gnu_compatible_cc)
def check_Bsymbolic(enable_asan, building_with_gnu_compatible_cc):
    return enable_asan and building_with_gnu_compatible_cc


# ASan assumes no symbols are being interposed, and when that happens,
# it's not happy with it. Inconveniently, since Firefox is exporting
# libffi symbols and Gtk+3 pulls system libffi via libwayland-client,
# system libffi interposes libffi symbols that ASan assumes are in
# libxul, so it barfs about buffer overflows.
# Using -Wl,-Bsymbolic ensures no exported symbol can be interposed.
check_and_add_linker_flag("-Wl,-Bsymbolic", when=check_Bsymbolic)


# Check what kind of list files are supported by the linker
@depends(c_compiler, link, linker_ldflags, extra_toolchain_flags)
@checking("what kind of list files are supported by the linker")
@imports("os")
@imports(_from="__builtin__", _import="open")
@imports(_from="tempfile", _import="mkstemp")
def expand_libs_list_style(c_compiler, link, linker_flags, extra_flags):
    objfd, objname = mkstemp(prefix="conftest.", suffix=".o")
    os.close(objfd)

    listfd, listname = mkstemp(prefix="conftest.", suffix=".list")
    os.write(listfd, objname.encode())
    os.close(listfd)

    outfd, outname = mkstemp(prefix="conftest.", suffix=".out")
    os.close(outfd)

    try:
        cflags = c_compiler.flags

        if (
            try_invoke_compiler(
                # No configure_cache because it would not create the
                # expected output file.
                None,
                [c_compiler.compiler] + cflags,
                c_compiler.language,
                "int main() {}",
                ["-c", "-o", objname],
                wrapper=c_compiler.wrapper,
                onerror=lambda: None,
            )
            is None
        ):
            die("couldn't compile a simple C file")

        ldflags = linker_flags + (extra_flags or [])

        if c_compiler.type == "clang-cl":
            base_linker_cmd = [link] + ldflags + [f"-OUT:{outname}"]
        else:
            base_linker_cmd = [c_compiler.compiler] + cflags + ldflags + ["-o", outname]

        # -filelist is for the OS X linker.  We need to try -filelist
        # first because clang understands @file, but may pass an
        # oversized argument list to the linker depending on the
        # contents of @file.
        options = [
            ("linkerlist", f"-Wl,@{listname}"),
            ("filelist", f"-Wl,@{listname}"),
            ("list", f"@{listname}"),
        ]
        for kind, option in options:
            linker_cmd = base_linker_cmd + [option]
            if check_cmd_output(*linker_cmd, onerror=lambda: None) is not None:
                return kind
        die("Couldn't find one that works")

    finally:
        os.remove(objname)
        os.remove(listname)
        os.remove(outname)


set_config("EXPAND_LIBS_LIST_STYLE", expand_libs_list_style)

# Please keep these last in this file.
add_old_configure_assignment("_COMPILATION_ASFLAGS", asm_flags.asflags)
add_old_configure_assignment("_COMPILATION_HOST_ASFLAGS", asm_flags.host_asflags)
add_old_configure_assignment("_COMPILATION_LDFLAGS", linker_flags.ldflags)
add_old_configure_assignment(
    "_COMPILATION_OPTIMIZE_LDFLAGS", linker_optimize_flags.ldflags
)
add_old_configure_assignment("_COMPILATION_HOST_LDFLAGS", linker_flags.host_ldflags)
