kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/indexer/testdata/go_indexer_test.bzl (about)

     1  #
     2  # Copyright 2016 The Kythe Authors. All rights reserved.
     3  #
     4  # Licensed under the Apache License, Version 2.0 (the "License");
     5  # you may not use this file except in compliance with the License.
     6  # You may obtain a copy of the License at
     7  #
     8  #   http://www.apache.org/licenses/LICENSE-2.0
     9  #
    10  # Unless required by applicable law or agreed to in writing, software
    11  # distributed under the License is distributed on an "AS IS" BASIS,
    12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  # See the License for the specific language governing permissions and
    14  # limitations under the License.
    15  #
    16  
    17  # Bazel rules to extract Go compilations from library targets for testing the
    18  # Go cross-reference indexer.
    19  load("@bazel_skylib//lib:shell.bzl", "shell")
    20  load(
    21      "@io_bazel_rules_go//go:def.bzl",
    22      "GoSource",
    23      "go_library",
    24  )
    25  load(
    26      "//tools/build_rules/verifier_test:verifier_test.bzl",
    27      "KytheEntryProducerInfo",
    28      "kythe_integration_test",
    29      "verifier_test",
    30  )
    31  
    32  # Emit a shell script that sets up the environment needed by the extractor to
    33  # capture dependencies and runs the extractor.
    34  def _emit_extractor_script(ctx, mode, script, output, srcs, deps, ipath, data, extra_extractor_args):
    35      tmpdir = output.dirname + "/tmp"
    36      srcroot = tmpdir + "/src"
    37      srcdir = srcroot + "/" + ipath
    38      extras = []
    39      cmds = ["#!/bin/sh -e", "mkdir -p " + srcdir]
    40  
    41      # Link the source files and dependencies into a common temporary directory.
    42      # Source files need to be made relative to the temp directory.
    43      ups = srcdir.count("/") + 1
    44      cmds += [
    45          'ln -s "%s%s" "%s"' % ("../" * ups, src.path, srcdir)
    46          for src in srcs
    47      ]
    48      for dep in deps:
    49          gosrc = dep[GoSource]
    50          path = gosrc.library.importpath
    51          fullpath = "/".join([srcroot, path])
    52          tups = fullpath.count("/") + 1
    53          cmds += ["mkdir -p " + fullpath]
    54          for src in gosrc.srcs:
    55              cmds += ["ln -s '%s%s' '%s'" % ("../" * tups, src.path, fullpath + "/" + src.basename)]
    56  
    57      # Gather any extra data dependencies.
    58      for target in data:
    59          for f in target.files.to_list():
    60              cmds.append('ln -s "%s%s" "%s"' % ("../" * ups, f.path, srcdir))
    61              extras.append(srcdir + "/" + f.path.rsplit("/", 1)[-1])
    62  
    63      # Invoke the extractor on the temp directory.
    64      goroot = "/".join(ctx.files._sdk_files[0].path.split("/")[:-2])
    65      cmds.append("export GOCACHE=\"$PWD/" + tmpdir + "/cache\"")
    66      cmds.append("export CGO_ENABLED=0")
    67  
    68      args = [ctx.files._extractor[-1].path] + extra_extractor_args + [
    69          "-output",
    70          output.path,
    71          "-goos",
    72          mode.goos,
    73          "-goarch",
    74          mode.goarch,
    75          "-goroot",
    76          goroot,
    77          "-gocompiler",
    78          "gc",
    79          "-gopath",
    80          tmpdir,
    81          "-extra_files",
    82          "'%s'" % ",".join(extras),
    83          ipath,
    84      ]
    85      cmds.append(" ".join(args))
    86  
    87      f = ctx.actions.declare_file(script)
    88      ctx.actions.write(output = f, content = "\n".join(cmds), is_executable = True)
    89      return f
    90  
    91  def _go_extract(ctx):
    92      gosrc = ctx.attr.library[GoSource]
    93      mode = gosrc.mode
    94      srcs = gosrc.srcs
    95  
    96      # TODO: handle transitive dependencies
    97      deps = gosrc.deps
    98      depsrcs = []
    99      for dep in deps:
   100          depsrcs += dep[GoSource].srcs
   101  
   102      ipath = gosrc.library.importpath
   103      data = ctx.attr.data
   104      output = ctx.outputs.kzip
   105      script = _emit_extractor_script(
   106          ctx,
   107          mode,
   108          ctx.label.name + "-extract.sh",
   109          output,
   110          srcs,
   111          deps,
   112          ipath,
   113          data,
   114          ctx.attr.extra_extractor_args,
   115      )
   116  
   117      extras = []
   118      for target in data:
   119          extras += target.files.to_list()
   120  
   121      tools = ctx.files._extractor + ctx.files._sdk_files
   122      ctx.actions.run(
   123          mnemonic = "GoExtract",
   124          executable = script,
   125          outputs = [output],
   126          inputs = srcs + extras + depsrcs,
   127          tools = tools,
   128      )
   129      return struct(kzip = output)
   130  
   131  # Generate a kzip with the compilations captured from a single Go library or
   132  # binary rule.
   133  go_extract = rule(
   134      _go_extract,
   135      attrs = {
   136          # Additional data files to include in each compilation.
   137          "data": attr.label_list(
   138              allow_empty = True,
   139              allow_files = True,
   140          ),
   141          "library": attr.label(
   142              providers = [GoSource],
   143              mandatory = True,
   144          ),
   145          "_extractor": attr.label(
   146              default = Label("//kythe/go/extractors/cmd/gotool"),
   147              executable = True,
   148              cfg = "exec",
   149          ),
   150          "_sdk_files": attr.label(
   151              allow_files = True,
   152              default = "@go_sdk//:files",
   153          ),
   154          "extra_extractor_args": attr.string_list(),
   155      },
   156      outputs = {"kzip": "%{name}.kzip"},
   157      toolchains = ["@io_bazel_rules_go//go:toolchain"],
   158  )
   159  
   160  def _go_entries(ctx):
   161      kzip = ctx.attr.kzip.kzip
   162      iargs = []
   163      output = ctx.outputs.entries
   164  
   165      # If the test wants marked source, enable support for it in the indexer.
   166      if ctx.attr.has_marked_source:
   167          iargs.append("-code")
   168  
   169      if ctx.attr.emit_anchor_scopes:
   170          iargs.append("-anchor_scopes")
   171  
   172      if ctx.attr.use_compilation_corpus_for_all:
   173          iargs.append("-use_compilation_corpus_for_all")
   174  
   175      if ctx.attr.use_file_as_top_level_scope:
   176          iargs.append("-use_file_as_top_level_scope")
   177  
   178      if ctx.attr.override_stdlib_corpus:
   179          iargs.append("-override_stdlib_corpus=%s" % ctx.attr.override_stdlib_corpus)
   180  
   181      # If the test wants linkage metadata, enable support for it in the indexer.
   182      if ctx.attr.metadata_suffix:
   183          iargs += ["-meta", ctx.attr.metadata_suffix]
   184  
   185      test_runners = []
   186      eargs = [ctx.expand_location(arg.replace("$(location", "$(rootpath"), ctx.attr.data) for arg in ctx.attr.extra_indexer_args]
   187      test_runners.append(_make_test_runner(ctx, {}, arguments = iargs + eargs + [kzip.short_path]))
   188  
   189      iargs += [ctx.expand_location(arg, ctx.attr.data) for arg in ctx.attr.extra_indexer_args]
   190      iargs += [kzip.path, "| gzip >" + output.path]
   191      iargs.insert(0, ctx.executable._exec_indexer.path)
   192  
   193      cmds = ["set -e", "set -o pipefail", " ".join(iargs), ""]
   194      ctx.actions.run_shell(
   195          mnemonic = "GoIndexer",
   196          command = "\n".join(cmds),
   197          outputs = [output],
   198          inputs = [kzip] + ctx.files.data,
   199          tools = [ctx.executable._exec_indexer],
   200      )
   201  
   202      return [
   203          KytheEntryProducerInfo(
   204              executables = test_runners,
   205              runfiles = ctx.runfiles(
   206                  files = (test_runners + [kzip] + ctx.files.data),
   207              ).merge(ctx.attr._indexer[DefaultInfo].default_runfiles),
   208          ),
   209      ]
   210  
   211  # Run the Kythe indexer on the output that results from a go_extract rule.
   212  go_entries = rule(
   213      _go_entries,
   214      attrs = {
   215          # Whether to enable explosion of MarkedSource facts.
   216          "has_marked_source": attr.bool(default = False),
   217  
   218          # Whether to enable anchor scope edges.
   219          "emit_anchor_scopes": attr.bool(default = False),
   220  
   221          # The go_extract output to pass to the indexer.
   222          "kzip": attr.label(
   223              providers = ["kzip"],
   224              mandatory = True,
   225          ),
   226  
   227          # The suffix used to recognize linkage metadata files, if non-empty.
   228          "metadata_suffix": attr.string(default = ""),
   229          "use_compilation_corpus_for_all": attr.bool(default = False),
   230          "use_file_as_top_level_scope": attr.bool(default = False),
   231          "override_stdlib_corpus": attr.string(default = ""),
   232          "extra_indexer_args": attr.string_list(),
   233  
   234          # Extra files required by the indexer
   235          "data": attr.label_list(
   236              allow_empty = True,
   237              allow_files = True,
   238          ),
   239  
   240          # The location of the Go indexer binary.
   241          "_indexer": attr.label(
   242              default = Label("//kythe/go/indexer/cmd/go_indexer"),
   243              executable = True,
   244              cfg = "target",
   245          ),
   246          "_exec_indexer": attr.label(
   247              default = Label("//kythe/go/indexer/cmd/go_indexer"),
   248              executable = True,
   249              cfg = "exec",
   250          ),
   251          "_test_template": attr.label(
   252              default = Label("//tools/build_rules/verifier_test:indexer.sh.in"),
   253              allow_single_file = True,
   254          ),
   255      },
   256      outputs = {"entries": "%{name}.entries.gz"},
   257  )
   258  
   259  def _make_test_runner(ctx, env, arguments):
   260      output = ctx.actions.declare_file(ctx.label.name + "_test_runner")
   261      ctx.actions.expand_template(
   262          output = output,
   263          is_executable = True,
   264          template = ctx.file._test_template,
   265          substitutions = {
   266              "@INDEXER@": shell.quote(ctx.executable._indexer.short_path),
   267              "@ENV@": "\n".join([
   268                  shell.quote("{key}={value}".format(key = key, value = value))
   269                  for key, value in env.items()
   270              ]),
   271              "@ARGS@": "\n".join([
   272                  shell.quote(a)
   273                  for a in arguments
   274              ]),
   275          },
   276      )
   277      return output
   278  
   279  def go_verifier_test(
   280          name,
   281          entries,
   282          srcs = [],
   283          deps = [],
   284          size = "small",
   285          tags = [],
   286          log_entries = False,
   287          has_marked_source = False,
   288          resolve_code_facts = False,
   289          allow_duplicates = False,
   290          use_fast_solver = False):
   291      opts = ["--use_file_nodes", "--show_goals", "--check_for_singletons", "--goal_regex='\\s*//\\s*-(.*)'"]
   292      if log_entries:
   293          opts.append("--show_protos")
   294      if allow_duplicates or len(deps) > 0:
   295          opts.append("--ignore_dups")
   296      if len(srcs) > 0:
   297          opts.append("--nofile_vnames")
   298  
   299      # If the test wants marked source, enable support for it in the verifier.
   300      if has_marked_source:
   301          opts.append("--convert_marked_source")
   302      if use_fast_solver:
   303          opts.append("--use_fast_solver")
   304      return verifier_test(
   305          name = name,
   306          size = size,
   307          opts = opts,
   308          tags = tags,
   309          resolve_code_facts = resolve_code_facts,
   310          srcs = srcs,
   311          deps = [entries] + deps,
   312      )
   313  
   314  # Shared extract/index logic for the go_indexer_test/go_integration_test rules.
   315  def _go_indexer(
   316          name,
   317          srcs,
   318          deps = [],
   319          importpath = None,
   320          data = None,
   321          has_marked_source = False,
   322          emit_anchor_scopes = False,
   323          allow_duplicates = False,
   324          use_compilation_corpus_for_all = False,
   325          use_file_as_top_level_scope = False,
   326          override_stdlib_corpus = "",
   327          metadata_suffix = "",
   328          extra_indexer_args = [],
   329          extra_extractor_args = []):
   330      if importpath == None:
   331          importpath = native.package_name() + "/" + name
   332      lib = name + "_lib"
   333      go_library(
   334          name = lib,
   335          srcs = srcs,
   336          importpath = importpath,
   337          deps = [dep + "_lib" for dep in deps],
   338      )
   339      kzip = name + "_units"
   340      go_extract(
   341          name = kzip,
   342          data = data,
   343          library = lib,
   344          extra_extractor_args = extra_extractor_args,
   345      )
   346      entries = name + "_entries"
   347      go_entries(
   348          name = entries,
   349          data = data,
   350          has_marked_source = has_marked_source,
   351          emit_anchor_scopes = emit_anchor_scopes,
   352          use_compilation_corpus_for_all = use_compilation_corpus_for_all,
   353          use_file_as_top_level_scope = use_file_as_top_level_scope,
   354          override_stdlib_corpus = override_stdlib_corpus,
   355          extra_indexer_args = extra_indexer_args,
   356          kzip = ":" + kzip,
   357          metadata_suffix = metadata_suffix,
   358          tags = ["manual"],
   359      )
   360      return entries
   361  
   362  # A convenience macro to generate a test library, pass it to the Go indexer,
   363  # and feed the output of indexing to the Kythe schema verifier.
   364  def go_indexer_test(
   365          name,
   366          srcs,
   367          deps = [],
   368          import_path = None,
   369          size = None,
   370          tags = None,
   371          log_entries = False,
   372          data = None,
   373          has_marked_source = False,
   374          resolve_code_facts = False,
   375          emit_anchor_scopes = False,
   376          allow_duplicates = False,
   377          use_compilation_corpus_for_all = False,
   378          use_file_as_top_level_scope = False,
   379          override_stdlib_corpus = "",
   380          metadata_suffix = "",
   381          extra_goals = [],
   382          extra_indexer_args = [],
   383          extra_extractor_args = [],
   384          use_fast_solver = False):
   385      entries = _go_indexer(
   386          name = name,
   387          srcs = srcs,
   388          data = data,
   389          has_marked_source = has_marked_source,
   390          emit_anchor_scopes = emit_anchor_scopes,
   391          use_compilation_corpus_for_all = use_compilation_corpus_for_all,
   392          use_file_as_top_level_scope = use_file_as_top_level_scope,
   393          override_stdlib_corpus = override_stdlib_corpus,
   394          importpath = import_path,
   395          metadata_suffix = metadata_suffix,
   396          deps = deps,
   397          extra_indexer_args = extra_indexer_args,
   398          extra_extractor_args = extra_extractor_args,
   399      )
   400      go_verifier_test(
   401          name = name,
   402          srcs = extra_goals,
   403          size = size,
   404          allow_duplicates = allow_duplicates,
   405          entries = ":" + entries,
   406          deps = [dep + "_entries" for dep in deps],
   407          has_marked_source = has_marked_source,
   408          resolve_code_facts = resolve_code_facts,
   409          log_entries = log_entries,
   410          tags = tags,
   411          use_fast_solver = use_fast_solver,
   412      )
   413  
   414  # A convenience macro to generate a test library, pass it to the Go indexer,
   415  # and feed the output of indexing to the Kythe integration test pipeline.
   416  def go_integration_test(
   417          name,
   418          srcs,
   419          deps = [],
   420          data = None,
   421          file_tickets = [],
   422          import_path = None,
   423          size = "small",
   424          has_marked_source = False,
   425          metadata_suffix = ""):
   426      entries = _go_indexer(
   427          name = name,
   428          srcs = srcs,
   429          data = data,
   430          has_marked_source = has_marked_source,
   431          import_path = import_path,
   432          metadata_suffix = metadata_suffix,
   433          deps = deps,
   434      )
   435      kythe_integration_test(
   436          name = name,
   437          size = size,
   438          srcs = [":" + entries],
   439          file_tickets = file_tickets,
   440      )