gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/deps.bzl (about)

     1  """Rules for dependency checking."""
     2  
     3  # DepsInfo provides a list of dependencies found when building a target.
     4  DepsInfo = provider(
     5      "lists dependencies encountered while building",
     6      fields = {
     7          "nodes": "a dict from targets to a list of their dependencies",
     8      },
     9  )
    10  
    11  def _deps_check_impl(target, ctx):
    12      # Check the target's dependencies and add any of our own deps.
    13      deps = []
    14      for dep in ctx.rule.attr.deps:
    15          deps.append(dep)
    16      nodes = {}
    17      if len(deps) != 0:
    18          nodes[target] = deps
    19  
    20      # Keep and propagate each dep's providers.
    21      for dep in ctx.rule.attr.deps:
    22          nodes.update(dep[DepsInfo].nodes)
    23  
    24      return [DepsInfo(nodes = nodes)]
    25  
    26  _deps_check = aspect(
    27      implementation = _deps_check_impl,
    28      attr_aspects = ["deps"],
    29  )
    30  
    31  def _is_allowed(target, allowlist, prefixes):
    32      # Check for allowed prefixes.
    33      for prefix in prefixes:
    34          workspace, pfx = prefix.split("//", 1)
    35          if len(workspace) > 0 and workspace[0] == "@":
    36              workspace = workspace[1:]
    37          if target.workspace_name == workspace and target.package.startswith(pfx):
    38              return True
    39  
    40      # Check the allowlist.
    41      for allowed in allowlist:
    42          if target == allowed.label:
    43              return True
    44  
    45      return False
    46  
    47  def _deps_test_impl(ctx):
    48      nodes = {}
    49      for target in ctx.attr.targets:
    50          for (node_target, node_deps) in target[DepsInfo].nodes.items():
    51              # Ignore any disallowed targets. This generates more useful error
    52              # messages. Consider the case where A dependes on B and B depends
    53              # on C, and both B and C are disallowed. Avoid emitting an error
    54              # that B depends on C, when the real issue is that A depends on B.
    55              if not _is_allowed(node_target.label, ctx.attr.allowed, ctx.attr.allowed_prefixes) and node_target.label != target.label:
    56                  continue
    57              bad_deps = []
    58              for dep in node_deps:
    59                  if not _is_allowed(dep.label, ctx.attr.allowed, ctx.attr.allowed_prefixes):
    60                      bad_deps.append(dep)
    61              if len(bad_deps) > 0:
    62                  nodes[node_target] = bad_deps
    63  
    64      # If there aren't any violations, write a passing test.
    65      if len(nodes) == 0:
    66          ctx.actions.write(
    67              output = ctx.outputs.executable,
    68              content = "#!/bin/bash\n\nexit 0\n",
    69          )
    70          return []
    71  
    72      # If we're here, we've found at least one violation.
    73      script_lines = [
    74          "#!/bin/bash",
    75          "echo Invalid dependencies found. If you\\'re sure you want to add dependencies,",
    76          "echo modify this target.",
    77          "echo",
    78      ]
    79  
    80      # List the violations.
    81      for target, deps in nodes.items():
    82          script_lines.append(
    83              'echo "{target} depends on:"'.format(target = target.label),
    84          )
    85          for dep in deps:
    86              script_lines.append('echo "\t{dep}"'.format(dep = dep.label))
    87  
    88      # The test must fail.
    89      script_lines.append("exit 1\n")
    90  
    91      ctx.actions.write(
    92          output = ctx.outputs.executable,
    93          content = "\n".join(script_lines),
    94      )
    95      return []
    96  
    97  # Checks that targets only depend on an allowlist of other targets. Targets can
    98  # be specified directly, or prefixes can be used to allow entire packages or
    99  # directory trees.
   100  #
   101  # This recursively checks the "deps" attribute of each target, dependencies
   102  # expressed other ways are not checked. For example, protobuf targets pull in
   103  # protobuf code, but aren't analyzed by deps_test.
   104  deps_test = rule(
   105      implementation = _deps_test_impl,
   106      attrs = {
   107          "targets": attr.label_list(
   108              doc = "The targets to check the transitive dependencies of.",
   109              aspects = [_deps_check],
   110          ),
   111          "allowed": attr.label_list(
   112              doc = "The allowed dependency targets.",
   113          ),
   114          "allowed_prefixes": attr.string_list(
   115              doc = "Any packages beginning with these prefixes are allowed.",
   116          ),
   117      },
   118      test = True,
   119  )