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 )