kythe.io@v0.0.68-0.20240422202219-7225dbc01741/tools/build_rules/verifier_test/cc_indexer_test.bzl (about)

     1  """Rules for testing the c++ indexer"""
     2  
     3  #
     4  # Copyright 2017 The Kythe Authors. All rights reserved.
     5  #
     6  # Licensed under the Apache License, Version 2.0 (the "License");
     7  # you may not use this file except in compliance with the License.
     8  # You may obtain a copy of the License at
     9  #
    10  #   http://www.apache.org/licenses/LICENSE-2.0
    11  #
    12  # Unless required by applicable law or agreed to in writing, software
    13  # distributed under the License is distributed on an "AS IS" BASIS,
    14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  # See the License for the specific language governing permissions and
    16  # limitations under the License.
    17  #
    18  
    19  load("@rules_proto//proto:defs.bzl", "ProtoInfo")
    20  load(
    21      "@bazel_tools//tools/build_defs/cc:action_names.bzl",
    22      "CPP_COMPILE_ACTION_NAME",
    23  )
    24  load("//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
    25  load("@bazel_skylib//lib:paths.bzl", "paths")
    26  load("@bazel_skylib//lib:shell.bzl", "shell")
    27  load(
    28      ":verifier_test.bzl",
    29      "KytheEntryProducerInfo",
    30      "KytheVerifierSources",
    31      "extract",
    32      "verifier_test",
    33  )
    34  load(
    35      "@io_kythe//kythe/cxx/extractor:toolchain.bzl",
    36      "CXX_EXTRACTOR_TOOLCHAINS",
    37      "CxxExtractorToolchainInfo",
    38      "find_extractor_toolchains",
    39  )
    40  
    41  UNSUPPORTED_FEATURES = [
    42      "thin_lto",
    43      "module_maps",
    44      "use_header_modules",
    45      "fdo_instrument",
    46      "fdo_optimize",
    47  ]
    48  
    49  CxxCompilationUnits = provider(
    50      doc = "A bundle of pre-extracted Kythe CompilationUnits for C++.",
    51      fields = {
    52          "files": "Depset of .kzip files.",
    53      },
    54  )
    55  
    56  _VERIFIER_FLAGS = {
    57      "check_for_singletons": False,
    58      "convert_marked_source": False,
    59      "goal_prefix": "//-",
    60      "ignore_dups": False,
    61      "ignore_code_conflicts": False,
    62      "use_fast_solver": False,
    63      "show_protos": False,
    64      "show_goals": False,
    65      "show_anchors": False,
    66  }
    67  
    68  _INDEXER_FLAGS = {
    69      "experimental_alias_template_instantiations": False,
    70      "experimental_drop_cpp_fwd_decl_docs": False,
    71      "experimental_drop_instantiation_independent_data": False,
    72      "experimental_drop_objc_fwd_class_docs": False,
    73      "experimental_usr_byte_size": 0,
    74      "emit_anchor_scopes": False,
    75      "emit_usr_corpus": False,
    76      "template_instance_exclude_path_pattern": "",
    77      "fail_on_unimplemented_builtin": True,
    78      "ignore_unimplemented": False,
    79      "index_template_instantiations": True,
    80      "ibuild_config": "",
    81      "use_compilation_corpus_as_default": False,
    82      "record_call_directness": False,
    83      "experimental_analysis_exclude_path_pattern": "",
    84      "experimental_record_variable_init_types": False,
    85      "experimental_record_c_symbols": False,
    86  }
    87  
    88  def _compiler_options(ctx, extractor_toolchain, copts, cc_info):
    89      """Returns the list of compiler flags from the C++ toolchain."""
    90      cc_toolchain = extractor_toolchain.cc_toolchain
    91      feature_configuration = cc_common.configure_features(
    92          ctx = ctx,
    93          cc_toolchain = cc_toolchain,
    94          requested_features = ctx.features,
    95          unsupported_features = ctx.disabled_features + UNSUPPORTED_FEATURES,
    96      )
    97  
    98      variables = cc_common.create_compile_variables(
    99          feature_configuration = feature_configuration,
   100          cc_toolchain = cc_toolchain,
   101          user_compile_flags = copts,
   102          include_directories = cc_info.compilation_context.includes,
   103          quote_include_directories = cc_info.compilation_context.quote_includes,
   104          system_include_directories = cc_info.compilation_context.system_includes,
   105      )
   106  
   107      # TODO(schroederc): use memory-efficient Args, when available
   108      args = ctx.actions.args()
   109      args.add("--with_executable", extractor_toolchain.compiler_executable)
   110      args.add_all(cc_common.get_memory_inefficient_command_line(
   111          feature_configuration = feature_configuration,
   112          action_name = CPP_COMPILE_ACTION_NAME,
   113          variables = variables,
   114      ))
   115  
   116      env = cc_common.get_environment_variables(
   117          feature_configuration = feature_configuration,
   118          action_name = CPP_COMPILE_ACTION_NAME,
   119          variables = variables,
   120      )
   121      return args, env
   122  
   123  def _compile_and_link(ctx, cc_info_providers, sources, headers):
   124      cc_toolchain = find_cpp_toolchain(ctx)
   125      feature_configuration = cc_common.configure_features(
   126          ctx = ctx,
   127          cc_toolchain = cc_toolchain,
   128          requested_features = ctx.features,
   129          unsupported_features = ctx.disabled_features + UNSUPPORTED_FEATURES,
   130      )
   131      compile_ctx, compile_outs = cc_common.compile(
   132          name = ctx.label.name,
   133          actions = ctx.actions,
   134          cc_toolchain = cc_toolchain,
   135          srcs = sources,
   136          public_hdrs = headers,
   137          feature_configuration = feature_configuration,
   138          compilation_contexts = [provider.compilation_context for provider in cc_info_providers],
   139      )
   140      linking_ctx, linking_out = cc_common.create_linking_context_from_compilation_outputs(
   141          name = ctx.label.name + "_transitive_library",
   142          actions = ctx.actions,
   143          feature_configuration = feature_configuration,
   144          cc_toolchain = cc_toolchain,
   145          compilation_outputs = compile_outs,
   146          linking_contexts = [provider.linking_context for provider in cc_info_providers],
   147      )
   148      return struct(
   149          compilation_context = compile_ctx,
   150          linking_context = linking_ctx,
   151          transitive_library_file = linking_out,
   152      )
   153  
   154  def _flag(name, typename, value):
   155      if value == None:  # Omit None flags.
   156          return None
   157  
   158      if type(value) != typename:
   159          fail("Invalid value for %s: %s; expected %s, found %s" % (
   160              name,
   161              value,
   162              typename,
   163              type(value),
   164          ))
   165      if typename == "bool":
   166          value = str(value).lower()
   167      return "--%s=%s" % (name, value)
   168  
   169  def _flags(values, defaults):
   170      return [
   171          flag
   172          for flag in [
   173              _flag(name, type(default), values.pop(name, default))
   174              for name, default in defaults.items()
   175          ]
   176          if flag != None
   177      ]
   178  
   179  def _split_flags(kwargs):
   180      flags = struct(
   181          indexer = _flags(kwargs, _INDEXER_FLAGS),
   182          verifier = _flags(kwargs, _VERIFIER_FLAGS),
   183      )
   184      if kwargs:
   185          fail("Unrecognized verifier flags: %s" % (kwargs.keys(),))
   186      return flags
   187  
   188  def _fix_path_for_generated_file(path):
   189      virtual_imports = "/_virtual_imports/"
   190      if virtual_imports in path:
   191          return path.split(virtual_imports)[1].split("/", 1)[1]
   192      else:
   193          return path
   194  
   195  def _generate_files(ctx, files, extensions):
   196      return [
   197          ctx.actions.declare_file(
   198              paths.replace_extension(
   199                  paths.relativize(_fix_path_for_generated_file(f.short_path), ctx.label.package),
   200                  extension,
   201              ),
   202          )
   203          for f in files
   204          for extension in extensions
   205      ]
   206  
   207  def _format_path_and_short_path(f):
   208      return "-I{0}={1}".format(_fix_path_for_generated_file(f.short_path), f.path)
   209  
   210  def _get_short_path(f):
   211      return _fix_path_for_generated_file(f.short_path)
   212  
   213  _KytheProtoInfo = provider()
   214  
   215  def _cc_kythe_proto_library_aspect_impl(target, ctx):
   216      sources = _generate_files(ctx, target[ProtoInfo].direct_sources, [".pb.cc"])
   217      if ctx.attr.enable_proto_static_reflection:
   218          headers = _generate_files(ctx, target[ProtoInfo].direct_sources, [".pb.h", ".proto.h", ".proto.static_reflection.h"])
   219      else:
   220          headers = _generate_files(ctx, target[ProtoInfo].direct_sources, [".pb.h", ".proto.h"])
   221      args = ctx.actions.args()
   222      args.add("--plugin=protoc-gen-PLUGIN=" + ctx.executable._plugin.path)
   223      if ctx.attr.enable_proto_static_reflection:
   224          args.add("--PLUGIN_out=proto_h,proto_static_reflection_h:" + ctx.bin_dir.path + "/")
   225      else:
   226          args.add("--PLUGIN_out=proto_h:" + ctx.bin_dir.path + "/")
   227      args.add_all(target[ProtoInfo].transitive_sources, map_each = _format_path_and_short_path)
   228      args.add_all(target[ProtoInfo].direct_sources, map_each = _get_short_path)
   229      ctx.actions.run(
   230          arguments = [args],
   231          outputs = sources + headers,
   232          inputs = target[ProtoInfo].transitive_sources,
   233          executable = ctx.executable._protoc,
   234          tools = [ctx.executable._plugin],
   235          mnemonic = "GenerateKytheCCProto",
   236      )
   237      cc_info_providers = [lib[CcInfo] for lib in [target, ctx.attr._runtime] if CcInfo in lib]
   238      cc_context = _compile_and_link(ctx, cc_info_providers, headers = headers, sources = sources)
   239      return [
   240          _KytheProtoInfo(files = depset(sources + headers)),
   241          CcInfo(
   242              compilation_context = cc_context.compilation_context,
   243              linking_context = cc_context.linking_context,
   244          ),
   245      ]
   246  
   247  _cc_kythe_proto_library_aspect = aspect(
   248      attr_aspects = ["deps"],
   249      attrs = {
   250          "_protoc": attr.label(
   251              default = Label("@com_google_protobuf//:protoc"),
   252              executable = True,
   253              cfg = "exec",
   254          ),
   255          "_plugin": attr.label(
   256              default = Label("//kythe/cxx/tools:proto_metadata_plugin"),
   257              executable = True,
   258              cfg = "exec",
   259          ),
   260          "_runtime": attr.label(
   261              default = Label("@com_google_protobuf//:protobuf"),
   262              cfg = "target",
   263          ),
   264          # Do not add references, temporary attribute for find_cpp_toolchain.
   265          "_cc_toolchain": attr.label(
   266              default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
   267          ),
   268          "enable_proto_static_reflection": attr.bool(
   269              default = False,
   270              doc = "Emit and capture generated code for proto static reflection",
   271          ),
   272      },
   273      fragments = ["cpp"],
   274      toolchains = use_cpp_toolchain(),
   275      implementation = _cc_kythe_proto_library_aspect_impl,
   276  )
   277  
   278  def _cc_kythe_proto_library(ctx):
   279      files = [dep[_KytheProtoInfo].files for dep in ctx.attr.deps]
   280      return [
   281          ctx.attr.deps[0][CcInfo],
   282          DefaultInfo(files = depset([], transitive = files)),
   283      ]
   284  
   285  cc_kythe_proto_library = rule(
   286      attrs = {
   287          "deps": attr.label_list(
   288              providers = [ProtoInfo],
   289              aspects = [_cc_kythe_proto_library_aspect],
   290          ),
   291          "enable_proto_static_reflection": attr.bool(
   292              default = False,
   293              doc = "Emit and capture generated code for proto static reflection",
   294          ),
   295      },
   296      implementation = _cc_kythe_proto_library,
   297  )
   298  
   299  def _cc_write_kzip_impl(ctx):
   300      extractor_toolchain, runtimes_deps = find_extractor_toolchains(ctx)
   301      cpp = extractor_toolchain.cc_toolchain
   302      cc_info = cc_common.merge_cc_infos(cc_infos = [
   303          src[CcInfo]
   304          for src in ctx.attr.srcs + ctx.attr.deps + runtimes_deps
   305          if CcInfo in src
   306      ])
   307      opts, cc_env = _compiler_options(ctx, extractor_toolchain, ctx.attr.opts, cc_info)
   308      if ctx.attr.corpus:
   309          env = {"KYTHE_CORPUS": ctx.attr.corpus}
   310          env.update(cc_env)
   311      else:
   312          env = cc_env
   313  
   314      outputs = depset([
   315          extract(
   316              srcs = depset([src]),
   317              ctx = ctx,
   318              env = env,
   319              extractor = extractor_toolchain.extractor_binary,
   320              kzip = ctx.actions.declare_file("{}/{}.kzip".format(ctx.label.name, src.basename)),
   321              opts = opts,
   322              vnames_config = ctx.file.vnames_config,
   323              deps = depset(
   324                  direct = ctx.files.srcs,
   325                  transitive = [
   326                      depset(ctx.files.deps),
   327                      cc_info.compilation_context.headers,
   328                      cpp.all_files,
   329                  ],
   330              ),
   331          )
   332          for src in ctx.files.srcs
   333          if not src.path.endswith(".h")  # Don't extract headers.
   334      ])
   335      outputs = depset(transitive = [outputs] + [
   336          dep[CxxCompilationUnits].files
   337          for dep in ctx.attr.deps
   338          if CxxCompilationUnits in dep
   339      ])
   340      return [
   341          DefaultInfo(files = outputs),
   342          CxxCompilationUnits(files = outputs),
   343          KytheVerifierSources(files = depset(ctx.files.srcs)),
   344      ]
   345  
   346  cc_write_kzip = rule(
   347      attrs = {
   348          "srcs": attr.label_list(
   349              doc = "A list of C++ source files to extract.",
   350              mandatory = True,
   351              allow_empty = False,
   352              allow_files = [
   353                  ".cc",
   354                  ".c",
   355                  ".h",
   356              ],
   357          ),
   358          "copts": attr.string_list(
   359              doc = """Options which are required to compile/index the sources.
   360  
   361              These will be included in the resulting .kzip CompilationUnits.
   362              """,
   363          ),
   364          "opts": attr.string_list(
   365              doc = "Options which will be passed to the extractor as arguments.",
   366          ),
   367          "corpus": attr.string(
   368              doc = "The compilation unit corpus to use.",
   369              default = "",
   370          ),
   371          "vnames_config": attr.label(
   372              doc = "vnames_config file to be used by the extractor.",
   373              default = Label("//external:vnames_config"),
   374              allow_single_file = [".json"],
   375          ),
   376          "deps": attr.label_list(
   377              doc = """Files which are required by the extracted sources.
   378  
   379              Additionally, targets providing CxxCompilationUnits
   380              may be used for dependencies which are required for an eventual
   381              Kythe index, but should not be extracted here.
   382              """,
   383              allow_files = True,
   384              providers = [
   385                  [CcInfo],
   386                  [CxxCompilationUnits],
   387              ],
   388          ),
   389          "_cxx_extractor_toolchain": attr.label(
   390              doc = "Fallback cxx_extractor_toolchain to use.",
   391              default = Label("@io_kythe//kythe/cxx/extractor:cxx_extractor_default"),
   392              providers = [CxxExtractorToolchainInfo],
   393          ),
   394      },
   395      doc = """cc_write_kzip extracts srcs into CompilationUnits.
   396  
   397      Each file in srcs will be extracted into a separate .kzip file, based on the name
   398      of the source.
   399      """,
   400      fragments = ["cpp"],
   401      toolchains = CXX_EXTRACTOR_TOOLCHAINS,
   402      implementation = _cc_write_kzip_impl,
   403  )
   404  
   405  def _extract_bundle_impl(ctx):
   406      bundle = ctx.actions.declare_directory(ctx.label.name + "_unbundled")
   407      ctx.actions.run(
   408          inputs = [ctx.file.src],
   409          tools = [ctx.executable.unbundle],
   410          outputs = [bundle],
   411          mnemonic = "Unbundle",
   412          executable = ctx.executable.unbundle,
   413          arguments = [ctx.file.src.path, bundle.path],
   414      )
   415      ctx.actions.run_shell(
   416          inputs = [
   417              ctx.file.vnames_config,
   418              bundle,
   419          ],
   420          tools = [ctx.executable.extractor],
   421          outputs = [ctx.outputs.kzip],
   422          mnemonic = "ExtractBundle",
   423          env = {
   424              "KYTHE_OUTPUT_FILE": ctx.outputs.kzip.path,
   425              "KYTHE_ROOT_DIRECTORY": ".",
   426              "KYTHE_VNAMES": ctx.file.vnames_config.path,
   427          },
   428          arguments = [
   429              ctx.executable.extractor.path,
   430              bundle.path,
   431          ] + ctx.attr.opts,
   432          command = "\"$1\" -c \"${@:2}\" $(cat \"${2}/cflags\") \"${2}/test_bundle/test.cc\"",
   433      )
   434  
   435      # TODO(shahms): Allow directly specifying the unbundled sources as verifier sources,
   436      #   rather than relying on --use_file_nodes.
   437      #   Possibly, just use the bundled source directly as the verifier doesn't actually
   438      #   care about the expanded source.
   439      #   Bazel makes it hard to use a glob here.
   440      return [CxxCompilationUnits(files = depset([ctx.outputs.kzip]))]
   441  
   442  cc_extract_bundle = rule(
   443      attrs = {
   444          "src": attr.label(
   445              doc = "Label of the bundled test to extract.",
   446              mandatory = True,
   447              allow_single_file = True,
   448          ),
   449          "extractor": attr.label(
   450              default = Label("//kythe/cxx/extractor:cxx_extractor"),
   451              executable = True,
   452              cfg = "exec",
   453          ),
   454          "opts": attr.string_list(
   455              doc = "Additional arguments to pass to the extractor.",
   456          ),
   457          "unbundle": attr.label(
   458              default = Label("//tools/build_rules/verifier_test:unbundle"),
   459              executable = True,
   460              cfg = "exec",
   461          ),
   462          "vnames_config": attr.label(
   463              default = Label("//kythe/cxx/indexer/cxx/testdata:test_vnames.json"),
   464              allow_single_file = True,
   465          ),
   466      },
   467      doc = "Extracts a bundled C++ indexer test into a .kzip file.",
   468      outputs = {"kzip": "%{name}.kzip"},
   469      implementation = _extract_bundle_impl,
   470  )
   471  
   472  def _bazel_extract_kzip_impl(ctx):
   473      # TODO(shahms): This is a hack as we get both executable
   474      #   and .sh from files.scripts but only want the "executable" one.
   475      #   Unlike `attr.label`, `attr.label_list` lacks an `executable` argument.
   476      #   Excluding "is_source" files may be overly aggressive, but effective.
   477      scripts = [s for s in ctx.files.scripts if not s.is_source]
   478      ctx.actions.run(
   479          inputs = [
   480              ctx.file.vnames_config,
   481              ctx.file.data,
   482          ] + scripts + ctx.files.srcs,
   483          tools = [ctx.executable.extractor],
   484          outputs = [ctx.outputs.kzip],
   485          mnemonic = "BazelExtractKZip",
   486          executable = ctx.executable.extractor,
   487          arguments = [
   488              ctx.file.data.path,
   489              ctx.outputs.kzip.path,
   490              ctx.file.vnames_config.path,
   491          ] + [script.path for script in scripts],
   492      )
   493      return [
   494          KytheVerifierSources(files = depset(ctx.files.srcs)),
   495          CxxCompilationUnits(files = depset([ctx.outputs.kzip])),
   496      ]
   497  
   498  # TODO(shahms): Clean up the bazel extraction rules.
   499  _bazel_extract_kzip = rule(
   500      attrs = {
   501          "srcs": attr.label_list(
   502              doc = "Source files to provide via KytheVerifierSources.",
   503              allow_files = True,
   504          ),
   505          "data": attr.label(
   506              doc = "The .xa extra action to extract.",
   507              # TODO(shahms): This should be the "src" which is extracted.
   508              mandatory = True,
   509              allow_single_file = [".xa"],
   510          ),
   511          "extractor": attr.label(
   512              default = Label("//kythe/cxx/extractor:cxx_extractor_bazel"),
   513              executable = True,
   514              cfg = "exec",
   515          ),
   516          "scripts": attr.label_list(
   517              cfg = "exec",
   518              allow_files = True,
   519          ),
   520          "vnames_config": attr.label(
   521              default = Label("//external:vnames_config"),
   522              allow_single_file = True,
   523          ),
   524      },
   525      doc = "Extracts a Bazel extra action binary proto file into a .kzip.",
   526      outputs = {"kzip": "%{name}.kzip"},
   527      implementation = _bazel_extract_kzip_impl,
   528  )
   529  
   530  def _expand_as_rootpath(ctx, option):
   531      # Replace $(location X) and $(execpath X) with $(rootpath X).
   532      return ctx.expand_location(
   533          option.replace("$(location ", "$(rootpath ").replace("$(execpath ", "$(rootpath "),
   534      )
   535  
   536  def _cc_index_source(ctx, src, test_runners):
   537      entries = ctx.actions.declare_file(
   538          ctx.label.name + "/" + src.basename + ".entries",
   539      )
   540      ctx.actions.run(
   541          mnemonic = "CcIndexSource",
   542          outputs = [entries],
   543          inputs = ctx.files.srcs + ctx.files.deps,
   544          tools = [ctx.executable.indexer],
   545          executable = ctx.executable.indexer,
   546          arguments = [ctx.expand_location(o) for o in ctx.attr.opts] + [
   547              "-i",
   548              src.path,
   549              "-o",
   550              entries.path,
   551              "--",
   552              "-c",
   553          ] + [ctx.expand_location(o) for o in ctx.attr.copts],
   554      )
   555      test_runners.append(_make_test_runner(
   556          ctx,
   557          src,
   558          {},
   559          arguments = [_expand_as_rootpath(ctx, o) for o in ctx.attr.opts] + [
   560              "-i",
   561              src.short_path,
   562              "--",
   563              "-c",
   564          ] + [_expand_as_rootpath(ctx, o) for o in ctx.attr.copts],
   565      ))
   566      return entries
   567  
   568  def _cc_index_compilation(ctx, compilation, test_runners):
   569      if ctx.attr.copts:
   570          print("Ignoring compiler options:", ctx.attr.copts)
   571      entries = ctx.actions.declare_file(
   572          ctx.label.name + "/" + compilation.basename + ".entries",
   573      )
   574      ctx.actions.run(
   575          mnemonic = "CcIndexCompilation",
   576          outputs = [entries],
   577          inputs = [compilation] + ctx.files.deps,
   578          tools = [ctx.executable.indexer],
   579          executable = ctx.executable.indexer,
   580          arguments = [ctx.expand_location(o) for o in ctx.attr.opts] + [
   581              "-o",
   582              entries.path,
   583              compilation.path,
   584          ],
   585      )
   586      test_runners.append(_make_test_runner(
   587          ctx,
   588          compilation,
   589          {},
   590          arguments = [_expand_as_rootpath(ctx, o) for o in ctx.attr.opts] + [
   591              compilation.short_path,
   592          ],
   593      ))
   594      return entries
   595  
   596  def _cc_index_single_file(ctx, input, test_runners):
   597      if input.extension == "kzip":
   598          return _cc_index_compilation(ctx, input, test_runners)
   599      elif input.extension in ("c", "cc", "m"):
   600          return _cc_index_source(ctx, input, test_runners)
   601      fail("Cannot index input file: %s" % (input,))
   602  
   603  def _make_test_runner(ctx, source, env, arguments):
   604      output = ctx.actions.declare_file(ctx.label.name + source.basename.replace(".", "_") + "_test_runner")
   605      ctx.actions.expand_template(
   606          output = output,
   607          is_executable = True,
   608          template = ctx.file._test_template,
   609          substitutions = {
   610              "@INDEXER@": shell.quote(ctx.executable.test_indexer.short_path),
   611              "@ENV@": "\n".join([
   612                  shell.quote("{key}={value}".format(key = key, value = value))
   613                  for key, value in env.items()
   614              ]),
   615              "@ARGS@": "\n".join([
   616                  shell.quote(a)
   617                  for a in arguments
   618              ]),
   619          },
   620      )
   621      return output
   622  
   623  def _cc_index_impl(ctx):
   624      test_runners = []
   625      intermediates = [
   626          _cc_index_single_file(ctx, src, test_runners)
   627          for src in ctx.files.srcs
   628          if src.extension in ("m", "c", "cc", "kzip")
   629      ]
   630      additional_kzips = [
   631          kzip
   632          for dep in ctx.attr.deps
   633          if CxxCompilationUnits in dep
   634          for kzip in dep[CxxCompilationUnits].files
   635          if kzip not in ctx.files.deps
   636      ]
   637  
   638      intermediates += [
   639          _cc_index_compilation(ctx, kzip, test_runners)
   640          for kzip in additional_kzips
   641      ]
   642  
   643      ctx.actions.run_shell(
   644          outputs = [ctx.outputs.entries],
   645          inputs = intermediates,
   646          command = '("${@:1:${#@}-1}" || rm -f "${@:${#@}}") | gzip -c > "${@:${#@}}"',
   647          mnemonic = "CompressEntries",
   648          arguments = ["cat"] + [i.path for i in intermediates] + [ctx.outputs.entries.path],
   649      )
   650  
   651      sources = [depset([src for src in ctx.files.srcs if src.extension != "kzip"])]
   652      for dep in ctx.attr.srcs:
   653          if KytheVerifierSources in dep:
   654              sources.append(dep[KytheVerifierSources].files)
   655  
   656      return [
   657          KytheVerifierSources(files = depset(transitive = sources)),
   658          KytheEntryProducerInfo(
   659              executables = test_runners,
   660              runfiles = ctx.runfiles(
   661                  files = (test_runners +
   662                           ctx.files.deps +
   663                           ctx.files.srcs +
   664                           additional_kzips),
   665              ).merge(ctx.attr.test_indexer[DefaultInfo].default_runfiles),
   666          ),
   667      ]
   668  
   669  # TODO(shahms): Support cc_library deps, along with cc toolchain support.
   670  # TODO(shahms): Split objc_index into a separate rule.
   671  cc_index = rule(
   672      attrs = {
   673          # .cc/.h files, added to KytheVerifierSources provider, but not transitively.
   674          # CxxCompilationUnits, which may also include sources.
   675          "srcs": attr.label_list(
   676              doc = "C++/ObjC source files or extracted .kzip files to index.",
   677              allow_files = [
   678                  ".cc",
   679                  ".c",
   680                  ".h",
   681                  ".m",  # Objective-C is supported by the indexer as well.
   682                  ".kzip",
   683              ],
   684          ),
   685          "copts": attr.string_list(
   686              doc = "Options to pass to the compiler while indexing.",
   687          ),
   688          "opts": attr.string_list(
   689              doc = "Options to pass to the indexer.",
   690          ),
   691          "deps": attr.label_list(
   692              doc = "Files required to index srcs.",
   693              # .meta files, .h files
   694              allow_files = [
   695                  ".h",
   696                  ".meta",  # Cross language metadata files.
   697                  ".claim",  # Static claim files.
   698              ],
   699          ),
   700          "indexer": attr.label(
   701              default = Label("//kythe/cxx/indexer/cxx:indexer"),
   702              executable = True,
   703              cfg = "exec",
   704          ),
   705          "test_indexer": attr.label(
   706              default = Label("//kythe/cxx/indexer/cxx:indexer"),
   707              executable = True,
   708              cfg = "target",
   709          ),
   710          "_test_template": attr.label(
   711              default = Label("//tools/build_rules/verifier_test:indexer.sh.in"),
   712              allow_single_file = True,
   713          ),
   714      },
   715      doc = """Produces a Kythe index from the C++ source files.
   716  
   717      Files in `srcs` and `deps` will be indexed, files in `srcs` will
   718      additionally be included in the provided KytheVerifierSources.
   719      KytheEntries dependencies will be transitively included in the index.
   720      """,
   721      outputs = {
   722          "entries": "%{name}.entries.gz",
   723      },
   724      implementation = _cc_index_impl,
   725  )
   726  
   727  def _indexer_test(
   728          name,
   729          srcs,
   730          copts,
   731          deps = [],
   732          tags = [],
   733          size = "small",
   734          target_compatible_with = [],
   735          bundled = False,
   736          expect_fail_verify = False,
   737          indexer = None,
   738          **kwargs):
   739      flags = _split_flags(kwargs)
   740      goals = srcs
   741      if bundled:
   742          if len(srcs) != 1:
   743              fail("Bundled indexer tests require exactly one src!")
   744          cc_extract_bundle(
   745              name = name + "_kzip",
   746              testonly = True,
   747              src = srcs[0],
   748              opts = copts,
   749              target_compatible_with = target_compatible_with,
   750              tags = tags,
   751          )
   752  
   753          # Verifier sources come from file nodes.
   754          srcs = [":" + name + "_kzip"]
   755          goals = []
   756      indexer_args = {}
   757      if indexer != None:
   758          # Obnoxiously, we have to duplicate these attributes so that
   759          # they both have the proper configuration.
   760          indexer_args = {
   761              "indexer": indexer,
   762              "test_indexer": indexer,
   763          }
   764  
   765      cc_index(
   766          name = name + "_entries",
   767          testonly = True,
   768          srcs = srcs,
   769          copts = copts if not bundled else [],
   770          opts = (["-claim_unknown=false"] if bundled else []) + flags.indexer,
   771          target_compatible_with = target_compatible_with,
   772          tags = tags,
   773          deps = deps,
   774          **indexer_args
   775      )
   776      verifier_test(
   777          name = name,
   778          size = size,
   779          srcs = goals,
   780          deps = [":" + name + "_entries"],
   781          expect_success = not expect_fail_verify,
   782          opts = flags.verifier,
   783          target_compatible_with = target_compatible_with,
   784          tags = tags,
   785      )
   786  
   787  # If a test is expected to pass on OSX but not on linux, you can set
   788  # target_compatible_with=["@platforms//os:osx"]. This causes the test to be skipped on linux and it
   789  # causes the actual test to execute on OSX.
   790  def cc_indexer_test(
   791          name,
   792          srcs,
   793          deps = [],
   794          tags = [],
   795          size = "small",
   796          target_compatible_with = [],
   797          std = "c++11",
   798          bundled = False,
   799          expect_fail_verify = False,
   800          copts = [],
   801          indexer = None,
   802          **kwargs):
   803      """C++ indexer test rule.
   804  
   805      Args:
   806        name: The name of the test rule.
   807        srcs: Source files to index and run the verifier.
   808        deps: Sources, compilation units or entries which should be present
   809          in the index or are required to index the sources.
   810        std: The C++ standard to use for the test.
   811        bundled: True if this test is a "bundled" C++ test and must be extracted.
   812        expect_fail_verify: True if this test is expected to fail.
   813        convert_marked_source: Whether the verifier should convert marked source.
   814        ignore_dups: Whether the verifier should ignore duplicate nodes.
   815        check_for_singletons: Whether the verifier should check for singleton facts.
   816        goal_prefix: The comment prefix the verifier should use for goals.
   817        fail_on_unimplemented_builtin: Whether the indexer should fail on
   818          unimplemented builtins.
   819        ignore_unimplemented: Whether the indexer should continue after encountering
   820          an unimplemented construct.
   821        index_template_instantiations: Whether the indexer should index template
   822          instantiations.
   823        emit_usr_corpus: Whether the indexer should emit corpora for USRs.
   824        experimental_alias_template_instantiations: Whether the indexer should alias
   825          template instantiations.
   826        experimental_drop_instantiation_independent_data: Whether the indexer should
   827          drop extraneous instantiation independent data.
   828        experimental_usr_byte_size: How many bytes of a USR to use.
   829        ignore_code_conflicts: Ignore conflicting /kythe/code facts during verification.
   830        use_fast_solver: Use the fast solver.
   831      """
   832      _indexer_test(
   833          name = name,
   834          srcs = srcs,
   835          deps = deps,
   836          tags = tags,
   837          size = size,
   838          copts = ["-std=" + std] + copts,
   839          target_compatible_with = target_compatible_with,
   840          bundled = bundled,
   841          expect_fail_verify = expect_fail_verify,
   842          indexer = indexer,
   843          **kwargs
   844      )
   845  
   846  def objc_indexer_test(
   847          name,
   848          srcs,
   849          deps = [],
   850          tags = [],
   851          size = "small",
   852          target_compatible_with = [],
   853          bundled = False,
   854          expect_fail_verify = False,
   855          **kwargs):
   856      """Objective C indexer test rule.
   857  
   858      Args:
   859        name: The name of the test rule.
   860        srcs: Source files to index and run the verifier.
   861        deps: Sources, compilation units or entries which should be present
   862          in the index or are required to index the sources.
   863        bundled: True if this test is a "bundled" C++ test and must be extracted.
   864        expect_fail_verify: True if this test is expected to fail.
   865        convert_marked_source: Whether the verifier should convert marked source.
   866        ignore_dups: Whether the verifier should ignore duplicate nodes.
   867        check_for_singletons: Whether the verifier should check for singleton facts.
   868        goal_prefix: The comment prefix the verifier should use for goals.
   869        fail_on_unimplemented_builtin: Whether the indexer should fail on
   870          unimplemented builtins.
   871        ignore_unimplemented: Whether the indexer should continue after encountering
   872          an unimplemented construct.
   873        index_template_instantiations: Whether the indexer should index template
   874          instantiations.
   875        experimental_alias_template_instantiations: Whether the indexer should alias
   876          template instantiations.
   877        experimental_drop_instantiation_independent_data: Whether the indexer should
   878          drop extraneous instantiation independent data.
   879        experimental_usr_byte_size: How many bytes of a USR to use.
   880      """
   881      _indexer_test(
   882          name = name,
   883          srcs = srcs,
   884          deps = deps,
   885          tags = tags,
   886          size = size,
   887          # Newer ObjC features are only enabled on the "modern" runtime.
   888          copts = ["-fblocks", "-fobjc-runtime=macosx"],
   889          target_compatible_with = target_compatible_with,
   890          bundled = bundled,
   891          expect_fail_verify = expect_fail_verify,
   892          **kwargs
   893      )
   894  
   895  def objc_bazel_extractor_test(
   896          name,
   897          src,
   898          data,
   899          size = "small",
   900          tags = [],
   901          target_compatible_with = []):
   902      """Objective C Bazel extractor test.
   903  
   904      Args:
   905        src: The source file to use with the verifier.
   906        data: The extracted .xa protocol buffer to index.
   907      """
   908      _bazel_extract_kzip(
   909          name = name + "_kzip",
   910          testonly = True,
   911          srcs = [src],
   912          data = data,
   913          extractor = "//kythe/cxx/extractor:objc_extractor_bazel",
   914          target_compatible_with = target_compatible_with,
   915          scripts = [
   916              "//third_party/bazel:get_devdir",
   917              "//third_party/bazel:get_sdkroot",
   918          ],
   919          tags = tags,
   920      )
   921      cc_index(
   922          name = name + "_entries",
   923          testonly = True,
   924          srcs = [":" + name + "_kzip"],
   925          target_compatible_with = target_compatible_with,
   926          tags = tags,
   927      )
   928      return verifier_test(
   929          name = name,
   930          size = size,
   931          srcs = [src],
   932          deps = [":" + name + "_entries"],
   933          opts = ["--ignore_dups"],
   934          target_compatible_with = target_compatible_with,
   935          tags = tags,
   936      )
   937  
   938  def cc_bazel_extractor_test(name, src, data, size = "small", tags = []):
   939      """C++ Bazel extractor test.
   940  
   941      Args:
   942        src: The source file to use with the verifier.
   943        data: The extracted .xa protocol buffer to index.
   944      """
   945      _bazel_extract_kzip(
   946          name = name + "_kzip",
   947          testonly = True,
   948          srcs = [src],
   949          data = data,
   950          tags = tags,
   951      )
   952      cc_index(
   953          name = name + "_entries",
   954          testonly = True,
   955          srcs = [":" + name + "_kzip"],
   956          tags = tags,
   957      )
   958      return verifier_test(
   959          name = name,
   960          size = size,
   961          srcs = [src],
   962          deps = [":" + name + "_entries"],
   963          opts = ["--ignore_dups"],
   964          tags = tags,
   965      )
   966  
   967  def cc_extractor_test(
   968          name,
   969          srcs,
   970          deps = [],
   971          data = [],
   972          size = "small",
   973          std = "c++11",
   974          tags = [],
   975          target_compatible_with = []):
   976      """C++ verifier test on an extracted source file."""
   977      args = ["-std=" + std, "-c"]
   978      cc_write_kzip(
   979          name = name + "_kzip",
   980          testonly = True,
   981          srcs = srcs,
   982          opts = args,
   983          target_compatible_with = target_compatible_with,
   984          tags = tags,
   985          deps = data,
   986      )
   987      cc_index(
   988          name = name + "_entries",
   989          testonly = True,
   990          srcs = [":" + name + "_kzip"],
   991          opts = ["--ignore_unimplemented"],
   992          target_compatible_with = target_compatible_with,
   993          tags = tags,
   994          deps = data,
   995      )
   996      return verifier_test(
   997          name = name,
   998          size = size,
   999          srcs = srcs,
  1000          opts = ["--ignore_dups", "--ignore_code_conflicts"],
  1001          target_compatible_with = target_compatible_with,
  1002          tags = tags,
  1003          deps = deps + [":" + name + "_entries"],
  1004      )