Support restrictions based on a module's dependencies

Adds a neverallow InDirectDeps(deps) verb that will allow a neverallow
rule to restrict access to a specific dependency, irrespective of how
it is specified.

Bug: 137543088
Test: m nothing
Change-Id: I0c6bb702d55175e9b78b79e86e96924c5dd83efa
diff --git a/android/neverallow.go b/android/neverallow.go
index 23b6454..be396fc 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -189,6 +189,10 @@
 			continue
 		}
 
+		if !n.appliesToDirectDeps(ctx) {
+			continue
+		}
+
 		ctx.ModuleErrorf("violates " + n.String())
 	}
 }
@@ -246,6 +250,8 @@
 
 	NotIn(path ...string) Rule
 
+	InDirectDeps(deps ...string) Rule
+
 	ModuleType(types ...string) Rule
 
 	NotModuleType(types ...string) Rule
@@ -268,6 +274,8 @@
 	paths       []string
 	unlessPaths []string
 
+	directDeps map[string]bool
+
 	moduleTypes       []string
 	unlessModuleTypes []string
 
@@ -277,7 +285,7 @@
 
 // Create a new NeverAllow rule.
 func NeverAllow() Rule {
-	return &rule{}
+	return &rule{directDeps: make(map[string]bool)}
 }
 
 func (r *rule) In(path ...string) Rule {
@@ -290,6 +298,13 @@
 	return r
 }
 
+func (r *rule) InDirectDeps(deps ...string) Rule {
+	for _, d := range deps {
+		r.directDeps[d] = true
+	}
+	return r
+}
+
 func (r *rule) ModuleType(types ...string) Rule {
 	r.moduleTypes = append(r.moduleTypes, types...)
 	return r
@@ -356,6 +371,9 @@
 	for _, v := range r.unlessProps {
 		s += " -" + strings.Join(v.fields, ".") + v.matcher.String()
 	}
+	for k := range r.directDeps {
+		s += " deps:" + k
+	}
 	if len(r.reason) != 0 {
 		s += " which is restricted because " + r.reason
 	}
@@ -368,6 +386,22 @@
 	return includePath && !excludePath
 }
 
+func (r *rule) appliesToDirectDeps(ctx BottomUpMutatorContext) bool {
+	if len(r.directDeps) == 0 {
+		return true
+	}
+
+	matches := false
+	ctx.VisitDirectDeps(func(m Module) {
+		if !matches {
+			name := ctx.OtherModuleName(m)
+			matches = r.directDeps[name]
+		}
+	})
+
+	return matches
+}
+
 func (r *rule) appliesToModuleType(moduleType string) bool {
 	return (len(r.moduleTypes) == 0 || InList(moduleType, r.moduleTypes)) && !InList(moduleType, r.unlessModuleTypes)
 }
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 02b4362..920b9a5 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -16,13 +16,43 @@
 
 import (
 	"testing"
+
+	"github.com/google/blueprint"
 )
 
+func init() {
+	// Add extra rules needed for testing.
+	AddNeverAllowRules(
+		NeverAllow().InDirectDeps("not_allowed_in_direct_deps"),
+	)
+}
+
 var neverallowTests = []struct {
 	name          string
 	fs            map[string][]byte
 	expectedError string
 }{
+	// Test General Functionality
+
+	// in direct deps tests
+	{
+		name: "not_allowed_in_direct_deps",
+		fs: map[string][]byte{
+			"top/Blueprints": []byte(`
+				cc_library {
+					name: "not_allowed_in_direct_deps",
+				}`),
+			"other/Blueprints": []byte(`
+				cc_library {
+					name: "libother",
+					static_libs: ["not_allowed_in_direct_deps"],
+				}`),
+		},
+		expectedError: `module "libother": violates neverallow deps:not_allowed_in_direct_deps`,
+	},
+
+	// Test specific rules
+
 	// include_dir rule tests
 	{
 		name: "include_dir not allowed to reference art",
@@ -242,6 +272,7 @@
 type mockCcLibraryProperties struct {
 	Include_dirs     []string
 	Vendor_available *bool
+	Static_libs      []string
 
 	Vndk struct {
 		Enabled                *bool
@@ -272,6 +303,19 @@
 	return m
 }
 
+type neverallowTestDependencyTag struct {
+	blueprint.BaseDependencyTag
+	name string
+}
+
+var staticDepTag = neverallowTestDependencyTag{name: "static"}
+
+func (c *mockCcLibraryModule) DepsMutator(ctx BottomUpMutatorContext) {
+	for _, lib := range c.properties.Static_libs {
+		ctx.AddDependency(ctx.Module(), staticDepTag, lib)
+	}
+}
+
 func (p *mockCcLibraryModule) GenerateAndroidBuildActions(ModuleContext) {
 }