# -*- 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/. # 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 ) # 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++", 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, 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, 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" % symbol, when=when, onerror=onerror, ) # 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. @template def check_and_add_flags( flag, flags_collection, test_flags, compiler=None, when=None, check=True ): if compiler is not None: compilers = (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} 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] 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 result = c.try_compile( flags=flags, when=result, check_msg="whether the %s compiler supports %s" % (lang, 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(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 ) # 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(warning, compiler, when, check=False) # Like the warning checks above, but for general compilation flags. @dependable def compilation_flags(): return namespace(cflags=[], cxxflags=[], host_cflags=[], host_cxxflags=[]) # 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)