blob: a7e718ba7761f59a4997586f723562ec8866790a [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//
Paul Duffine2453c72019-05-31 14:00:04 +010026// Multi stage process:
27// * First stage works bottom up, before defaults expansion, to check the syntax of the visibility
28// rules that have been specified.
29//
30// * Second stage works bottom up to extract the package info for each package and store them in a
31// map by package name. See package.go for functionality for this.
32//
33// * Third stage works bottom up to extract visibility information from the modules, parse it,
Paul Duffin2e61fa62019-03-28 14:10:57 +000034// create visibilityRule structures and store them in a map keyed by the module's
35// qualifiedModuleName instance, i.e. //<pkg>:<name>. The map is stored in the context rather
36// than a global variable for testing. Each test has its own Config so they do not share a map
Paul Duffine2453c72019-05-31 14:00:04 +010037// and so can be run in parallel. If a module has no visibility specified then it uses the
38// default package visibility if specified.
Paul Duffin2e61fa62019-03-28 14:10:57 +000039//
Paul Duffine2453c72019-05-31 14:00:04 +010040// * Fourth stage works top down and iterates over all the deps for each module. If the dep is in
Paul Duffin2e61fa62019-03-28 14:10:57 +000041// the same package then it is automatically visible. Otherwise, for each dep it first extracts
42// its visibilityRule from the config map. If one could not be found then it assumes that it is
43// publicly visible. Otherwise, it calls the visibility rule to check that the module can see
44// the dependency. If it cannot then an error is reported.
45//
46// TODO(b/130631145) - Make visibility work properly with prebuilts.
47// TODO(b/130796911) - Make visibility work properly with defaults.
48
49// Patterns for the values that can be specified in visibility property.
50const (
51 packagePattern = `//([^/:]+(?:/[^/:]+)*)`
52 namePattern = `:([^/:]+)`
53 visibilityRulePattern = `^(?:` + packagePattern + `)?(?:` + namePattern + `)?$`
54)
55
56var visibilityRuleRegexp = regexp.MustCompile(visibilityRulePattern)
57
Paul Duffin2e61fa62019-03-28 14:10:57 +000058// A visibility rule is associated with a module and determines which other modules it is visible
59// to, i.e. which other modules can depend on the rule's module.
60type visibilityRule interface {
61 // Check to see whether this rules matches m.
62 // Returns true if it does, false otherwise.
63 matches(m qualifiedModuleName) bool
64
65 String() string
66}
67
Paul Duffine2453c72019-05-31 14:00:04 +010068// Describes the properties provided by a module that contain visibility rules.
69type visibilityPropertyImpl struct {
Paul Duffin63c6e182019-07-24 14:24:38 +010070 name string
71 stringsProperty *[]string
Paul Duffine2453c72019-05-31 14:00:04 +010072}
73
74type visibilityProperty interface {
75 getName() string
76 getStrings() []string
77}
78
Paul Duffin63c6e182019-07-24 14:24:38 +010079func newVisibilityProperty(name string, stringsProperty *[]string) visibilityProperty {
Paul Duffine2453c72019-05-31 14:00:04 +010080 return visibilityPropertyImpl{
Paul Duffin63c6e182019-07-24 14:24:38 +010081 name: name,
82 stringsProperty: stringsProperty,
Paul Duffine2453c72019-05-31 14:00:04 +010083 }
84}
85
86func (p visibilityPropertyImpl) getName() string {
87 return p.name
88}
89
90func (p visibilityPropertyImpl) getStrings() []string {
Paul Duffin63c6e182019-07-24 14:24:38 +010091 return *p.stringsProperty
Paul Duffine2453c72019-05-31 14:00:04 +010092}
93
Martin Stjernholm226b20d2019-05-17 22:42:02 +010094// A compositeRule is a visibility rule composed from a list of atomic visibility rules.
95//
96// The list corresponds to the list of strings in the visibility property after defaults expansion.
97// Even though //visibility:public is not allowed together with other rules in the visibility list
98// of a single module, it is allowed here to permit a module to override an inherited visibility
99// spec with public visibility.
100//
101// //visibility:private is not allowed in the same way, since we'd need to check for it during the
102// defaults expansion to make that work. No non-private visibility rules are allowed in a
103// compositeRule containing a privateRule.
104//
Paul Duffin2e61fa62019-03-28 14:10:57 +0000105// This array will only be [] if all the rules are invalid and will behave as if visibility was
106// ["//visibility:private"].
107type compositeRule []visibilityRule
108
109// A compositeRule matches if and only if any of its rules matches.
110func (c compositeRule) matches(m qualifiedModuleName) bool {
111 for _, r := range c {
112 if r.matches(m) {
113 return true
114 }
115 }
116 return false
117}
118
Paul Duffine2453c72019-05-31 14:00:04 +0100119func (c compositeRule) String() string {
120 s := make([]string, 0, len(c))
121 for _, r := range c {
Paul Duffin2e61fa62019-03-28 14:10:57 +0000122 s = append(s, r.String())
123 }
124
125 return "[" + strings.Join(s, ", ") + "]"
126}
127
128// A packageRule is a visibility rule that matches modules in a specific package (i.e. directory).
129type packageRule struct {
130 pkg string
131}
132
133func (r packageRule) matches(m qualifiedModuleName) bool {
134 return m.pkg == r.pkg
135}
136
137func (r packageRule) String() string {
138 return fmt.Sprintf("//%s:__pkg__", r.pkg)
139}
140
141// A subpackagesRule is a visibility rule that matches modules in a specific package (i.e.
142// directory) or any of its subpackages (i.e. subdirectories).
143type subpackagesRule struct {
144 pkgPrefix string
145}
146
147func (r subpackagesRule) matches(m qualifiedModuleName) bool {
148 return isAncestor(r.pkgPrefix, m.pkg)
149}
150
151func isAncestor(p1 string, p2 string) bool {
152 return strings.HasPrefix(p2+"/", p1+"/")
153}
154
155func (r subpackagesRule) String() string {
156 return fmt.Sprintf("//%s:__subpackages__", r.pkgPrefix)
157}
158
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100159// visibilityRule for //visibility:public
160type publicRule struct{}
161
162func (r publicRule) matches(_ qualifiedModuleName) bool {
163 return true
164}
165
166func (r publicRule) String() string {
167 return "//visibility:public"
168}
169
170// visibilityRule for //visibility:private
171type privateRule struct{}
172
173func (r privateRule) matches(_ qualifiedModuleName) bool {
174 return false
175}
176
177func (r privateRule) String() string {
178 return "//visibility:private"
179}
180
Paul Duffin2e61fa62019-03-28 14:10:57 +0000181var visibilityRuleMap = NewOnceKey("visibilityRuleMap")
182
183// The map from qualifiedModuleName to visibilityRule.
184func moduleToVisibilityRuleMap(ctx BaseModuleContext) *sync.Map {
185 return ctx.Config().Once(visibilityRuleMap, func() interface{} {
186 return &sync.Map{}
187 }).(*sync.Map)
188}
189
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100190// The rule checker needs to be registered before defaults expansion to correctly check that
191// //visibility:xxx isn't combined with other packages in the same list in any one module.
192func registerVisibilityRuleChecker(ctx RegisterMutatorsContext) {
193 ctx.BottomUp("visibilityRuleChecker", visibilityRuleChecker).Parallel()
194}
195
Paul Duffine2453c72019-05-31 14:00:04 +0100196// Registers the function that gathers the visibility rules for each module.
197//
Paul Duffin2e61fa62019-03-28 14:10:57 +0000198// Visibility is not dependent on arch so this must be registered before the arch phase to avoid
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100199// having to process multiple variants for each module. This goes after defaults expansion to gather
Paul Duffine2453c72019-05-31 14:00:04 +0100200// the complete visibility lists from flat lists and after the package info is gathered to ensure
201// that default_visibility is available.
Paul Duffin2e61fa62019-03-28 14:10:57 +0000202func registerVisibilityRuleGatherer(ctx RegisterMutatorsContext) {
203 ctx.BottomUp("visibilityRuleGatherer", visibilityRuleGatherer).Parallel()
204}
205
206// This must be registered after the deps have been resolved.
207func registerVisibilityRuleEnforcer(ctx RegisterMutatorsContext) {
208 ctx.TopDown("visibilityRuleEnforcer", visibilityRuleEnforcer).Parallel()
209}
210
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100211// Checks the per-module visibility rule lists before defaults expansion.
212func visibilityRuleChecker(ctx BottomUpMutatorContext) {
213 qualified := createQualifiedModuleName(ctx)
Paul Duffin63c6e182019-07-24 14:24:38 +0100214 if m, ok := ctx.Module().(Module); ok {
Paul Duffine2453c72019-05-31 14:00:04 +0100215 visibilityProperties := m.visibilityProperties()
216 for _, p := range visibilityProperties {
217 if visibility := p.getStrings(); visibility != nil {
218 checkRules(ctx, qualified.pkg, p.getName(), visibility)
219 }
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100220 }
221 }
222}
223
Paul Duffine2453c72019-05-31 14:00:04 +0100224func checkRules(ctx BaseModuleContext, currentPkg, property string, visibility []string) {
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100225 ruleCount := len(visibility)
226 if ruleCount == 0 {
227 // This prohibits an empty list as its meaning is unclear, e.g. it could mean no visibility and
228 // it could mean public visibility. Requiring at least one rule makes the owner's intent
229 // clearer.
Paul Duffine2453c72019-05-31 14:00:04 +0100230 ctx.PropertyErrorf(property, "must contain at least one visibility rule")
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100231 return
232 }
233
234 for _, v := range visibility {
Paul Duffine2453c72019-05-31 14:00:04 +0100235 ok, pkg, name := splitRule(v, currentPkg)
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100236 if !ok {
237 // Visibility rule is invalid so ignore it. Keep going rather than aborting straight away to
238 // ensure all the rules on this module are checked.
Paul Duffine2453c72019-05-31 14:00:04 +0100239 ctx.PropertyErrorf(property,
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100240 "invalid visibility pattern %q must match"+
241 " //<package>:<module>, //<package> or :<module>",
242 v)
243 continue
244 }
245
246 if pkg == "visibility" {
247 switch name {
248 case "private", "public":
249 case "legacy_public":
Paul Duffine2453c72019-05-31 14:00:04 +0100250 ctx.PropertyErrorf(property, "//visibility:legacy_public must not be used")
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100251 continue
252 default:
Paul Duffine2453c72019-05-31 14:00:04 +0100253 ctx.PropertyErrorf(property, "unrecognized visibility rule %q", v)
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100254 continue
255 }
256 if ruleCount != 1 {
Paul Duffine2453c72019-05-31 14:00:04 +0100257 ctx.PropertyErrorf(property, "cannot mix %q with any other visibility rules", v)
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100258 continue
259 }
260 }
261
262 // If the current directory is not in the vendor tree then there are some additional
263 // restrictions on the rules.
264 if !isAncestor("vendor", currentPkg) {
265 if !isAllowedFromOutsideVendor(pkg, name) {
Paul Duffine2453c72019-05-31 14:00:04 +0100266 ctx.PropertyErrorf(property,
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100267 "%q is not allowed. Packages outside //vendor cannot make themselves visible to specific"+
268 " targets within //vendor, they can only use //vendor:__subpackages__.", v)
269 continue
270 }
271 }
272 }
273}
274
275// Gathers the flattened visibility rules after defaults expansion, parses the visibility
276// properties, stores them in a map by qualifiedModuleName for retrieval during enforcement.
Paul Duffin2e61fa62019-03-28 14:10:57 +0000277//
278// See ../README.md#Visibility for information on the format of the visibility rules.
Paul Duffin2e61fa62019-03-28 14:10:57 +0000279func visibilityRuleGatherer(ctx BottomUpMutatorContext) {
280 m, ok := ctx.Module().(Module)
281 if !ok {
282 return
283 }
284
Paul Duffine2453c72019-05-31 14:00:04 +0100285 qualifiedModuleId := m.qualifiedModuleId(ctx)
286 currentPkg := qualifiedModuleId.pkg
Paul Duffin2e61fa62019-03-28 14:10:57 +0000287
Paul Duffin63c6e182019-07-24 14:24:38 +0100288 // Parse the visibility rules that control access to the module and store them by id
289 // for use when enforcing the rules.
290 if visibility := m.visibility(); visibility != nil {
291 rule := parseRules(ctx, currentPkg, m.visibility())
292 if rule != nil {
293 moduleToVisibilityRuleMap(ctx).Store(qualifiedModuleId, rule)
Paul Duffin2e61fa62019-03-28 14:10:57 +0000294 }
295 }
296}
297
Paul Duffine2453c72019-05-31 14:00:04 +0100298func parseRules(ctx BaseModuleContext, currentPkg string, visibility []string) compositeRule {
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100299 rules := make(compositeRule, 0, len(visibility))
300 hasPrivateRule := false
301 hasNonPrivateRule := false
Paul Duffin2e61fa62019-03-28 14:10:57 +0000302 for _, v := range visibility {
Paul Duffine2453c72019-05-31 14:00:04 +0100303 ok, pkg, name := splitRule(v, currentPkg)
Paul Duffin2e61fa62019-03-28 14:10:57 +0000304 if !ok {
Paul Duffin2e61fa62019-03-28 14:10:57 +0000305 continue
306 }
307
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100308 var r visibilityRule
309 isPrivateRule := false
Paul Duffin2e61fa62019-03-28 14:10:57 +0000310 if pkg == "visibility" {
Paul Duffin2e61fa62019-03-28 14:10:57 +0000311 switch name {
312 case "private":
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100313 r = privateRule{}
314 isPrivateRule = true
Paul Duffin2e61fa62019-03-28 14:10:57 +0000315 case "public":
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100316 r = publicRule{}
317 }
318 } else {
319 switch name {
320 case "__pkg__":
321 r = packageRule{pkg}
322 case "__subpackages__":
323 r = subpackagesRule{pkg}
Paul Duffin2e61fa62019-03-28 14:10:57 +0000324 default:
Paul Duffin2e61fa62019-03-28 14:10:57 +0000325 continue
326 }
327 }
328
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100329 if isPrivateRule {
330 hasPrivateRule = true
331 } else {
332 hasNonPrivateRule = true
Paul Duffin2e61fa62019-03-28 14:10:57 +0000333 }
334
335 rules = append(rules, r)
336 }
337
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100338 if hasPrivateRule && hasNonPrivateRule {
339 ctx.PropertyErrorf("visibility",
340 "cannot mix \"//visibility:private\" with any other visibility rules")
341 return compositeRule{privateRule{}}
342 }
343
Paul Duffin2e61fa62019-03-28 14:10:57 +0000344 return rules
345}
346
347func isAllowedFromOutsideVendor(pkg string, name string) bool {
348 if pkg == "vendor" {
349 if name == "__subpackages__" {
350 return true
351 }
352 return false
353 }
354
355 return !isAncestor("vendor", pkg)
356}
357
Paul Duffine2453c72019-05-31 14:00:04 +0100358func splitRule(ruleExpression string, currentPkg string) (bool, string, string) {
Paul Duffin2e61fa62019-03-28 14:10:57 +0000359 // Make sure that the rule is of the correct format.
360 matches := visibilityRuleRegexp.FindStringSubmatch(ruleExpression)
361 if ruleExpression == "" || matches == nil {
362 return false, "", ""
363 }
364
365 // Extract the package and name.
366 pkg := matches[1]
367 name := matches[2]
368
369 // Normalize the short hands
370 if pkg == "" {
371 pkg = currentPkg
372 }
373 if name == "" {
374 name = "__pkg__"
375 }
376
377 return true, pkg, name
378}
379
380func visibilityRuleEnforcer(ctx TopDownMutatorContext) {
Martin Stjernholm226b20d2019-05-17 22:42:02 +0100381 if _, ok := ctx.Module().(Module); !ok {
Paul Duffin2e61fa62019-03-28 14:10:57 +0000382 return
383 }
384
385 qualified := createQualifiedModuleName(ctx)
386
387 moduleToVisibilityRule := moduleToVisibilityRuleMap(ctx)
388
389 // Visit all the dependencies making sure that this module has access to them all.
390 ctx.VisitDirectDeps(func(dep Module) {
391 depName := ctx.OtherModuleName(dep)
392 depDir := ctx.OtherModuleDir(dep)
393 depQualified := qualifiedModuleName{depDir, depName}
394
395 // Targets are always visible to other targets in their own package.
396 if depQualified.pkg == qualified.pkg {
397 return
398 }
399
Paul Duffine2453c72019-05-31 14:00:04 +0100400 value, ok := moduleToVisibilityRule.Load(depQualified)
401 var rule compositeRule
Paul Duffin2e61fa62019-03-28 14:10:57 +0000402 if ok {
Paul Duffine2453c72019-05-31 14:00:04 +0100403 rule = value.(compositeRule)
404 } else {
Paul Duffine484f472019-06-20 16:38:08 +0100405 rule = packageDefaultVisibility(ctx, depQualified)
Paul Duffin2e61fa62019-03-28 14:10:57 +0000406 }
Paul Duffine2453c72019-05-31 14:00:04 +0100407 if rule != nil && !rule.matches(qualified) {
408 ctx.ModuleErrorf("depends on %s which is not visible to this module", depQualified)
409 }
Paul Duffin2e61fa62019-03-28 14:10:57 +0000410 })
411}
412
413func createQualifiedModuleName(ctx BaseModuleContext) qualifiedModuleName {
414 moduleName := ctx.ModuleName()
415 dir := ctx.ModuleDir()
416 qualified := qualifiedModuleName{dir, moduleName}
417 return qualified
418}
Paul Duffine484f472019-06-20 16:38:08 +0100419
420func packageDefaultVisibility(ctx BaseModuleContext, moduleId qualifiedModuleName) compositeRule {
421 moduleToVisibilityRule := moduleToVisibilityRuleMap(ctx)
422 packageQualifiedId := moduleId.getContainingPackageId()
423 for {
424 value, ok := moduleToVisibilityRule.Load(packageQualifiedId)
425 if ok {
426 return value.(compositeRule)
427 }
428
429 if packageQualifiedId.isRootPackage() {
430 return nil
431 }
432
433 packageQualifiedId = packageQualifiedId.getContainingPackageId()
434 }
435}