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