blob: c7ef1dae60c0b8e74e71dd474d3dc13cddaa9040 [file] [log] [blame]
Paul Duffin2e61fa62019-03-28 14:10:57 +00001// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
18 "fmt"
19 "regexp"
20 "strings"
21 "sync"
22)
23
24// Enforces visibility rules between modules.
25//
26// Two stage process:
27// * First stage works bottom up to extract visibility information from the modules, parse it,
28// create visibilityRule structures and store them in a map keyed by the module's
29// qualifiedModuleName instance, i.e. //<pkg>:<name>. The map is stored in the context rather
30// than a global variable for testing. Each test has its own Config so they do not share a map
31// and so can be run in parallel.
32//
33// * Second stage works top down and iterates over all the deps for each module. If the dep is in
34// the same package then it is automatically visible. Otherwise, for each dep it first extracts
35// its visibilityRule from the config map. If one could not be found then it assumes that it is
36// publicly visible. Otherwise, it calls the visibility rule to check that the module can see
37// the dependency. If it cannot then an error is reported.
38//
39// TODO(b/130631145) - Make visibility work properly with prebuilts.
40// TODO(b/130796911) - Make visibility work properly with defaults.
41
42// Patterns for the values that can be specified in visibility property.
43const (
44 packagePattern = `//([^/:]+(?:/[^/:]+)*)`
45 namePattern = `:([^/:]+)`
46 visibilityRulePattern = `^(?:` + packagePattern + `)?(?:` + namePattern + `)?$`
47)
48
49var visibilityRuleRegexp = regexp.MustCompile(visibilityRulePattern)
50
51// Qualified id for a module
52type qualifiedModuleName struct {
53 // The package (i.e. directory) in which the module is defined, without trailing /
54 pkg string
55
56 // The name of the module.
57 name string
58}
59
60func (q qualifiedModuleName) String() string {
61 return fmt.Sprintf("//%s:%s", q.pkg, q.name)
62}
63
64// A visibility rule is associated with a module and determines which other modules it is visible
65// to, i.e. which other modules can depend on the rule's module.
66type visibilityRule interface {
67 // Check to see whether this rules matches m.
68 // Returns true if it does, false otherwise.
69 matches(m qualifiedModuleName) bool
70
71 String() string
72}
73
Martin Stjernholm226b20d2019-05-17 22:42:02 +010074// A compositeRule is a visibility rule composed from a list of atomic visibility rules.
75//
76// The list corresponds to the list of strings in the visibility property after defaults expansion.
77// Even though //visibility:public is not allowed together with other rules in the visibility list
78// of a single module, it is allowed here to permit a module to override an inherited visibility
79// spec with public visibility.
80//
81// //visibility:private is not allowed in the same way, since we'd need to check for it during the
82// defaults expansion to make that work. No non-private visibility rules are allowed in a
83// compositeRule containing a privateRule.
84//
Paul Duffin2e61fa62019-03-28 14:10:57 +000085// This array will only be [] if all the rules are invalid and will behave as if visibility was
86// ["//visibility:private"].
87type compositeRule []visibilityRule
88
89// A compositeRule matches if and only if any of its rules matches.
90func (c compositeRule) matches(m qualifiedModuleName) bool {
91 for _, r := range c {
92 if r.matches(m) {
93 return true
94 }
95 }
96 return false
97}
98
99func (r compositeRule) String() string {
100 s := make([]string, 0, len(r))
101 for _, r := range r {
102 s = append(s, r.String())
103 }
104
105 return "[" + strings.Join(s, ", ") + "]"
106}
107
108// A packageRule is a visibility rule that matches modules in a specific package (i.e. directory).
109type packageRule struct {
110 pkg string
111}
112
113func (r packageRule) matches(m qualifiedModuleName) bool {
114 return m.pkg == r.pkg
115}
116
117func (r packageRule) String() string {
118 return fmt.Sprintf("//%s:__pkg__", r.pkg)
119}
120
121// A subpackagesRule is a visibility rule that matches modules in a specific package (i.e.
122// directory) or any of its subpackages (i.e. subdirectories).
123type subpackagesRule struct {
124 pkgPrefix string
125}
126
127func (r subpackagesRule) matches(m qualifiedModuleName) bool {
128 return isAncestor(r.pkgPrefix, m.pkg)
129}
130
131func isAncestor(p1 string, p2 string) bool {
132 return strings.HasPrefix(p2+"/", p1+"/")
133}
134
135func (r subpackagesRule) String() string {
136 return fmt.Sprintf("//%s:__subpackages__", r.pkgPrefix)
137}
138
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100139// visibilityRule for //visibility:public
140type publicRule struct{}
141
142func (r publicRule) matches(_ qualifiedModuleName) bool {
143 return true
144}
145
146func (r publicRule) String() string {
147 return "//visibility:public"
148}
149
150// visibilityRule for //visibility:private
151type privateRule struct{}
152
153func (r privateRule) matches(_ qualifiedModuleName) bool {
154 return false
155}
156
157func (r privateRule) String() string {
158 return "//visibility:private"
159}
160
Paul Duffin2e61fa62019-03-28 14:10:57 +0000161var visibilityRuleMap = NewOnceKey("visibilityRuleMap")
162
163// The map from qualifiedModuleName to visibilityRule.
164func moduleToVisibilityRuleMap(ctx BaseModuleContext) *sync.Map {
165 return ctx.Config().Once(visibilityRuleMap, func() interface{} {
166 return &sync.Map{}
167 }).(*sync.Map)
168}
169
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100170// The rule checker needs to be registered before defaults expansion to correctly check that
171// //visibility:xxx isn't combined with other packages in the same list in any one module.
172func registerVisibilityRuleChecker(ctx RegisterMutatorsContext) {
173 ctx.BottomUp("visibilityRuleChecker", visibilityRuleChecker).Parallel()
174}
175
Paul Duffin2e61fa62019-03-28 14:10:57 +0000176// Visibility is not dependent on arch so this must be registered before the arch phase to avoid
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100177// having to process multiple variants for each module. This goes after defaults expansion to gather
178// the complete visibility lists from flat lists.
Paul Duffin2e61fa62019-03-28 14:10:57 +0000179func registerVisibilityRuleGatherer(ctx RegisterMutatorsContext) {
180 ctx.BottomUp("visibilityRuleGatherer", visibilityRuleGatherer).Parallel()
181}
182
183// This must be registered after the deps have been resolved.
184func registerVisibilityRuleEnforcer(ctx RegisterMutatorsContext) {
185 ctx.TopDown("visibilityRuleEnforcer", visibilityRuleEnforcer).Parallel()
186}
187
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100188// Checks the per-module visibility rule lists before defaults expansion.
189func visibilityRuleChecker(ctx BottomUpMutatorContext) {
190 qualified := createQualifiedModuleName(ctx)
191 if d, ok := ctx.Module().(Defaults); ok {
192 // Defaults modules don't store the payload properties in m.base().
193 for _, props := range d.properties() {
194 if cp, ok := props.(*commonProperties); ok {
195 if visibility := cp.Visibility; visibility != nil {
196 checkRules(ctx, qualified.pkg, visibility)
197 }
198 }
199 }
200 } else if m, ok := ctx.Module().(Module); ok {
201 if visibility := m.base().commonProperties.Visibility; visibility != nil {
202 checkRules(ctx, qualified.pkg, visibility)
203 }
204 }
205}
206
207func checkRules(ctx BottomUpMutatorContext, currentPkg string, visibility []string) {
208 ruleCount := len(visibility)
209 if ruleCount == 0 {
210 // This prohibits an empty list as its meaning is unclear, e.g. it could mean no visibility and
211 // it could mean public visibility. Requiring at least one rule makes the owner's intent
212 // clearer.
213 ctx.PropertyErrorf("visibility", "must contain at least one visibility rule")
214 return
215 }
216
217 for _, v := range visibility {
218 ok, pkg, name := splitRule(ctx, v, currentPkg)
219 if !ok {
220 // Visibility rule is invalid so ignore it. Keep going rather than aborting straight away to
221 // ensure all the rules on this module are checked.
222 ctx.PropertyErrorf("visibility",
223 "invalid visibility pattern %q must match"+
224 " //<package>:<module>, //<package> or :<module>",
225 v)
226 continue
227 }
228
229 if pkg == "visibility" {
230 switch name {
231 case "private", "public":
232 case "legacy_public":
233 ctx.PropertyErrorf("visibility", "//visibility:legacy_public must not be used")
234 continue
235 default:
236 ctx.PropertyErrorf("visibility", "unrecognized visibility rule %q", v)
237 continue
238 }
239 if ruleCount != 1 {
240 ctx.PropertyErrorf("visibility", "cannot mix %q with any other visibility rules", v)
241 continue
242 }
243 }
244
245 // If the current directory is not in the vendor tree then there are some additional
246 // restrictions on the rules.
247 if !isAncestor("vendor", currentPkg) {
248 if !isAllowedFromOutsideVendor(pkg, name) {
249 ctx.PropertyErrorf("visibility",
250 "%q is not allowed. Packages outside //vendor cannot make themselves visible to specific"+
251 " targets within //vendor, they can only use //vendor:__subpackages__.", v)
252 continue
253 }
254 }
255 }
256}
257
258// Gathers the flattened visibility rules after defaults expansion, parses the visibility
259// properties, stores them in a map by qualifiedModuleName for retrieval during enforcement.
Paul Duffin2e61fa62019-03-28 14:10:57 +0000260//
261// See ../README.md#Visibility for information on the format of the visibility rules.
Paul Duffin2e61fa62019-03-28 14:10:57 +0000262func visibilityRuleGatherer(ctx BottomUpMutatorContext) {
263 m, ok := ctx.Module().(Module)
264 if !ok {
265 return
266 }
267
268 qualified := createQualifiedModuleName(ctx)
269
270 visibility := m.base().commonProperties.Visibility
271 if visibility != nil {
272 rule := parseRules(ctx, qualified.pkg, visibility)
273 if rule != nil {
274 moduleToVisibilityRuleMap(ctx).Store(qualified, rule)
275 }
276 }
277}
278
279func parseRules(ctx BottomUpMutatorContext, currentPkg string, visibility []string) compositeRule {
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100280 rules := make(compositeRule, 0, len(visibility))
281 hasPrivateRule := false
282 hasNonPrivateRule := false
Paul Duffin2e61fa62019-03-28 14:10:57 +0000283 for _, v := range visibility {
284 ok, pkg, name := splitRule(ctx, v, currentPkg)
285 if !ok {
Paul Duffin2e61fa62019-03-28 14:10:57 +0000286 continue
287 }
288
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100289 var r visibilityRule
290 isPrivateRule := false
Paul Duffin2e61fa62019-03-28 14:10:57 +0000291 if pkg == "visibility" {
Paul Duffin2e61fa62019-03-28 14:10:57 +0000292 switch name {
293 case "private":
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100294 r = privateRule{}
295 isPrivateRule = true
Paul Duffin2e61fa62019-03-28 14:10:57 +0000296 case "public":
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100297 r = publicRule{}
298 }
299 } else {
300 switch name {
301 case "__pkg__":
302 r = packageRule{pkg}
303 case "__subpackages__":
304 r = subpackagesRule{pkg}
Paul Duffin2e61fa62019-03-28 14:10:57 +0000305 default:
Paul Duffin2e61fa62019-03-28 14:10:57 +0000306 continue
307 }
308 }
309
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100310 if isPrivateRule {
311 hasPrivateRule = true
312 } else {
313 hasNonPrivateRule = true
Paul Duffin2e61fa62019-03-28 14:10:57 +0000314 }
315
316 rules = append(rules, r)
317 }
318
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100319 if hasPrivateRule && hasNonPrivateRule {
320 ctx.PropertyErrorf("visibility",
321 "cannot mix \"//visibility:private\" with any other visibility rules")
322 return compositeRule{privateRule{}}
323 }
324
Paul Duffin2e61fa62019-03-28 14:10:57 +0000325 return rules
326}
327
328func isAllowedFromOutsideVendor(pkg string, name string) bool {
329 if pkg == "vendor" {
330 if name == "__subpackages__" {
331 return true
332 }
333 return false
334 }
335
336 return !isAncestor("vendor", pkg)
337}
338
339func splitRule(ctx BaseModuleContext, ruleExpression string, currentPkg string) (bool, string, string) {
340 // Make sure that the rule is of the correct format.
341 matches := visibilityRuleRegexp.FindStringSubmatch(ruleExpression)
342 if ruleExpression == "" || matches == nil {
343 return false, "", ""
344 }
345
346 // Extract the package and name.
347 pkg := matches[1]
348 name := matches[2]
349
350 // Normalize the short hands
351 if pkg == "" {
352 pkg = currentPkg
353 }
354 if name == "" {
355 name = "__pkg__"
356 }
357
358 return true, pkg, name
359}
360
361func visibilityRuleEnforcer(ctx TopDownMutatorContext) {
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100362 if _, ok := ctx.Module().(Module); !ok {
Paul Duffin2e61fa62019-03-28 14:10:57 +0000363 return
364 }
365
366 qualified := createQualifiedModuleName(ctx)
367
368 moduleToVisibilityRule := moduleToVisibilityRuleMap(ctx)
369
370 // Visit all the dependencies making sure that this module has access to them all.
371 ctx.VisitDirectDeps(func(dep Module) {
372 depName := ctx.OtherModuleName(dep)
373 depDir := ctx.OtherModuleDir(dep)
374 depQualified := qualifiedModuleName{depDir, depName}
375
376 // Targets are always visible to other targets in their own package.
377 if depQualified.pkg == qualified.pkg {
378 return
379 }
380
381 rule, ok := moduleToVisibilityRule.Load(depQualified)
382 if ok {
383 if !rule.(compositeRule).matches(qualified) {
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100384 ctx.ModuleErrorf("depends on %s which is not visible to this module", depQualified)
Paul Duffin2e61fa62019-03-28 14:10:57 +0000385 }
386 }
387 })
388}
389
390func createQualifiedModuleName(ctx BaseModuleContext) qualifiedModuleName {
391 moduleName := ctx.ModuleName()
392 dir := ctx.ModuleDir()
393 qualified := qualifiedModuleName{dir, moduleName}
394 return qualified
395}