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)