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