github.com/tiagovtristao/plz@v13.4.0+incompatible/src/parse/rules/cc_rules.build_defs (about) 1 """Rules to build C and C++ targets. 2 3 Note that the C / C++ build process is complex with many options; we attempt to keep things 4 as high-level as possible here but expose flags to tune things as needed. 5 6 As a general note, most of the rules work by attaching labels indicating various flags etc which 7 later rules need to know about. These get picked up by later rules to adjust commands; this way 8 you can write a cc_library rule specifying e.g. linker_flags = ['-lz'] and not have to re-specify 9 that on every single cc_binary / cc_test that transitively depends on that library. 10 """ 11 12 _COVERAGE_FLAGS = ' -ftest-coverage -fprofile-arcs -fprofile-dir=.' 13 # OSX's ld uses --all_load / --noall_load instead of --whole-archive. 14 _WHOLE_ARCHIVE = '-all_load' if CONFIG.OS == 'darwin' else '--whole-archive' 15 _NO_WHOLE_ARCHIVE = '-noall_load' if CONFIG.OS == 'darwin' else '--no-whole-archive' 16 17 18 def cc_library(name:str, srcs:list=[], hdrs:list=[], private_hdrs:list=[], deps:list=[], 19 visibility:list=None, test_only:bool&testonly=False, compiler_flags:list&cflags&copts=[], 20 linker_flags:list&ldflags&linkopts=[], pkg_config_libs:list=[], pkg_config_cflags:list=[], includes:list=[], 21 defines:list|dict=[], alwayslink:bool=False, linkstatic:bool=False, _c=False, 22 textual_hdrs:list=[], _module:bool=False, _interfaces:list=[]): 23 """Generate a C++ library target. 24 25 Args: 26 name (str): Name of the rule 27 srcs (list): C++ source files to compile. 28 hdrs (list): Header files. These will be made available to dependent rules, so the distinction 29 between srcs and hdrs is important. 30 private_hdrs (list): Header files that are available only to this rule and not exported to 31 dependent rules. 32 deps (list): Dependent rules. 33 visibility (list): Visibility declaration for this rule. 34 test_only (bool): If True, is only available to other test rules. 35 compiler_flags (list): Flags to pass to the compiler. 36 linker_flags (list): Flags to pass to the linker; these will not be used here but will be 37 picked up by a cc_binary or cc_test rule. 38 pkg_config_libs (list): Libraries to declare a dependency on using `pkg-config --libs`. Again, the ldflags 39 will be picked up by cc_binary or cc_test rules. 40 pkg_config_cflags (list): Libraries to declare a dependency on using `pkg-config --cflags`. Again, the ldflags 41 will be picked up by cc_binary or cc_test rules. 42 includes (list): List of include directories to be added to the compiler's path. 43 defines (list | dict): List of tokens to define in the preprocessor. 44 Alternatively can be a dict of name -> value to define, in which case 45 values are surrounded by quotes. 46 alwayslink (bool): If True, any binaries / tests using this library will link in all symbols, 47 even if they don't directly reference them. This is useful for e.g. having 48 static members that register themselves at construction time. 49 linkstatic (bool): Only provided for Bazel compatibility. Has no actual effect. 50 textual_hdrs (list): Also provided for Bazel compatibility. Effectively works the same as hdrs for now. 51 """ 52 # Bazel suggests passing nonexported header files in 'srcs'. We however treat 53 # srcs as things to actually compile and must mark a distinction. 54 if CONFIG.BAZEL_COMPATIBILITY: 55 src_hdrs = [src for src in srcs if src.endswith('.h') or src.endswith('.inc')] 56 srcs = [src for src in srcs if src not in src_hdrs] 57 # This is rather nasty; people seem to be relying on being able to reuse 58 # headers that they've put in srcs. We hence need to re-export them here, but really 59 # they should be added to private_hdrs instead. 60 hdrs += src_hdrs 61 hdrs += textual_hdrs 62 # Found this in a few cases... can't pass -pthread to the linker. 63 linker_flags = ['-lpthread' if l == '-pthread' else l for l in linker_flags] 64 65 # Handle defines being passed as a dict, as a nicety for the user. 66 if isinstance(defines, dict): 67 defines = [k if v is None else f'{k}=\\"{v}\\"' for k, v in sorted(defines.items())] 68 69 pkg_name = package_name() 70 labels = (['cc:ld:' + flag for flag in linker_flags] + 71 ['cc:pc:' + lib for lib in pkg_config_libs] + 72 ['cc:pcc:' + cflag for cflag in pkg_config_cflags] + 73 ['cc:inc:' + join_path(pkg_name, include) for include in includes] + 74 ['cc:def:' + define for define in defines]) 75 76 if not srcs and not _interfaces: 77 # Header-only library, no compilation needed. 78 return filegroup( 79 name = name, 80 srcs = hdrs, 81 exported_deps = deps, 82 labels = labels, 83 test_only = test_only, 84 visibility = visibility, 85 output_is_complete = False, 86 ) 87 88 # Collect the headers for other rules 89 requires = ['cc_hdrs', 'cc_mod'] 90 hdrs_rule = filegroup( 91 name = name, 92 tag = 'hdrs', 93 srcs = hdrs, 94 requires = requires, 95 deps = None if _module else deps, 96 test_only = test_only, 97 labels = labels, 98 output_is_complete = False, 99 ) 100 provides = {'cc_hdrs': hdrs_rule} 101 102 if _module: 103 compiler_flags += ['-fmodules-ts' if CONFIG.CC_MODULES_CLANG else '-fmodules'] 104 # TODO(pebers): handle includes and defines in _library_cmds as well. 105 pre_build = _library_transitive_labels(_c, compiler_flags, pkg_config_libs, pkg_config_cflags) if (deps or includes or defines or _interfaces) else None 106 pkg = package_name() 107 108 if _interfaces: 109 # Generate the module interface file 110 xflags = ['-fmodules-ts --precompile -x c++-module -o $OUT' if CONFIG.CC_MODULES_CLANG else '-fmodules -fmodule-output=$OUT'] 111 cmds, tools = _library_cmds(_c, compiler_flags + xflags, pkg_config_libs, pkg_config_cflags, archive=False) 112 interface_rule = build_rule( 113 name = name, 114 tag = 'interface', 115 srcs = {'srcs': _interfaces, 'hdrs': hdrs, 'priv': private_hdrs}, 116 outs = [name + '.pcm'], 117 cmd = cmds, 118 building_description = 'Compiling...', 119 requires = requires, 120 test_only = test_only, 121 labels = labels + [f'cc:mod:{pkg}/{name}.pcm'], 122 tools = tools, 123 needs_transitive_deps = True, 124 ) 125 srcs += _interfaces 126 all_deps = deps + [interface_rule] 127 provides['cc_mod'] = interface_rule 128 else: 129 all_deps = deps 130 131 cmds, tools = _library_cmds(_c, compiler_flags, pkg_config_libs, pkg_config_cflags) 132 if len(srcs) > 1: 133 # Compile all the sources separately, this is much faster for large numbers of files 134 # than giving them all to gcc in one invocation. 135 a_rules = [] 136 for src in srcs: 137 suffix = src.replace('/', '_').replace('.', '_').replace(':', '_').replace('|', '_') 138 a_name = f'_{name}#{suffix}' 139 a_rule = build_rule( 140 name=a_name, 141 srcs={'srcs': [src], 'hdrs': hdrs, 'priv': private_hdrs}, 142 outs=[a_name + '.a'], 143 optional_outs=['*.gcno'], # For coverage 144 deps=deps if src in _interfaces else all_deps, 145 cmd=cmds, 146 building_description='Compiling...', 147 requires=requires, 148 test_only=test_only, 149 labels=labels, 150 tools=tools, 151 pre_build=pre_build, 152 needs_transitive_deps=True, 153 ) 154 a_rules.append(a_rule) 155 156 # Combine the archives into one. 157 a_rule = build_rule( 158 name = name, 159 tag = 'a', 160 srcs = {'srcs': a_rules}, 161 outs = [name + '.a'], 162 cmd = '$TOOLS_JARCAT ar --combine && $TOOLS_AR s $OUT', 163 building_description = 'Archiving...', 164 test_only = test_only, 165 labels = labels, 166 output_is_complete = True, 167 tools = { 168 'jarcat': [CONFIG.JARCAT_TOOL], 169 'ar': [CONFIG.AR_TOOL], 170 }, 171 ) 172 if alwayslink: 173 labels.append(f'cc:al:{pkg}/{name}.a') 174 175 # Filegroup to pick that up with extra deps. This is a little annoying but means that 176 # things depending on this get the combined rule and not the individual ones, but do get 177 # all the other dependencies which are probably important. 178 lib_rule = filegroup( 179 name = name, 180 tag = 'lib', 181 srcs = [a_rule], 182 deps = deps, 183 requires = ['cc_mod'] if _module else None, 184 test_only = test_only, 185 labels = labels, 186 output_is_complete=False, 187 ) 188 189 else: 190 # Single source file, optimise slightly by not extracting & remerging the archive. 191 cc_rule = build_rule( 192 name=name, 193 tag='cc', 194 srcs={'srcs': srcs, 'hdrs': hdrs, 'priv': private_hdrs}, 195 outs=[name + '.a'], 196 optional_outs=['*.gcno'], # For coverage 197 deps=deps if srcs == _interfaces else all_deps, 198 cmd=cmds, 199 building_description='Compiling...', 200 requires=requires, 201 test_only=test_only, 202 labels=labels, 203 tools=tools, 204 pre_build=pre_build, 205 needs_transitive_deps=True, 206 ) 207 if alwayslink: 208 labels.append(f'cc:al:{pkg}/{name}.a') 209 # Need another rule to cover require / provide stuff. This is getting a bit complicated... 210 lib_rule = filegroup( 211 name = name, 212 tag = 'lib', 213 srcs = [cc_rule], 214 deps = deps, 215 requires = ['cc_mod'] if _module else None, 216 test_only = test_only, 217 labels = labels, 218 output_is_complete=False, 219 ) 220 221 provides['cc'] = lib_rule 222 return filegroup( 223 name=name, 224 srcs=[lib_rule], 225 deps=[hdrs_rule], 226 provides=provides, 227 test_only=test_only, 228 visibility=visibility, 229 output_is_complete=False, 230 ) 231 232 233 def cc_object(name:str, src:str, hdrs:list=[], private_hdrs:list=[], out:str=None, test_only:bool&testonly=False, 234 compiler_flags:list&cflags&copts=[], linker_flags:list&ldflags&linkopts=[], pkg_config_libs:list=[], pkg_config_cflags:list=[], 235 includes:list=[], defines:list|dict=[], alwayslink:bool=False, _c=False, visibility:list=None, deps:list=[]): 236 """Generate a C or C++ object file from a single source. 237 238 N.B. This is fairly low-level; for most use cases cc_library should be preferred. 239 240 Args: 241 name (str): Name of the rule 242 src (str): C or C++ source file to compile. This can be another rule, but if so it must 243 have exactly one output. 244 hdrs (list): Header files. These will be made available to dependent rules, so the distinction 245 between srcs and hdrs is important. 246 private_hdrs (list): Header files that are available only to this rule and not exported to 247 dependent rules. 248 out (str): Name of the output file. Defaults to name + .o. 249 deps (list): Dependent rules. 250 visibility (list): Visibility declaration for this rule. 251 test_only (bool): If True, is only available to other test rules. 252 compiler_flags (list): Flags to pass to the compiler. 253 linker_flags (list): Flags to pass to the linker; these will not be used here but will be 254 picked up by a cc_binary or cc_test rule. 255 pkg_config_libs (list): Libraries to declare a dependency on using `pkg-config --libs`. Again, the ldflags 256 will be picked up by cc_binary or cc_test rules. 257 pkg_config_cflags (list): Libraries to declare a dependency on using `pkg-config --cflags`. Again, the ldflags 258 will be picked up by cc_binary or cc_test rules. 259 includes (list): List of include directories to be added to the compiler's path. 260 defines (list | dict): List of tokens to define in the preprocessor. 261 Alternatively can be a dict of name -> value to define, in which case 262 values are surrounded by quotes. 263 alwayslink (bool): If True, any binaries / tests using this library will link in all symbols, 264 even if they don't directly reference them. This is useful for e.g. having 265 static members that register themselves at construction time. 266 """ 267 # Handle defines being passed as a dict, as a nicety for the user. 268 if isinstance(defines, dict): 269 defines = [k if v is None else f'{k}=\\"{v}\\"' for k, v in sorted(defines.items())] 270 271 pkg = package_name() 272 labels = (['cc:ld:' + flag for flag in linker_flags] + 273 ['cc:pc:' + lib for lib in pkg_config_libs] + 274 ['cc:pcc:' + cflag for cflag in pkg_config_cflags] + 275 [f'cc:inc:{pkg}/{include}' for include in includes] + 276 ['cc:def:' + define for define in defines]) 277 if alwayslink: 278 labels.append('cc:al:{pkg}/{name}.a') 279 cmds, tools = _library_cmds(_c, compiler_flags, pkg_config_libs, pkg_config_cflags, archive=False) 280 281 return build_rule( 282 name=name, 283 srcs={'srcs': [src], 'hdrs': hdrs, 'priv': private_hdrs}, 284 outs=[out or name + '.o'], 285 optional_outs=['*.gcno'], # For coverage 286 deps=deps, 287 cmd=cmds, 288 building_description='Compiling...', 289 requires=['cc_hdrs', 'cc_mod'], 290 test_only=test_only, 291 labels=labels, 292 tools=tools, 293 pre_build=_library_transitive_labels(_c, compiler_flags, pkg_config_libs, pkg_config_cflags, archive=False) 294 if (deps or includes or defines) else None, 295 needs_transitive_deps=True, 296 ) 297 298 299 def cc_static_library(name:str, srcs:list=[], hdrs:list=[], compiler_flags:list&cflags&copts=[], 300 linker_flags:list&ldflags&linkopts=[], deps:list=[], visibility:list=None, 301 test_only:bool&testonly=False, pkg_config_libs:list=[], pkg_config_cflags:list=[],_c=False): 302 """Generates a C++ static library (.a). 303 304 This is essentially just a collection of other cc_library rules into a single archive. 305 Optionally this rule can have sources of its own, but it's quite reasonable just to use 306 it as a collection of other rules. 307 308 Args: 309 name (str): Name of the rule 310 srcs (list): C or C++ source files to compile. 311 hdrs (list): Header files. 312 compiler_flags (list): Flags to pass to the compiler. 313 linker_flags (list): Flags to pass to the linker. 314 deps (list): Dependent rules. 315 visibility (list): Visibility declaration for this rule. 316 test_only (bool): If True, is only available to other test rules. 317 pkg_config_libs (list): Libraries to declare a dependency on using `pkg-config --libs` 318 pkg_config_cflags (list): Libraries to declare a dependency on using `pkg-config --cflags` 319 """ 320 provides = None 321 if srcs: 322 lib_rule = cc_library( 323 name = f'_{name}#lib', 324 srcs = srcs, 325 hdrs = hdrs, 326 compiler_flags = compiler_flags, 327 linker_flags = linker_flags, 328 deps = deps, 329 test_only = test_only, 330 pkg_config_libs = pkg_config_libs, 331 pkg_config_cflags = pkg_config_cflags, 332 _c=_c, 333 ) 334 deps += [lib_rule, f':_{name}#lib_hdrs'] 335 provides = { 336 'cc_hdrs': f':_{name}#lib_hdrs', 337 'cc': ':' + name, 338 } 339 return build_rule( 340 name = name, 341 deps = deps, 342 outs = [f'lib{name}.a'], 343 cmd = '$TOOLS_JARCAT ar --find && $TOOLS_AR s $OUT', 344 needs_transitive_deps = True, 345 output_is_complete = True, 346 visibility = visibility, 347 test_only = test_only, 348 building_description = 'Archiving...', 349 provides = provides, 350 requires = ['cc'], 351 tools = { 352 'jarcat': [CONFIG.JARCAT_TOOL], 353 'ar': [CONFIG.AR_TOOL], 354 }, 355 ) 356 357 358 def cc_shared_object(name:str, srcs:list=[], hdrs:list=[], out:str='', compiler_flags:list&cflags&copts=[], 359 linker_flags:list&ldflags&linkopts=[], deps:list=[], visibility:list=None, test_only:bool&testonly=False, 360 pkg_config_libs:list=[], pkg_config_cflags:list=[], includes:list=[], _c=False): 361 """Generates a C++ shared object (.so) with its dependencies linked in. 362 363 Args: 364 name (str): Name of the rule 365 srcs (list): C or C++ source files to compile. 366 hdrs (list): Header files. These will be made available to dependent rules, so the distinction 367 between srcs and hdrs is important. 368 out (str): Name of the output .so. Defaults to name + .so. 369 compiler_flags (list): Flags to pass to the compiler. 370 linker_flags (list): Flags to pass to the linker. 371 deps (list): Dependent rules. 372 visibility (list): Visibility declaration for this rule. 373 test_only (bool): If True, is only available to other test rules. 374 pkg_config_libs (list): Libraries to declare a dependency on using `pkg-config --libs` 375 pkg_config_cflags (list): Libraries to declare a dependency on using `pkg-config --cflags` 376 includes (list): Include directories to be added to the compiler's lookup path. 377 """ 378 if CONFIG.DEFAULT_LDFLAGS: 379 linker_flags.append(CONFIG.DEFAULT_LDFLAGS) 380 381 provides = None 382 if srcs: 383 lib_rule = cc_library( 384 name = f'_{name}#lib', 385 srcs = srcs, 386 hdrs = hdrs, 387 compiler_flags = compiler_flags, 388 linker_flags = linker_flags, 389 deps = deps, 390 test_only = test_only, 391 pkg_config_libs = pkg_config_libs, 392 pkg_config_cflags = pkg_config_cflags, 393 includes = includes, 394 _c=_c, 395 ) 396 deps += [lib_rule, f':_{name}#lib_hdrs'] 397 provides = { 398 'cc_hdrs': f':_{name}#lib_hdrs', 399 'cc': ':' + name, 400 } 401 cmds, tools = _binary_cmds(_c, linker_flags, pkg_config_libs, shared=True) 402 return build_rule( 403 name=name, 404 srcs={'srcs': srcs, 'hdrs': hdrs}, 405 outs=[out or name + '.so'], 406 deps=deps, 407 visibility=visibility, 408 cmd=cmds, 409 building_description='Linking...', 410 binary=True, 411 needs_transitive_deps=True, 412 output_is_complete=True, 413 provides=provides, 414 tools=tools, 415 test_only=test_only, 416 requires=['cc', 'cc_hdrs'], 417 pre_build=_binary_transitive_labels(_c, linker_flags, pkg_config_libs, shared=True) if deps else None, 418 ) 419 420 421 def cc_module(name:str, srcs:list=[], hdrs:list=[], interfaces:list=[], private_hdrs:list=[], 422 deps:list=[], visibility:list=None, test_only:bool&testonly=False, 423 compiler_flags:list&cflags&copts=[], linker_flags:list&ldflags&linkopts=[], 424 pkg_config_libs:list=[], pkg_config_cflags:list=[], includes:list=[], 425 defines:list|dict=[], alwayslink:bool=False): 426 """Generate a C++ module. 427 428 This is still experimental. Currently it has only been tested with clang - you can use 429 `package(cc_modules_clang=False)` to try with gcc (this may be needed since the two support 430 different flag structures at present). 431 432 Args: 433 name (str): Name of the rule 434 srcs (list): C++ source files to compile. 435 hdrs (list): Header files. These will be made available to dependent rules, so the distinction 436 between srcs and hdrs is important. 437 interfaces (list): Module interface files. Again, these are treated differently to `srcs` in 438 terms of compilation so the distinction is important. 439 private_hdrs (list): Header files that are available only to this rule and not exported to 440 dependent rules. 441 deps (list): Dependent rules. 442 visibility (list): Visibility declaration for this rule. 443 test_only (bool): If True, is only available to other test rules. 444 compiler_flags (list): Flags to pass to the compiler. 445 linker_flags (list): Flags to pass to the linker; these will not be used here but will be 446 picked up by a cc_binary or cc_test rule. 447 pkg_config_libs (list): Libraries to declare a dependency on using `pkg-config --libs`. Again, the ldflags 448 will be picked up by cc_binary or cc_test rules. 449 pkg_config_cflags (list): Libraries to declare a dependency on using `pkg-config --cflags`. Again, the ldflags 450 will be picked up by cc_binary or cc_test rules. 451 includes (list): List of include directories to be added to the compiler's path. 452 defines (list | dict): List of tokens to define in the preprocessor. 453 Alternatively can be a dict of name -> value to define, in which case 454 values are surrounded by quotes. 455 alwayslink (bool): If True, any binaries / tests using this library will link in all symbols, 456 even if they don't directly reference them. This is useful for e.g. having 457 static members that register themselves at construction time. 458 linkstatic (bool): Only provided for Bazel compatibility. Has no actual effect. 459 textual_hdrs (list): Also provided for Bazel compatibility. Effectively works the same as hdrs for now. 460 """ 461 return cc_library( 462 name = name, 463 srcs = srcs, 464 hdrs = hdrs, 465 _interfaces = interfaces, 466 private_hdrs = private_hdrs, 467 deps = deps, 468 visibility = visibility, 469 test_only = test_only, 470 compiler_flags = compiler_flags, 471 linker_flags = linker_flags, 472 pkg_config_libs = pkg_config_libs, 473 pkg_config_cflags = pkg_config_cflags, 474 includes = includes, 475 defines = defines, 476 alwayslink = alwayslink, 477 _module = True, 478 ) 479 480 481 def cc_binary(name:str, srcs:list=[], hdrs:list=[], private_hdrs:list=[], 482 compiler_flags:list&cflags&copts=[], linker_flags:list&ldflags&linkopts=[], 483 deps:list=[], visibility:list=None, pkg_config_libs:list=[], 484 pkg_config_cflags:list=[], test_only:bool&testonly=False, static:bool=False, _c=False, 485 linkstatic:bool=False): 486 """Builds a binary from a collection of C++ rules. 487 488 Args: 489 name (str): Name of the rule 490 srcs (list): C or C++ source files to compile. 491 hdrs (list): Header files. 492 private_hdrs (list): Header files that are available only to this rule and not exported to 493 dependent rules. 494 compiler_flags (list): Flags to pass to the compiler. 495 linker_flags (list): Flags to pass to the linker. 496 deps (list): Dependent rules. 497 visibility (list): Visibility declaration for this rule. 498 pkg_config_libs (list): Libraries to declare a dependency on using `pkg-config --libs` 499 pkg_config_cflags (list): Libraries to declare a dependency on using `pkg-config --cflags` 500 test_only (bool): If True, this rule can only be used by tests. 501 static (bool): If True, the binary will be linked statically. 502 linkstatic (bool): Only provided for Bazel compatibility. Has no actual effect since we always 503 link roughly equivalently to their "mostly-static" mode. 504 """ 505 if CONFIG.BAZEL_COMPATIBILITY: 506 linker_flags = ['-lpthread' if l == '-pthread' else l for l in linker_flags] 507 if CONFIG.DEFAULT_LDFLAGS: 508 linker_flags.append(CONFIG.DEFAULT_LDFLAGS) 509 if static: 510 linker_flags.append('-static') 511 cmds, tools = _binary_cmds(_c, linker_flags, pkg_config_libs) 512 if srcs: 513 if static: 514 compiler_flags.append('-static -static-libgcc') 515 lib_rule = cc_library( 516 name=f'_{name}#lib', 517 srcs=srcs, 518 hdrs=hdrs, 519 private_hdrs=private_hdrs, 520 deps=deps, 521 pkg_config_libs=pkg_config_libs, 522 pkg_config_cflags=pkg_config_cflags, 523 compiler_flags=compiler_flags, 524 test_only=test_only, 525 _c=_c, 526 ) 527 deps.append(lib_rule) 528 return build_rule( 529 name=name, 530 outs=[name], 531 deps=deps, 532 visibility=visibility, 533 cmd=cmds, 534 building_description='Linking...', 535 binary=True, 536 needs_transitive_deps=True, 537 output_is_complete=True, 538 requires=['cc'], 539 tools=tools, 540 pre_build=_binary_transitive_labels(_c, linker_flags, pkg_config_libs), 541 test_only=test_only, 542 ) 543 544 545 def cc_test(name:str, srcs:list=[], hdrs:list=[], compiler_flags:list&cflags&copts=[], 546 linker_flags:list&ldflags&linkopts=[], pkg_config_libs:list=[], 547 pkg_config_cflags:list=[], deps:list=[], worker:str='', data:list=[], 548 visibility:list=[], flags:str='', labels:list&features&tags=[], flaky:bool|int=0, 549 test_outputs:list=[], size:str=None, timeout:int=0, container:bool|dict=False, 550 sandbox:bool=None, write_main:bool=False, linkstatic:bool=False, _c=False): 551 """Defines a C++ test. 552 553 We template in a main file so you don't have to supply your own. 554 (Later we might allow that to be configured to help support other unit test frameworks). 555 556 Args: 557 name (str): Name of the rule 558 srcs (list): C or C++ source files to compile. 559 hdrs (list): Header files. 560 compiler_flags (list): Flags to pass to the compiler. 561 linker_flags (list): Flags to pass to the linker. 562 pkg_config_libs (list): Libraries to declare a dependency on using `pkg-config --libs` 563 pkg_config_cflags (list): Libraries to declare a dependency on using `pkg-config --cflags` 564 deps (list): Dependent rules. 565 worker (str): Reference to worker script, A persistent worker process that is used to set up the test. 566 data (list): Runtime data files for this test. 567 visibility (list): Visibility declaration for this rule. 568 flags (str): Flags to apply to the test invocation. 569 labels (list): Labels to attach to this test. 570 flaky (bool | int): If true the test will be marked as flaky and automatically retried. 571 test_outputs (list): Extra test output files to generate from this test. 572 size (str): Test size (enormous, large, medium or small). 573 timeout (int): Length of time in seconds to allow the test to run for before killing it. 574 container (bool | dict): If true the test is run in a container (eg. Docker). 575 sandbox (bool): Sandbox the test on Linux to restrict access to namespaces such as network. 576 write_main (bool): Deprecated, has no effect. See `plz help testmain` for more information 577 about how to define a default dependency for the test main. 578 linkstatic (bool): Only provided for Bazel compatibility. Has no actual effect since we always 579 link roughly equivalently to their "mostly-static" mode. 580 """ 581 582 if CONFIG.BAZEL_COMPATIBILITY: 583 linker_flags = ['-lpthread' if l == '-pthread' else l for l in linker_flags] 584 timeout, labels = _test_size_and_timeout(size, timeout, labels) 585 if CONFIG.DEFAULT_LDFLAGS: 586 linker_flags.append(CONFIG.DEFAULT_LDFLAGS) 587 if CONFIG.CC_TEST_MAIN and not _c: 588 deps += [CONFIG.CC_TEST_MAIN] 589 cmds, tools = _binary_cmds(_c, linker_flags, pkg_config_libs) 590 591 if srcs: 592 lib_rule = cc_library( 593 name=f'_{name}#lib', 594 srcs=srcs, 595 hdrs=hdrs, 596 deps=deps, 597 pkg_config_libs=pkg_config_libs, 598 pkg_config_cflags=pkg_config_cflags, 599 compiler_flags=compiler_flags, 600 test_only=True, 601 alwayslink=True, 602 _c=_c, 603 ) 604 deps.append(lib_rule) 605 606 test_cmd = f'$TEST {flags}' 607 if worker: 608 test_cmd = f'$(worker {worker}) && {test_cmd} ' 609 deps += [worker] 610 611 if CONFIG.CPP_COVERAGE: 612 test_cmd = { 613 'opt': test_cmd, 614 'dbg': test_cmd, 615 'cover': test_cmd + '; R=$?; cp $GCNO_DIR/*.gcno . && gcov *.gcda && cat *.gcov > test.coverage; exit $R' 616 } 617 618 return build_rule( 619 name=name, 620 outs=[name], 621 deps=deps, 622 data=data, 623 visibility=visibility, 624 cmd=cmds, 625 test_cmd=test_cmd, 626 building_description='Linking...', 627 binary=True, 628 test=True, 629 needs_transitive_deps=True, 630 output_is_complete=True, 631 requires=['cc', 'cc_hdrs'], 632 labels=labels, 633 tools=tools, 634 pre_build=_binary_transitive_labels(_c, linker_flags, pkg_config_libs), 635 flaky=flaky, 636 test_outputs=test_outputs, 637 test_timeout=timeout, 638 container=container, 639 test_sandbox=sandbox, 640 ) 641 642 643 def cc_embed_binary(name:str, src:str, deps:list=[], visibility:list=None, 644 test_only:bool&testonly=False, namespace:str=None, _c:bool=False): 645 """Build rule to embed an arbitrary binary file into a C library. 646 647 You can depend on the output of this as though it were a cc_library rule. 648 There are five functions available to access the data once compiled, all of which are 649 prefixed with the file's basename: 650 filename_start(): returns a const char* pointing to the beginning of the data. 651 filename_end(): returns a const char* pointing to the end of the data. 652 filename_size(): returns the length of the data in bytes. 653 filename_start_nc(): returns a char* pointing to the beginning of the data. 654 This is a convenience wrapper using const_cast, you should not 655 mutate the contents of the returned pointer. 656 filename_end_nc(): returns a char* pointing to the end of the data. 657 Again, don't mutate the contents of the pointer. 658 You don't own the contents of any of these pointers so don't try to delete them :) 659 660 Args: 661 name (str): Name of the rule. 662 src (str): Source file to embed. 663 deps (list): Dependencies. 664 visibility (list): Rule visibility. 665 test_only (bool): If True, is only available to test rules. 666 namespace (str): Allows specifying the namespace the symbols will be available in. 667 """ 668 if src.startswith(':') or src.startswith('/'): 669 deps += [src] 670 namespace = namespace or CONFIG.DEFAULT_NAMESPACE 671 if not namespace and not _c: 672 raise ValueError('You must either pass namespace= to cc_library or set the default namespace in .plzconfig') 673 darwin = CONFIG.OS == 'darwin' 674 hdr_contents = _C_HEADER_CONTENTS if _c else _CC_HEADER_CONTENTS 675 hdr_rule = build_rule( 676 name=name, 677 tag='hdr', 678 outs=[name + '.h'], 679 srcs=[src], 680 deps=deps, 681 cmd='; '.join([ 682 # This replacement roughly mimics what ld will do to munge it into a symbol name. 683 'export ENCODED_FILENAME="${SRCS//[\\/\\.]/_}"', 684 f'export BINARY_NAME="{name}"', 685 f'export NAMESPACE="{namespace}"', 686 f'echo "{hdr_contents}" > $OUT', 687 ]), 688 visibility=visibility, 689 building_description='Writing header...', 690 requires=['cc'], 691 test_only=test_only, 692 ) 693 694 tools = {'jarcat': [CONFIG.JARCAT_TOOL], 'ar': [CONFIG.AR_TOOL]} 695 if darwin: 696 # OSX's ld doesn't support '--format binary', and this is the least fiddly 697 # alternative. Requiring an additional tool is a bit suboptimal but probably 698 # in the end easier than the alternatives. 699 cmd = ' && '.join([ 700 'export ENCODED_FILENAME=${SRCS//[\\/\\.]/_}', 701 f'echo "{_CC_DARWIN_ASM_CONTENTS}" > embedded.asm', 702 '$TOOLS_ASM -fmacho64 embedded.asm -o ${OUTS/.a/.o}', 703 '$TOOLS_JARCAT ar --srcs ${OUTS/.a/.o}', 704 '$TOOLS_AR s $OUTS', 705 ]) 706 tools['asm'] = [CONFIG.ASM_TOOL] 707 else: 708 cmd = '$TOOLS_LD -r --format binary %s -o ${OUTS/.a/.o} $SRC && $TOOLS_JARCAT ar --srcs ${OUTS/.a/.o} && $TOOLS_AR s $OUTS' % CONFIG.DEFAULT_LDFLAGS 709 tools['ld'] = [CONFIG.LD_TOOL] 710 711 lib_rule = build_rule( 712 name = name, 713 tag = 'lib', 714 srcs=[src], 715 outs=['lib%s.a' % name], 716 deps=deps, 717 cmd=cmd, 718 visibility=visibility, 719 building_description='Embedding...', 720 requires=['cc'], 721 tools=tools, 722 test_only=test_only, 723 ) 724 return filegroup( 725 name=name, 726 srcs=[lib_rule, hdr_rule], 727 visibility=visibility, 728 test_only=test_only, 729 provides={ 730 'cc_hdrs': hdr_rule, 731 'cc': lib_rule, 732 }, 733 ) 734 735 736 _CC_HEADER_CONTENTS = """\ 737 #ifdef __cplusplus 738 namespace ${NAMESPACE} { 739 extern \\"C\\" { 740 #endif // __cplusplus 741 extern const char _binary_${ENCODED_FILENAME}_start[]; 742 extern const char _binary_${ENCODED_FILENAME}_end[]; 743 #ifdef __cplusplus 744 } 745 #endif // __cplusplus 746 747 // Nicer aliases. 748 inline const char* ${BINARY_NAME}_start() { 749 return _binary_${ENCODED_FILENAME}_start; 750 } 751 inline const char* ${BINARY_NAME}_end() { 752 return _binary_${ENCODED_FILENAME}_end; 753 } 754 inline unsigned long ${BINARY_NAME}_size() { 755 return _binary_${ENCODED_FILENAME}_end - _binary_${ENCODED_FILENAME}_start; 756 } 757 inline char* ${BINARY_NAME}_start_nc() { 758 return (char*)(_binary_${ENCODED_FILENAME}_start); 759 } 760 inline char* ${BINARY_NAME}_end_nc() { 761 return (char*)(_binary_${ENCODED_FILENAME}_end); 762 } 763 #ifdef __cplusplus 764 } // namespace ${NAMESPACE} 765 #endif // __cplusplus 766 """ 767 768 _C_HEADER_CONTENTS = """\ 769 extern const char _binary_${ENCODED_FILENAME}_start[]; 770 extern const char _binary_${ENCODED_FILENAME}_end[]; 771 772 inline const char* ${BINARY_NAME}_start() { 773 return _binary_${ENCODED_FILENAME}_start; 774 } 775 inline const char* ${BINARY_NAME}_end() { 776 return _binary_${ENCODED_FILENAME}_end; 777 } 778 inline unsigned long ${BINARY_NAME}_size() { 779 return _binary_${ENCODED_FILENAME}_end - _binary_${ENCODED_FILENAME}_start; 780 } 781 inline char* ${BINARY_NAME}_start_nc() { 782 return (char*)(_binary_${ENCODED_FILENAME}_start); 783 } 784 inline char* ${BINARY_NAME}_end_nc() { 785 return (char*)(_binary_${ENCODED_FILENAME}_end); 786 } 787 """ 788 789 # We duplicate the symbols with _ and __ preceding, the compiler fails if _ is not 790 # present and the linker fails if __ isn't. 791 _CC_DARWIN_ASM_CONTENTS = r""" 792 bits 64 793 794 section .rodata 795 796 global _binary_${ENCODED_FILENAME}_start 797 global __binary_${ENCODED_FILENAME}_start 798 global _binary_${ENCODED_FILENAME}_end 799 global __binary_${ENCODED_FILENAME}_end 800 global _binary_${ENCODED_FILENAME}_size 801 global __binary_${ENCODED_FILENAME}_size 802 803 _binary_${ENCODED_FILENAME}_start: incbin \"${SRCS}\" 804 __binary_${ENCODED_FILENAME}_start: incbin \"${SRCS}\" 805 _binary_${ENCODED_FILENAME}_end: 806 __binary_${ENCODED_FILENAME}_end: 807 _binary_${ENCODED_FILENAME}_size: dd \\$-_binary_${ENCODED_FILENAME}_start 808 __binary_${ENCODED_FILENAME}_size: dd \\$-_binary_${ENCODED_FILENAME}_start 809 """ 810 811 812 def _default_cflags(c, dbg): 813 """Returns the default cflags / cppflags for opt/dbg as appropriate.""" 814 if c: 815 return CONFIG.DEFAULT_DBG_CFLAGS if dbg else CONFIG.DEFAULT_OPT_CFLAGS 816 else: 817 return CONFIG.DEFAULT_DBG_CPPFLAGS if dbg else CONFIG.DEFAULT_OPT_CPPFLAGS 818 819 820 def _build_flags(compiler_flags:list, pkg_config_libs:list, pkg_config_cflags:list, defines=None, c=False, dbg=False): 821 """Builds flags that we'll pass to the compiler invocation.""" 822 compiler_flags = [_default_cflags(c, dbg), '-fPIC'] + compiler_flags # N.B. order is important! 823 if defines: 824 compiler_flags += ['-D' + define for define in defines] 825 826 pkg_config_cmd = ' '.join([f'`pkg-config --cflags {x}`' for x in pkg_config_cflags + pkg_config_libs]) 827 828 return ' '.join(compiler_flags) + ' ' + pkg_config_cmd 829 830 831 def _binary_build_flags(linker_flags:list, pkg_config_libs:list, shared=False, alwayslink='', c=False, dbg=False): 832 """Builds flags that we'll pass to the linker invocation.""" 833 pkg_config_cmd = ' '.join([f'`pkg-config --libs {x}`' for x in pkg_config_libs]) 834 835 objs = '`find . -name "*.o" -or -name "*.a" | sort`' 836 linker_prefix = '' if CONFIG.LINK_WITH_LD_TOOL else '-Wl,' 837 if (not shared) and alwayslink: 838 objs = f'{linker_prefix}{_WHOLE_ARCHIVE} {alwayslink} {linker_prefix}{_NO_WHOLE_ARCHIVE} {objs}' 839 if CONFIG.OS != 'darwin': 840 # We don't order libraries in a way that is especially useful for the linker, which is 841 # nicely solved by --start-group / --end-group. Unfortunately the OSX linker doesn't 842 # support those flags; in many cases it will work without, so try that. 843 # Ordering them would be ideal but we lack a convenient way of working that out from here. 844 objs = f'{linker_prefix}--start-group {objs} {linker_prefix}--end-group' 845 if CONFIG.OS == 'linux': 846 # This flag exists only in the GNU ld, where it improves determinism. OS detection is not ideal 847 # but there isn't much alternative. 848 linker_flags += ['--build-id=none'] 849 if shared: 850 objs = f'-shared {linker_prefix}{_WHOLE_ARCHIVE} {objs} {linker_prefix}{_NO_WHOLE_ARCHIVE}' 851 linker_flags = ' '.join([linker_prefix + f for f in linker_flags]) 852 if not CONFIG.LINK_WITH_LD_TOOL: 853 linker_flags += ' ' + _default_cflags(c, dbg) 854 return ' '.join([objs, linker_flags, pkg_config_cmd]) 855 856 857 def _library_cmds(c, compiler_flags, pkg_config_libs, pkg_config_cflags, extra_flags='', archive=True): 858 """Returns the commands needed for a cc_library rule.""" 859 dbg_flags = _build_flags(compiler_flags, pkg_config_libs, pkg_config_cflags, c=c, dbg=True) 860 opt_flags = _build_flags(compiler_flags, pkg_config_libs, pkg_config_cflags, c=c) 861 cmd_template = '$TOOLS_CC -c -I . ${SRCS_SRCS} %s %s' 862 if archive: 863 cmd_template += ' && $TOOLS_JARCAT ar -r && $TOOLS_AR s $OUT' 864 cmds = { 865 'dbg': cmd_template % (dbg_flags, extra_flags), 866 'opt': cmd_template % (opt_flags, extra_flags), 867 } 868 if CONFIG.CPP_COVERAGE: 869 cmds['cover'] = cmd_template % (dbg_flags + _COVERAGE_FLAGS, extra_flags) 870 return cmds, { 871 'cc': [CONFIG.CC_TOOL if c else CONFIG.CPP_TOOL], 872 'jarcat': [CONFIG.JARCAT_TOOL if archive else None], 873 'ar': [CONFIG.AR_TOOL if archive else None], 874 } 875 876 877 def _binary_cmds(c, linker_flags, pkg_config_libs, extra_flags='', shared=False, alwayslink=''): 878 """Returns the commands needed for a cc_binary, cc_test or cc_shared_object rule.""" 879 dbg_flags = _binary_build_flags(linker_flags, pkg_config_libs, shared, alwayslink, c=c, dbg=True) 880 opt_flags = _binary_build_flags(linker_flags, pkg_config_libs, shared, alwayslink, c=c, dbg=False) 881 cmds = { 882 'dbg': f'$TOOL -o $OUT {dbg_flags} {extra_flags}', 883 'opt': f'$TOOL -o $OUT {opt_flags} {extra_flags}', 884 } 885 if CONFIG.CPP_COVERAGE: 886 cmds['cover'] = f'$TOOL -o $OUT {dbg_flags} {extra_flags} {_COVERAGE_FLAGS} -lgcov' 887 return cmds, [CONFIG.LD_TOOL if CONFIG.LINK_WITH_LD_TOOL else 888 CONFIG.CC_TOOL if c else CONFIG.CPP_TOOL] 889 890 891 def _library_transitive_labels(c, compiler_flags, pkg_config_libs, pkg_config_cflags, archive=True): 892 """Applies commands from transitive labels to a cc_library rule.""" 893 def apply_transitive_labels(name): 894 labels = get_labels(name, 'cc:') 895 flags = ['-isystem %s' % l[4:] for l in labels if l.startswith('inc:')] 896 flags.extend(['-D' + l[4:] for l in labels if l.startswith('def:')]) 897 898 pkg_config_libs.extend([l[3:] for l in labels if l.startswith('pc:') and l[3:] not in pkg_config_libs]) 899 pkg_config_cflags.extend([l[4:] for l in labels if l.startswith('pcc:') and l[4:] not in pkg_config_cflags]) 900 mods = ['-fmodule-file=' + l[4:] for l in labels if l.startswith('mod:')] 901 flags += mods 902 if mods: 903 flags += ['-fmodules-ts' if CONFIG.CC_MODULES_CLANG else '-fmodules'] 904 if flags: # Don't update if there aren't any relevant labels 905 cmds, _ = _library_cmds(c, compiler_flags, pkg_config_libs, pkg_config_cflags, ' '.join(flags), archive=archive) 906 for k, v in cmds.items(): 907 set_command(name, k, v) 908 return apply_transitive_labels 909 910 911 def _binary_transitive_labels(c, linker_flags, pkg_config_libs, shared=False): 912 """Applies commands from transitive labels to a cc_binary, cc_test or cc_shared_object rule.""" 913 def apply_transitive_labels(name): 914 labels = get_labels(name, 'cc:') 915 linker_prefix = '' if CONFIG.LINK_WITH_LD_TOOL else '-Wl,' 916 flags = [linker_prefix + l[3:] for l in labels if l.startswith('ld:')] 917 918 flags.extend(['`pkg-config --libs %s`' % l[3:] for l in labels if l.startswith('pc:')]) 919 920 # ./ here because some weak linkers don't realise ./lib.a is the same file as lib.a 921 # and report duplicate symbol errors as a result. 922 alwayslink = ' '.join(['./' + l[3:] for l in labels if l.startswith('al:')]) 923 # Probably a little optimistic to check this (most binaries are likely to have *some* 924 # kind of linker flags to apply), but we might as well. 925 if flags or alwayslink: 926 cmds, _ = _binary_cmds(c, linker_flags, pkg_config_libs, ' '.join(flags), shared, alwayslink) 927 for k, v in cmds.items(): 928 set_command(name, k, v) 929 return apply_transitive_labels 930 931 932 if CONFIG.BAZEL_COMPATIBILITY: 933 # For nominal Buck compatibility. The cc_ forms are preferred. 934 cxx_binary = cc_binary 935 cxx_library = cc_library 936 cxx_test = cc_test