tor-browser

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

compile-checks.configure (20442B)


      1 # -*- Mode: python; c-basic-offset: 4; 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 
      8 # Generates a test program and attempts to compile it. In case of failure, the
      9 # resulting check will return None. If the test program succeeds, it will return
     10 # the output of the test program.
     11 # - `includes` are the includes (as file names) that will appear at the top of
     12 #   the generated test program.
     13 # - `body` is the code that will appear in the main function of the generated
     14 #   test program. `return 0;` is appended to the function body automatically.
     15 # - `language` is the language selection, so that the appropriate compiler is
     16 #   used.
     17 # - `flags` are the flags to be passed to the compiler, in addition to `-c`.
     18 # - `check_msg` is the message to be printed to accompany compiling the test
     19 #   program.
     20 @template
     21 def try_compile(
     22     includes=None,
     23     body="",
     24     language="C++",
     25     flags=None,
     26     check_msg=None,
     27     when=None,
     28     onerror=lambda: None,
     29 ):
     30     compiler = {
     31         "C": c_compiler,
     32         "C++": cxx_compiler,
     33     }[language]
     34 
     35     return compiler.try_compile(
     36         includes, body, flags, check_msg, when=when, onerror=onerror
     37     )
     38 
     39 
     40 # Generates a test program and attempts to link it. In case of failure, the
     41 # resulting check will return None. If the link succeeds, it will return
     42 # True
     43 # - `includes` are the includes (as file names) that will appear at the top of
     44 #   the generated test program.
     45 # - `body` is the code that will appear in the main function of the generated
     46 #   test program. `return 0;` is appended to the function body automatically.
     47 # - `language` is the language selection, so that the appropriate compiler is
     48 #   used.
     49 # - `flags` are the flags to be passed to the compiler.
     50 # - `check_msg` is the message to be printed to accompany compiling the test
     51 #   program.
     52 @template
     53 def try_link(
     54     includes=None,
     55     body="",
     56     language="C++",
     57     flags=None,
     58     check_msg=None,
     59     when=None,
     60     onerror=lambda: None,
     61 ):
     62     compiler = {
     63         "C": c_compiler,
     64         "C++": cxx_compiler,
     65     }[language]
     66 
     67     return compiler.try_link(
     68         linker_ldflags,
     69         includes,
     70         body,
     71         flags,
     72         check_msg,
     73         when=when,
     74         onerror=onerror,
     75     )
     76 
     77 
     78 # Checks for the presence of the given header on the target system by compiling
     79 # a test program including that header. The return value of the template is a
     80 # check function returning True if the header is present, and None if it is not.
     81 # The value of this check function is also used to set a variable (with set_define)
     82 # corresponding to the checked header. For instance, HAVE_MALLOC_H will be set in
     83 # defines if check_header if called with 'malloc.h' as input and malloc.h is
     84 # present on the target.
     85 # - `header` is the header, as a file name, to check for.
     86 # - `language` is the language selection, so that the appropriate compiler is
     87 #   used.
     88 # - `flags` are the flags to be passed to the compiler, in addition to `-c`.
     89 # - `includes` are additional includes, as file names, to appear before the
     90 #   header checked for.
     91 # - `when` is a depends function that if present will make performing the check
     92 #   conditional on the value of that function.
     93 @template
     94 def check_header(
     95     header,
     96     language="C++",
     97     body="",
     98     flags=None,
     99     includes=None,
    100     when=None,
    101     onerror=lambda: None,
    102 ):
    103     if when is None:
    104         when = always
    105 
    106     if includes:
    107         includes = includes[:]
    108     else:
    109         includes = []
    110     includes.append(header)
    111 
    112     have_header = try_compile(
    113         includes=includes,
    114         body=body,
    115         language=language,
    116         flags=flags,
    117         check_msg="for %s" % header,
    118         when=when,
    119         onerror=onerror,
    120     )
    121     header_var = "HAVE_%s" % (
    122         header.upper().replace("-", "_").replace("/", "_").replace(".", "_")
    123     )
    124     set_define(header_var, have_header)
    125     return have_header
    126 
    127 
    128 # A convenience wrapper for check_header for checking multiple headers.
    129 # returns an array of the resulting checks in order corresponding to the
    130 # provided headers.
    131 # - `headers` are the headers to be checked.
    132 # - `kwargs` are keyword arguments passed verbatim to check_header.
    133 
    134 
    135 @template
    136 def check_headers(*headers, **kwargs):
    137     checks = []
    138     for header in headers:
    139         checks.append(check_header(header, **kwargs))
    140     return checks
    141 
    142 
    143 @depends(linker_ldflags, target.kernel)
    144 def check_symbol_flags(linker_ldflags, kernel):
    145     if kernel == "WINNT":
    146         # The build doesn't use the compiler to link things as of writing,
    147         # but some compilation checks do. When using clang-cl, the only
    148         # linker we really support is lld.link, but clang-cl defaults to
    149         # link.exe (even when cross-compiling). So we force the use of
    150         # lld.link for the linkage checks.
    151         return ["-fuse-ld=lld"]
    152     return linker_ldflags
    153 
    154 
    155 # Checks for the presence of the given symbol on the target system by compiling
    156 # a test program. The return value of the template is a check function
    157 # returning True if the symbol can be found, and None if it is not.
    158 @template
    159 def check_symbol(
    160     symbol, language="C", flags=None, msg_extra="", when=None, onerror=lambda: None
    161 ):
    162     if when is None:
    163         when = always
    164 
    165     compiler, extern_c = {
    166         "C": (c_compiler, ""),
    167         "C++": (cxx_compiler, 'extern "C" '),
    168     }[language]
    169 
    170     # Stolen from autoconf 2.13 ; might be irrelevant now, but it doesn't hurt to
    171     # keep using a char return type.
    172     comment = [
    173         "/* Override any gcc2 internal prototype to avoid an error.  */",
    174         "/* We use char because int might match the return type of a gcc2",
    175         "    builtin and then its argument prototype would still apply.  */",
    176     ]
    177 
    178     if flags:
    179 
    180         @depends(check_symbol_flags, dependable(flags))
    181         def flags(base_flags, extra_flags):
    182             if base_flags and extra_flags:
    183                 return base_flags + list(extra_flags)
    184             if extra_flags:
    185                 return extra_flags
    186             return base_flags
    187 
    188     else:
    189         flags = check_symbol_flags
    190 
    191     return compiler.try_run(
    192         header=comment + ["%schar %s();" % (extern_c, symbol)],
    193         body="%s();" % symbol,
    194         flags=flags,
    195         check_msg="for %s%s" % (symbol, msg_extra),
    196         when=when,
    197         onerror=onerror,
    198     )
    199 
    200 
    201 # Checks for the presence of the given symbol in the given library on the
    202 # target system by compiling a test program. The return value of the template
    203 # is a check function returning True if the symbol can be found, and None if it
    204 # is not.
    205 @template
    206 def check_symbol_in_lib(libname, symbol, language="C", when=None, onerror=lambda: None):
    207     flag = f"-l{libname}"
    208     have_symbol = check_symbol(
    209         symbol,
    210         flags=[flag],
    211         msg_extra=f" in {flag}",
    212         language=language,
    213         when=when,
    214         onerror=onerror,
    215     )
    216 
    217     return have_symbol
    218 
    219 
    220 # Same as check_symbol_in_lib but iteratively try libraries in the given libnames until it
    221 # finds one that contains the given symbol.
    222 # Returns None if not found, empty tuple if None is in the given libnames and
    223 # the symbol is present without library linked, a singleton containing the
    224 # library name if found in that library.
    225 @template
    226 def check_symbol_in_libs(
    227     libnames, symbol, language="C", when=None, onerror=lambda: None
    228 ):
    229     have_symbol = never
    230 
    231     found_lib = dependable(namespace(found=False, lib=None))
    232 
    233     kwargs = {
    234         "symbol": symbol,
    235         "language": language,
    236         "when": when or always,
    237         "onerror": onerror,
    238     }
    239 
    240     for libname in libnames:
    241         kwargs["when"] &= ~have_symbol
    242         if libname:
    243             have_symbol = check_symbol_in_lib(libname, **kwargs)
    244         else:
    245             have_symbol = check_symbol(**kwargs)
    246 
    247         add_lib = namespace(found=True, lib=libname)
    248         found_lib = depends(have_symbol, found_lib)(lambda h, f: add_lib if h else f)
    249 
    250     return found_lib
    251 
    252 
    253 # Determine whether to add a given flag to the given lists of flags for C or
    254 # C++ compilation.
    255 # - `flag` is the flag to test
    256 # - `flags_collection` is a @depends function for a namespace of lists of
    257 #    C/C++ compiler flags to add to.
    258 # - `test_flags` is a list of flags to pass to the compiler instead of merely
    259 #   passing `flag`. This is especially useful for checking warning flags. If
    260 #   this list is empty, `flag` will be passed on its own.
    261 # - `compiler` (optional) is the compiler to test against (c_compiler or
    262 #   cxx_compiler, from toolchain.configure). When omitted, both compilers
    263 #   are tested; the list of flags added to is dependent on the compiler tested.
    264 # - `when` (optional) is a @depends function or option name conditioning
    265 #   when the warning flag is wanted.
    266 # - `check`, when not set, skips checking whether the flag is supported and
    267 #   adds it to the list of flags unconditionally.
    268 # - `mode`, can be "compile", "link" or "assemble"
    269 @template
    270 def check_and_add_flags(
    271     flag,
    272     flags_collection,
    273     test_flags=(),
    274     compiler=None,
    275     when=None,
    276     check=True,
    277     mode="compile",
    278 ):
    279     assert mode in ("compile", "link", "assemble")
    280 
    281     if compiler is not None:
    282         compilers = (compiler,)
    283     elif mode in ("link", "assemble"):
    284         compilers = (c_compiler,)
    285     else:
    286         compilers = (c_compiler, cxx_compiler)
    287 
    288     if when is None:
    289         when = always
    290 
    291     results = []
    292 
    293     if test_flags:
    294         flags = test_flags
    295     else:
    296         flags = [flag]
    297 
    298     for c in compilers:
    299         assert c in {c_compiler, cxx_compiler, host_c_compiler, host_cxx_compiler}
    300         if mode == "compile":
    301             lang, list_of_flags = {
    302                 c_compiler: ("C", flags_collection.cflags),
    303                 cxx_compiler: ("C++", flags_collection.cxxflags),
    304                 host_c_compiler: ("host C", flags_collection.host_cflags),
    305                 host_cxx_compiler: ("host C++", flags_collection.host_cxxflags),
    306             }[c]
    307         elif mode == "assemble":
    308             lang, list_of_flags = {
    309                 c_compiler: ("C", flags_collection.asflags),
    310             }[c]
    311         elif mode == "link":
    312             lang, list_of_flags = {
    313                 c_compiler: ("C", flags_collection.ldflags),
    314                 cxx_compiler: ("C++", flags_collection.ldflags),
    315                 host_c_compiler: ("host C", flags_collection.host_ldflags),
    316                 host_cxx_compiler: ("host C++", flags_collection.host_ldflags),
    317             }[c]
    318 
    319         result = when
    320 
    321         if check:
    322 
    323             @depends(c, dependable(flags))
    324             def flags(c, flags):
    325                 # Don't error out just because clang complains about other things.
    326                 if c.type in ("clang", "clang-cl"):
    327                     flags += ["-Wno-error=unused-command-line-argument"]
    328 
    329                 return flags
    330 
    331             if mode == "link":
    332 
    333                 def runner(*args, **kwargs):
    334                     if c in (c_compiler, cxx_compiler):
    335                         return c.try_link(linker_ldflags, *args, **kwargs)
    336                     else:
    337                         return c.try_link(host_linker_ldflags, *args, **kwargs)
    338 
    339                 tool = "linker"
    340             elif mode == "assemble":
    341                 runner = c.try_compile
    342                 tool = "assembler"
    343             else:
    344                 runner = c.try_compile
    345                 tool = "compiler"
    346 
    347             result = runner(
    348                 flags=flags,
    349                 when=result,
    350                 check_msg="whether the %s %s supports %s" % (lang, tool, flag),
    351             )
    352 
    353         @depends(result, list_of_flags)
    354         def maybe_add_flag(result, list_of_flags):
    355             if result:
    356                 list_of_flags.append(flag)
    357 
    358         results.append(result)
    359 
    360     return tuple(results)
    361 
    362 
    363 @dependable
    364 def warnings_flags():
    365     return namespace(cflags=[], cxxflags=[], host_cflags=[], host_cxxflags=[])
    366 
    367 
    368 # Tests whether GCC or clang support the given warning flag, and if it is,
    369 # add it to the list of warning flags for the build.
    370 # - `warning` is the warning flag (e.g. -Wfoo)
    371 # - `compiler` (optional) is the compiler to test against (c_compiler or
    372 #   cxx_compiler, from toolchain.configure). When omitted, both compilers
    373 #   are tested.
    374 # - `when` (optional) is a @depends function or option name conditioning
    375 #   when the warning flag is wanted.
    376 # - `check`, when not set, skips checking whether the flag is supported and
    377 #   adds it to the list of warning flags unconditionally. This is only meant
    378 #   for add_warning().
    379 @template
    380 def check_and_add_warning_impl(warning, compiler=None, when=None, check=True):
    381     # GCC and clang will fail if given an unknown warning option like
    382     # -Wfoobar. But later versions won't fail if given an unknown negated
    383     # warning option like -Wno-foobar. So when we are checking for support
    384     # of a negated warning option, we actually test the positive form, but
    385     # add the negated form to the flags variable.
    386     if warning.startswith("-Wno-") and not warning.startswith("-Wno-error="):
    387         flags = ["-Werror", "-W" + warning[5:]]
    388     elif warning.startswith("-Werror="):
    389         flags = [warning]
    390     else:
    391         flags = ["-Werror", warning]
    392 
    393     return check_and_add_flags(
    394         warning, warnings_flags, flags, compiler=compiler, when=when, check=check
    395     )
    396 
    397 
    398 # When changing this version, please check that no warning support have been
    399 # removed.
    400 clang_latest_release = dependable("21.1.0")
    401 
    402 
    403 @depends(c_compiler, clang_latest_release, when=moz_automation)
    404 def check_latest_release_version(compiler, clang_latest_release):
    405     if (
    406         compiler.type in ("clang", "clang-cl")
    407         and compiler.version > clang_latest_release
    408         # Don't error out on automation when using clang trunk (18.x is the first
    409         # version where trunk is always y.0, and release y.1.*)
    410         and not (compiler.version.major >= 18 and compiler.version.minor == 0)
    411     ):
    412         configure_error("latest clang release version must be updated")
    413 
    414 
    415 # Add the given warning to the list of warning flags for the build.
    416 # - `warning` is the warning flag (e.g. -Wfoo)
    417 # - `compiler` (optional) is the compiler to add the flag for (c_compiler or
    418 #   cxx_compiler, from toolchain.configure). When omitted, the warning flag
    419 #   is added for both compilers.
    420 # - `when` (optional) is a @depends function or option name conditioning
    421 #   when the warning flag is wanted.
    422 @template
    423 def add_warning(warning, compiler=None, when=None):
    424     check_and_add_warning_impl(warning, compiler, when, check=False)
    425 
    426 
    427 # Add the given warning to the list of warning flags for the build.
    428 # - `warning` is the warning flag (e.g. -Wfoo)
    429 # - `compiler` (optional) is the compiler to add the flag for (c_compiler or
    430 #   cxx_compiler, from toolchain.configure). When omitted, the warning flag
    431 #   is added for both compilers.
    432 # - `when` (optional) is a @depends function or option name conditioning
    433 #   when the warning flag is wanted.
    434 # - if min_clang_version is set, do not run the check for clang compiler, use
    435 #   know result instead. Possibly rely on max_clang_version in that case to
    436 #   bound the version check.
    437 @template
    438 def check_and_add_warning(
    439     warning, compiler=None, when=None, max_clang_version=None, min_clang_version=None
    440 ):
    441     if when is None:
    442         when = always
    443     if min_clang_version is None and max_clang_version is None:
    444         return check_and_add_warning_impl(warning, compiler, when, check=True)
    445 
    446     min_clang_version = min_clang_version or "0.0.0"
    447     max_clang_version = (
    448         dependable(max_clang_version) if max_clang_version else clang_latest_release
    449     )
    450     is_compatible_clang = depends(c_compiler, max_clang_version)(
    451         lambda c, max_clang_version: c.type in ("clang", "clang-cl")
    452         and min_clang_version <= c.version <= max_clang_version
    453     )
    454     is_clang = depends(c_compiler.type)(lambda t: t in ("clang", "clang-cl"))
    455     res_clang = check_and_add_warning_impl(
    456         warning, compiler, when & is_compatible_clang, check=False
    457     )
    458     res_other = check_and_add_warning_impl(
    459         warning, compiler, when & (building_with_gcc | ~is_compatible_clang), check=True
    460     )
    461     return res_clang + res_other
    462 
    463 
    464 # Like the warning checks above, but for general compilation flags.
    465 @dependable
    466 def compilation_flags():
    467     return namespace(
    468         cflags=[],
    469         cxxflags=[],
    470         host_cflags=[],
    471         host_cxxflags=[],
    472         cmflags=[],
    473         cmmflags=[],
    474     )
    475 
    476 
    477 # Tests whether GCC or clang support the given compilation flag; if the flag
    478 # is supported, add it to the list of compilation flags for the build.
    479 # - `flag` is the flag to test
    480 # - `compiler` (optional) is the compiler to test against (c_compiler or
    481 #   cxx_compiler, from toolchain.configure). When omitted, both compilers
    482 #   are tested.
    483 # - `when` (optional) is a @depends function or option name conditioning
    484 #   when the warning flag is wanted.
    485 # - `check`, when not set, skips checking whether the flag is supported and
    486 #   adds it to the list of flags unconditionally. This is only meant for
    487 #   add_flag().
    488 @template
    489 def check_and_add_flag(flag, compiler=None, when=None, check=True):
    490     flags = ["-Werror", flag]
    491 
    492     return check_and_add_flags(
    493         flag, compilation_flags, flags, compiler=compiler, when=when, check=check
    494     )
    495 
    496 
    497 # Add the given flag to the list of flags for the build.
    498 # - `flag` is the flag (e.g. -fno-sized-deallocation)
    499 # - `compiler` (optional) is the compiler to add the flag for (c_compiler or
    500 #   cxx_compiler, from toolchain.configure). When omitted, the flag is added
    501 #   for both compilers.
    502 # - `when` (optional) is a @depends function or option name conditioning
    503 #   when the flag is wanted.
    504 @template
    505 def add_flag(warning, compiler=None, when=None):
    506     check_and_add_flag(warning, compiler, when, check=False)
    507 
    508 
    509 # Like the compilation checks above, but for asm flags.
    510 @dependable
    511 def asm_flags():
    512     return namespace(asflags=[])
    513 
    514 
    515 # Tests the given assembler flag is supported; if the flag
    516 # is supported, add it to the list of compilation flags for the build.
    517 # - `flag` is the flag to test
    518 # - `when` (optional) is a @depends function or option name conditioning
    519 #   when the warning flag is wanted.
    520 # - `check`, when not set, skips checking whether the flag is supported and
    521 #   adds it to the list of flags unconditionally. This is only meant for
    522 #   add_flag().
    523 @template
    524 def check_and_add_asm_flag(flag, when=None, check=True):
    525     return check_and_add_flags(
    526         flag,
    527         asm_flags,
    528         [flag],
    529         when=when,
    530         check=check,
    531         mode="assemble",
    532     )
    533 
    534 
    535 # Like the compilation checks above, but for linker flags.
    536 @dependable
    537 def linker_flags():
    538     return namespace(ldflags=[], host_ldflags=[])
    539 
    540 
    541 @dependable
    542 def dso_flags():
    543     return namespace(ldopts=[])
    544 
    545 
    546 # Tests the given linker flag is supported; if the flag
    547 # is supported, add it to the list of compilation flags for the build.
    548 # - `flag` is the flag to test
    549 # - `when` (optional) is a @depends function or option name conditioning
    550 #   when the warning flag is wanted.
    551 # - `check`, when not set, skips checking whether the flag is supported and
    552 #   adds it to the list of flags unconditionally. This is only meant for
    553 #   add_flag().
    554 @template
    555 def check_and_add_linker_flag(flag, compiler=None, when=None, check=True):
    556     return check_and_add_flags(
    557         flag,
    558         linker_flags,
    559         [flag],
    560         compiler=compiler,
    561         when=when,
    562         check=check,
    563         mode="link",
    564     )
    565 
    566 
    567 # Like the compilation checks above, but for linker optimization flags.
    568 @dependable
    569 def linker_optimize_flags():
    570     return namespace(ldflags=[])
    571 
    572 
    573 @template
    574 def check_and_add_linker_optimize_flag(flag, compiler=None, when=None, check=True):
    575     return check_and_add_flags(
    576         flag,
    577         linker_optimize_flags,
    578         [flag],
    579         compiler=compiler,
    580         when=when,
    581         check=check,
    582         mode="link",
    583     )
    584 
    585 
    586 # Add the given flag to the list of linker flags for the build.
    587 # - `flag` is the flag (e.g. -fno-sized-deallocation)
    588 # - `when` (optional) is a @depends function or option name conditioning
    589 #   when the flag is wanted.
    590 @template
    591 def add_linker_flag(flag, compiler=None, when=None):
    592     check_and_add_linker_flag(flag, compiler, when, check=False)