Add visibility support
Implementation uploaded for review. Includes unit tests but does not yet handle prebuilts, that will come in a future change once some more general issues with prebuilts and namespaces is resolved. See README.md#Visibility for details of what this does and how to use it. Bug: 112158820 Test: add visibility rules for core library modules, make core-tests Change-Id: I8ec980554398ad6f2d42043ce518f811a35da679
This commit is contained in:
parent
02cbe8f1c6
commit
2e61fa6e14
|
@ -70,6 +70,7 @@ bootstrap_go_package {
|
|||
"android/testing.go",
|
||||
"android/util.go",
|
||||
"android/variable.go",
|
||||
"android/visibility.go",
|
||||
"android/vts_config.go",
|
||||
"android/writedocs.go",
|
||||
|
||||
|
@ -90,6 +91,7 @@ bootstrap_go_package {
|
|||
"android/rule_builder_test.go",
|
||||
"android/util_test.go",
|
||||
"android/variable_test.go",
|
||||
"android/visibility_test.go",
|
||||
"android/vts_config_test.go",
|
||||
],
|
||||
}
|
||||
|
|
72
README.md
72
README.md
|
@ -107,6 +107,30 @@ cc_binary {
|
|||
}
|
||||
```
|
||||
|
||||
### Packages
|
||||
|
||||
The build is organized into packages where each package is a collection of related files and a
|
||||
specification of the dependencies among them in the form of modules.
|
||||
|
||||
A package is defined as a directory containing a file named `Android.bp`, residing beneath the
|
||||
top-level directory in the build and its name is its path relative to the top-level directory. A
|
||||
package includes all files in its directory, plus all subdirectories beneath it, except those which
|
||||
themselves contain an `Android.bp` file.
|
||||
|
||||
The modules in a package's `Android.bp` and included files are part of the module.
|
||||
|
||||
For example, in the following directory tree (where `.../android/` is the top-level Android
|
||||
directory) there are two packages, `my/app`, and the subpackage `my/app/tests`. Note that
|
||||
`my/app/data` is not a package, but a directory belonging to package `my/app`.
|
||||
|
||||
.../android/my/app/Android.bp
|
||||
.../android/my/app/app.cc
|
||||
.../android/my/app/data/input.txt
|
||||
.../android/my/app/tests/Android.bp
|
||||
.../android/my/app/tests/test.cc
|
||||
|
||||
This is based on the Bazel package concept.
|
||||
|
||||
### Name resolution
|
||||
|
||||
Soong provides the ability for modules in different directories to specify
|
||||
|
@ -139,6 +163,54 @@ should be a space-separated list of namespaces that Soong export to Make to be
|
|||
built by the `m` command. After we have fully converted from Make to Soong, the
|
||||
details of enabling namespaces could potentially change.
|
||||
|
||||
### Visibility
|
||||
|
||||
The `visibility` property on a module controls whether the module can be
|
||||
used by other packages. Modules are always visible to other modules declared
|
||||
in the same package. This is based on the Bazel visibility mechanism.
|
||||
|
||||
If specified the `visibility` property must contain at least one rule.
|
||||
|
||||
Each rule in the property must be in one of the following forms:
|
||||
* `["//visibility:public"]`: Anyone can use this module.
|
||||
* `["//visibility:private"]`: Only rules in the module's package (not its
|
||||
subpackages) can use this module.
|
||||
* `["//some/package:__pkg__", "//other/package:__pkg__"]`: Only modules in
|
||||
`some/package` and `other/package` (defined in `some/package/*.bp` and
|
||||
`other/package/*.bp`) have access to this module. Note that sub-packages do not
|
||||
have access to the rule; for example, `//some/package/foo:bar` or
|
||||
`//other/package/testing:bla` wouldn't have access. `__pkg__` is a special
|
||||
module and must be used verbatim. It represents all of the modules in the
|
||||
package.
|
||||
* `["//project:__subpackages__", "//other:__subpackages__"]`: Only modules in
|
||||
packages `project` or `other` or in one of their sub-packages have access to
|
||||
this module. For example, `//project:rule`, `//project/library:lib` or
|
||||
`//other/testing/internal:munge` are allowed to depend on this rule (but not
|
||||
`//independent:evil`)
|
||||
* `["//project"]`: This is shorthand for `["//project:__pkg__"]`
|
||||
* `[":__subpackages__"]`: This is shorthand for `["//project:__subpackages__"]`
|
||||
where `//project` is the module's package. e.g. using `[":__subpackages__"]` in
|
||||
`packages/apps/Settings/Android.bp` is equivalent to
|
||||
`//packages/apps/Settings:__subpackages__`.
|
||||
* `["//visibility:legacy_public"]`: The default visibility, behaves as
|
||||
`//visibility:public` for now. It is an error if it is used in a module.
|
||||
|
||||
The visibility rules of `//visibility:public` and `//visibility:private` can
|
||||
not be combined with any other visibility specifications.
|
||||
|
||||
Packages outside `vendor/` cannot make themselves visible to specific packages
|
||||
in `vendor/`, e.g. a module in `libcore` cannot declare that it is visible to
|
||||
say `vendor/google`, instead it must make itself visible to all packages within
|
||||
`vendor/` using `//vendor:__subpackages__`.
|
||||
|
||||
If a module does not specify the `visibility` property the module is
|
||||
`//visibility:legacy_public`. Once the build has been completely switched over to
|
||||
soong it is possible that a global refactoring will be done to change this to
|
||||
`//visibility:private` at which point all modules that do not currently specify
|
||||
a `visibility` property will be updated to have
|
||||
`visibility = [//visibility:legacy_public]` added. It will then be the owner's
|
||||
responsibility to replace that with a more appropriate visibility.
|
||||
|
||||
### Formatter
|
||||
|
||||
Soong includes a canonical formatter for blueprint files, similar to
|
||||
|
|
|
@ -210,6 +210,33 @@ type commonProperties struct {
|
|||
// emit build rules for this module
|
||||
Enabled *bool `android:"arch_variant"`
|
||||
|
||||
// Controls the visibility of this module to other modules. Allowable values are one or more of
|
||||
// these formats:
|
||||
//
|
||||
// ["//visibility:public"]: Anyone can use this module.
|
||||
// ["//visibility:private"]: Only rules in the module's package (not its subpackages) can use
|
||||
// this module.
|
||||
// ["//some/package:__pkg__", "//other/package:__pkg__"]: Only modules in some/package and
|
||||
// other/package (defined in some/package/*.bp and other/package/*.bp) have access to
|
||||
// this module. Note that sub-packages do not have access to the rule; for example,
|
||||
// //some/package/foo:bar or //other/package/testing:bla wouldn't have access. __pkg__
|
||||
// is a special module and must be used verbatim. It represents all of the modules in the
|
||||
// package.
|
||||
// ["//project:__subpackages__", "//other:__subpackages__"]: Only modules in packages project
|
||||
// or other or in one of their sub-packages have access to this module. For example,
|
||||
// //project:rule, //project/library:lib or //other/testing/internal:munge are allowed
|
||||
// to depend on this rule (but not //independent:evil)
|
||||
// ["//project"]: This is shorthand for ["//project:__pkg__"]
|
||||
// [":__subpackages__"]: This is shorthand for ["//project:__subpackages__"] where
|
||||
// //project is the module's package. e.g. using [":__subpackages__"] in
|
||||
// packages/apps/Settings/Android.bp is equivalent to
|
||||
// //packages/apps/Settings:__subpackages__.
|
||||
// ["//visibility:legacy_public"]: The default visibility, behaves as //visibility:public
|
||||
// for now. It is an error if it is used in a module.
|
||||
// See https://android.googlesource.com/platform/build/soong/+/master/README.md#visibility for
|
||||
// more details.
|
||||
Visibility []string
|
||||
|
||||
// control whether this module compiles for 32-bit, 64-bit, or both. Possible values
|
||||
// are "32" (compile for 32-bit only), "64" (compile for 64-bit only), "both" (compile for both
|
||||
// architectures), or "first" (compile for 64-bit on a 64-bit platform, and 32-bit on a 32-bit
|
||||
|
|
|
@ -78,6 +78,7 @@ var preArch = []RegisterMutatorFunc{
|
|||
RegisterPrebuiltsPreArchMutators,
|
||||
RegisterDefaultsPreArchMutators,
|
||||
RegisterOverridePreArchMutators,
|
||||
registerVisibilityRuleGatherer,
|
||||
}
|
||||
|
||||
func registerArchMutator(ctx RegisterMutatorsContext) {
|
||||
|
@ -92,6 +93,7 @@ var preDeps = []RegisterMutatorFunc{
|
|||
var postDeps = []RegisterMutatorFunc{
|
||||
registerPathDepsMutator,
|
||||
RegisterPrebuiltsPostDepsMutators,
|
||||
registerVisibilityRuleEnforcer,
|
||||
registerNeverallowMutator,
|
||||
}
|
||||
|
||||
|
@ -118,6 +120,7 @@ type TopDownMutatorContext interface {
|
|||
Module() Module
|
||||
|
||||
OtherModuleName(m blueprint.Module) string
|
||||
OtherModuleDir(m blueprint.Module) string
|
||||
OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{})
|
||||
OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag
|
||||
|
||||
|
|
|
@ -26,12 +26,6 @@ import (
|
|||
"github.com/google/blueprint"
|
||||
)
|
||||
|
||||
// This file implements namespaces
|
||||
const (
|
||||
namespacePrefix = "//"
|
||||
modulePrefix = ":"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterModuleType("soong_namespace", NamespaceFactory)
|
||||
}
|
||||
|
@ -215,11 +209,11 @@ func (r *NameResolver) AllModules() []blueprint.ModuleGroup {
|
|||
// parses a fully-qualified path (like "//namespace_path:module_name") into a namespace name and a
|
||||
// module name
|
||||
func (r *NameResolver) parseFullyQualifiedName(name string) (namespaceName string, moduleName string, ok bool) {
|
||||
if !strings.HasPrefix(name, namespacePrefix) {
|
||||
if !strings.HasPrefix(name, "//") {
|
||||
return "", "", false
|
||||
}
|
||||
name = strings.TrimPrefix(name, namespacePrefix)
|
||||
components := strings.Split(name, modulePrefix)
|
||||
name = strings.TrimPrefix(name, "//")
|
||||
components := strings.Split(name, ":")
|
||||
if len(components) != 2 {
|
||||
return "", "", false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,313 @@
|
|||
// Copyright 2019 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package android
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Enforces visibility rules between modules.
|
||||
//
|
||||
// Two stage process:
|
||||
// * First stage works bottom up to extract visibility information from the modules, parse it,
|
||||
// create visibilityRule structures and store them in a map keyed by the module's
|
||||
// qualifiedModuleName instance, i.e. //<pkg>:<name>. The map is stored in the context rather
|
||||
// than a global variable for testing. Each test has its own Config so they do not share a map
|
||||
// and so can be run in parallel.
|
||||
//
|
||||
// * Second stage works top down and iterates over all the deps for each module. If the dep is in
|
||||
// the same package then it is automatically visible. Otherwise, for each dep it first extracts
|
||||
// its visibilityRule from the config map. If one could not be found then it assumes that it is
|
||||
// publicly visible. Otherwise, it calls the visibility rule to check that the module can see
|
||||
// the dependency. If it cannot then an error is reported.
|
||||
//
|
||||
// TODO(b/130631145) - Make visibility work properly with prebuilts.
|
||||
// TODO(b/130796911) - Make visibility work properly with defaults.
|
||||
|
||||
// Patterns for the values that can be specified in visibility property.
|
||||
const (
|
||||
packagePattern = `//([^/:]+(?:/[^/:]+)*)`
|
||||
namePattern = `:([^/:]+)`
|
||||
visibilityRulePattern = `^(?:` + packagePattern + `)?(?:` + namePattern + `)?$`
|
||||
)
|
||||
|
||||
var visibilityRuleRegexp = regexp.MustCompile(visibilityRulePattern)
|
||||
|
||||
// Qualified id for a module
|
||||
type qualifiedModuleName struct {
|
||||
// The package (i.e. directory) in which the module is defined, without trailing /
|
||||
pkg string
|
||||
|
||||
// The name of the module.
|
||||
name string
|
||||
}
|
||||
|
||||
func (q qualifiedModuleName) String() string {
|
||||
return fmt.Sprintf("//%s:%s", q.pkg, q.name)
|
||||
}
|
||||
|
||||
// A visibility rule is associated with a module and determines which other modules it is visible
|
||||
// to, i.e. which other modules can depend on the rule's module.
|
||||
type visibilityRule interface {
|
||||
// Check to see whether this rules matches m.
|
||||
// Returns true if it does, false otherwise.
|
||||
matches(m qualifiedModuleName) bool
|
||||
|
||||
String() string
|
||||
}
|
||||
|
||||
// A compositeRule is a visibility rule composed from other visibility rules.
|
||||
// This array will only be [] if all the rules are invalid and will behave as if visibility was
|
||||
// ["//visibility:private"].
|
||||
type compositeRule []visibilityRule
|
||||
|
||||
// A compositeRule matches if and only if any of its rules matches.
|
||||
func (c compositeRule) matches(m qualifiedModuleName) bool {
|
||||
for _, r := range c {
|
||||
if r.matches(m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r compositeRule) String() string {
|
||||
s := make([]string, 0, len(r))
|
||||
for _, r := range r {
|
||||
s = append(s, r.String())
|
||||
}
|
||||
|
||||
return "[" + strings.Join(s, ", ") + "]"
|
||||
}
|
||||
|
||||
// A packageRule is a visibility rule that matches modules in a specific package (i.e. directory).
|
||||
type packageRule struct {
|
||||
pkg string
|
||||
}
|
||||
|
||||
func (r packageRule) matches(m qualifiedModuleName) bool {
|
||||
return m.pkg == r.pkg
|
||||
}
|
||||
|
||||
func (r packageRule) String() string {
|
||||
return fmt.Sprintf("//%s:__pkg__", r.pkg)
|
||||
}
|
||||
|
||||
// A subpackagesRule is a visibility rule that matches modules in a specific package (i.e.
|
||||
// directory) or any of its subpackages (i.e. subdirectories).
|
||||
type subpackagesRule struct {
|
||||
pkgPrefix string
|
||||
}
|
||||
|
||||
func (r subpackagesRule) matches(m qualifiedModuleName) bool {
|
||||
return isAncestor(r.pkgPrefix, m.pkg)
|
||||
}
|
||||
|
||||
func isAncestor(p1 string, p2 string) bool {
|
||||
return strings.HasPrefix(p2+"/", p1+"/")
|
||||
}
|
||||
|
||||
func (r subpackagesRule) String() string {
|
||||
return fmt.Sprintf("//%s:__subpackages__", r.pkgPrefix)
|
||||
}
|
||||
|
||||
var visibilityRuleMap = NewOnceKey("visibilityRuleMap")
|
||||
|
||||
// The map from qualifiedModuleName to visibilityRule.
|
||||
func moduleToVisibilityRuleMap(ctx BaseModuleContext) *sync.Map {
|
||||
return ctx.Config().Once(visibilityRuleMap, func() interface{} {
|
||||
return &sync.Map{}
|
||||
}).(*sync.Map)
|
||||
}
|
||||
|
||||
// Visibility is not dependent on arch so this must be registered before the arch phase to avoid
|
||||
// having to process multiple variants for each module.
|
||||
func registerVisibilityRuleGatherer(ctx RegisterMutatorsContext) {
|
||||
ctx.BottomUp("visibilityRuleGatherer", visibilityRuleGatherer).Parallel()
|
||||
}
|
||||
|
||||
// This must be registered after the deps have been resolved.
|
||||
func registerVisibilityRuleEnforcer(ctx RegisterMutatorsContext) {
|
||||
ctx.TopDown("visibilityRuleEnforcer", visibilityRuleEnforcer).Parallel()
|
||||
}
|
||||
|
||||
// Gathers the visibility rules, parses the visibility properties, stores them in a map by
|
||||
// qualifiedModuleName for retrieval during enforcement.
|
||||
//
|
||||
// See ../README.md#Visibility for information on the format of the visibility rules.
|
||||
|
||||
func visibilityRuleGatherer(ctx BottomUpMutatorContext) {
|
||||
m, ok := ctx.Module().(Module)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
qualified := createQualifiedModuleName(ctx)
|
||||
|
||||
visibility := m.base().commonProperties.Visibility
|
||||
if visibility != nil {
|
||||
rule := parseRules(ctx, qualified.pkg, visibility)
|
||||
if rule != nil {
|
||||
moduleToVisibilityRuleMap(ctx).Store(qualified, rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseRules(ctx BottomUpMutatorContext, currentPkg string, visibility []string) compositeRule {
|
||||
ruleCount := len(visibility)
|
||||
if ruleCount == 0 {
|
||||
// This prohibits an empty list as its meaning is unclear, e.g. it could mean no visibility and
|
||||
// it could mean public visibility. Requiring at least one rule makes the owner's intent
|
||||
// clearer.
|
||||
ctx.PropertyErrorf("visibility", "must contain at least one visibility rule")
|
||||
return nil
|
||||
}
|
||||
|
||||
rules := make(compositeRule, 0, ruleCount)
|
||||
for _, v := range visibility {
|
||||
ok, pkg, name := splitRule(ctx, v, currentPkg)
|
||||
if !ok {
|
||||
// Visibility rule is invalid so ignore it. Keep going rather than aborting straight away to
|
||||
// ensure all the rules on this module are checked.
|
||||
ctx.PropertyErrorf("visibility",
|
||||
"invalid visibility pattern %q must match"+
|
||||
" //<package>:<module>, //<package> or :<module>",
|
||||
v)
|
||||
continue
|
||||
}
|
||||
|
||||
if pkg == "visibility" {
|
||||
if ruleCount != 1 {
|
||||
ctx.PropertyErrorf("visibility", "cannot mix %q with any other visibility rules", v)
|
||||
continue
|
||||
}
|
||||
switch name {
|
||||
case "private":
|
||||
rules = append(rules, packageRule{currentPkg})
|
||||
continue
|
||||
case "public":
|
||||
return nil
|
||||
case "legacy_public":
|
||||
ctx.PropertyErrorf("visibility", "//visibility:legacy_public must not be used")
|
||||
return nil
|
||||
default:
|
||||
ctx.PropertyErrorf("visibility", "unrecognized visibility rule %q", v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If the current directory is not in the vendor tree then there are some additional
|
||||
// restrictions on the rules.
|
||||
if !isAncestor("vendor", currentPkg) {
|
||||
if !isAllowedFromOutsideVendor(pkg, name) {
|
||||
ctx.PropertyErrorf("visibility",
|
||||
"%q is not allowed. Packages outside //vendor cannot make themselves visible to specific"+
|
||||
" targets within //vendor, they can only use //vendor:__subpackages__.", v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Create the rule
|
||||
var r visibilityRule
|
||||
switch name {
|
||||
case "__pkg__":
|
||||
r = packageRule{pkg}
|
||||
case "__subpackages__":
|
||||
r = subpackagesRule{pkg}
|
||||
default:
|
||||
ctx.PropertyErrorf("visibility", "unrecognized visibility rule %q", v)
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, r)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func isAllowedFromOutsideVendor(pkg string, name string) bool {
|
||||
if pkg == "vendor" {
|
||||
if name == "__subpackages__" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return !isAncestor("vendor", pkg)
|
||||
}
|
||||
|
||||
func splitRule(ctx BaseModuleContext, ruleExpression string, currentPkg string) (bool, string, string) {
|
||||
// Make sure that the rule is of the correct format.
|
||||
matches := visibilityRuleRegexp.FindStringSubmatch(ruleExpression)
|
||||
if ruleExpression == "" || matches == nil {
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
// Extract the package and name.
|
||||
pkg := matches[1]
|
||||
name := matches[2]
|
||||
|
||||
// Normalize the short hands
|
||||
if pkg == "" {
|
||||
pkg = currentPkg
|
||||
}
|
||||
if name == "" {
|
||||
name = "__pkg__"
|
||||
}
|
||||
|
||||
return true, pkg, name
|
||||
}
|
||||
|
||||
func visibilityRuleEnforcer(ctx TopDownMutatorContext) {
|
||||
_, ok := ctx.Module().(Module)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
qualified := createQualifiedModuleName(ctx)
|
||||
|
||||
moduleToVisibilityRule := moduleToVisibilityRuleMap(ctx)
|
||||
|
||||
// Visit all the dependencies making sure that this module has access to them all.
|
||||
ctx.VisitDirectDeps(func(dep Module) {
|
||||
depName := ctx.OtherModuleName(dep)
|
||||
depDir := ctx.OtherModuleDir(dep)
|
||||
depQualified := qualifiedModuleName{depDir, depName}
|
||||
|
||||
// Targets are always visible to other targets in their own package.
|
||||
if depQualified.pkg == qualified.pkg {
|
||||
return
|
||||
}
|
||||
|
||||
rule, ok := moduleToVisibilityRule.Load(depQualified)
|
||||
if ok {
|
||||
if !rule.(compositeRule).matches(qualified) {
|
||||
ctx.ModuleErrorf(
|
||||
"depends on %s which is not visible to this module; %s is only visible to %s",
|
||||
depQualified, depQualified, rule)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func createQualifiedModuleName(ctx BaseModuleContext) qualifiedModuleName {
|
||||
moduleName := ctx.ModuleName()
|
||||
dir := ctx.ModuleDir()
|
||||
qualified := qualifiedModuleName{dir, moduleName}
|
||||
return qualified
|
||||
}
|
|
@ -0,0 +1,474 @@
|
|||
package android
|
||||
|
||||
import (
|
||||
"github.com/google/blueprint"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var visibilityTests = []struct {
|
||||
name string
|
||||
fs map[string][]byte
|
||||
expectedErrors []string
|
||||
}{
|
||||
{
|
||||
name: "invalid visibility: empty list",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: [],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{`visibility: must contain at least one visibility rule`},
|
||||
},
|
||||
{
|
||||
name: "invalid visibility: empty rule",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: [""],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{`visibility: invalid visibility pattern ""`},
|
||||
},
|
||||
{
|
||||
name: "invalid visibility: unqualified",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["target"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{`visibility: invalid visibility pattern "target"`},
|
||||
},
|
||||
{
|
||||
name: "invalid visibility: empty namespace",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{`visibility: invalid visibility pattern "//"`},
|
||||
},
|
||||
{
|
||||
name: "invalid visibility: empty module",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: [":"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{`visibility: invalid visibility pattern ":"`},
|
||||
},
|
||||
{
|
||||
name: "invalid visibility: empty namespace and module",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//:"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{`visibility: invalid visibility pattern "//:"`},
|
||||
},
|
||||
{
|
||||
name: "//visibility:unknown",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//visibility:unknown"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{`unrecognized visibility rule "//visibility:unknown"`},
|
||||
},
|
||||
{
|
||||
name: "//visibility:public mixed",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//visibility:public", "//namespace"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libother",
|
||||
visibility: ["//visibility:private", "//namespace"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{
|
||||
`module "libother" variant "android_common": visibility: cannot mix "//visibility:private"` +
|
||||
` with any other visibility rules`,
|
||||
`module "libexample" variant "android_common": visibility: cannot mix` +
|
||||
` "//visibility:public" with any other visibility rules`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "//visibility:legacy_public",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//visibility:legacy_public"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{
|
||||
`module "libexample" variant "android_common": visibility: //visibility:legacy_public must` +
|
||||
` not be used`,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that //visibility:public will allow the module to be referenced from anywhere, e.g.
|
||||
// the current directory, a nested directory and a directory in a separate tree.
|
||||
name: "//visibility:public",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//visibility:public"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnested",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"other/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libother",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that //visibility:public will allow the module to be referenced from anywhere, e.g.
|
||||
// the current directory, a nested directory and a directory in a separate tree.
|
||||
name: "//visibility:public",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//visibility:public"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnested",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"other/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libother",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that //visibility:private allows the module to be referenced from the current
|
||||
// directory only.
|
||||
name: "//visibility:private",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//visibility:private"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnested",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{
|
||||
`module "libnested" variant "android_common": depends on //top:libexample which is not` +
|
||||
` visible to this module; //top:libexample is only visible to \[//top:__pkg__\]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that :__pkg__ allows the module to be referenced from the current directory only.
|
||||
name: ":__pkg__",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: [":__pkg__"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnested",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{
|
||||
`module "libnested" variant "android_common": depends on //top:libexample which is not` +
|
||||
` visible to this module; //top:libexample is only visible to \[//top:__pkg__\]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that //top/nested allows the module to be referenced from the current directory and
|
||||
// the top/nested directory only, not a subdirectory of top/nested and not peak directory.
|
||||
name: "//top/nested",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//top/nested"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnested",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/again/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnestedagain",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"peak/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libother",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{
|
||||
`module "libother" variant "android_common": depends on //top:libexample which is not` +
|
||||
` visible to this module; //top:libexample is only visible to \[//top/nested:__pkg__\]`,
|
||||
`module "libnestedagain" variant "android_common": depends on //top:libexample which is not` +
|
||||
` visible to this module; //top:libexample is only visible to \[//top/nested:__pkg__\]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that :__subpackages__ allows the module to be referenced from the current directory
|
||||
// and sub directories but nowhere else.
|
||||
name: ":__subpackages__",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: [":__subpackages__"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnested",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"peak/other/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libother",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{
|
||||
`module "libother" variant "android_common": depends on //top:libexample which is not` +
|
||||
` visible to this module; //top:libexample is only visible to \[//top:__subpackages__\]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that //top/nested:__subpackages__ allows the module to be referenced from the current
|
||||
// directory and sub directories but nowhere else.
|
||||
name: "//top/nested:__subpackages__",
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//top/nested:__subpackages__", "//other"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnested",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/other/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libother",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{
|
||||
`module "libother" variant "android_common": depends on //top:libexample which is not` +
|
||||
` visible to this module; //top:libexample is only visible to` +
|
||||
` \[//top/nested:__subpackages__, //other:__pkg__\]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that ["//top/nested", "//peak:__subpackages"] allows the module to be referenced from
|
||||
// the current directory, top/nested and peak and all its subpackages.
|
||||
name: `["//top/nested", "//peak:__subpackages__"]`,
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//top/nested", "//peak:__subpackages__"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"top/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libnested",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
"peak/other/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libother",
|
||||
deps: ["libexample"],
|
||||
}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
// Verify that //vendor... cannot be used outside vendor apart from //vendor:__subpackages__
|
||||
name: `//vendor`,
|
||||
fs: map[string][]byte{
|
||||
"top/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libexample",
|
||||
visibility: ["//vendor:__subpackages__"],
|
||||
}
|
||||
|
||||
mock_library {
|
||||
name: "libsamepackage",
|
||||
visibility: ["//vendor/apps/AcmeSettings"],
|
||||
}`),
|
||||
"vendor/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libvendorexample",
|
||||
deps: ["libexample"],
|
||||
visibility: ["//vendor/nested"],
|
||||
}`),
|
||||
"vendor/nested/Blueprints": []byte(`
|
||||
mock_library {
|
||||
name: "libvendornested",
|
||||
deps: ["libexample", "libvendorexample"],
|
||||
}`),
|
||||
},
|
||||
expectedErrors: []string{
|
||||
`module "libsamepackage" variant "android_common": visibility: "//vendor/apps/AcmeSettings"` +
|
||||
` is not allowed. Packages outside //vendor cannot make themselves visible to specific` +
|
||||
` targets within //vendor, they can only use //vendor:__subpackages__.`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestVisibility(t *testing.T) {
|
||||
buildDir, err := ioutil.TempDir("", "soong_neverallow_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(buildDir)
|
||||
|
||||
for _, test := range visibilityTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, errs := testVisibility(buildDir, test.fs)
|
||||
|
||||
expectedErrors := test.expectedErrors
|
||||
if expectedErrors == nil {
|
||||
FailIfErrored(t, errs)
|
||||
} else {
|
||||
for _, expectedError := range expectedErrors {
|
||||
FailIfNoMatchingErrors(t, expectedError, errs)
|
||||
}
|
||||
if len(errs) > len(expectedErrors) {
|
||||
t.Errorf("additional errors found, expected %d, found %d", len(expectedErrors), len(errs))
|
||||
for i, expectedError := range expectedErrors {
|
||||
t.Errorf("expectedErrors[%d] = %s", i, expectedError)
|
||||
}
|
||||
for i, err := range errs {
|
||||
t.Errorf("errs[%d] = %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testVisibility(buildDir string, fs map[string][]byte) (*TestContext, []error) {
|
||||
|
||||
// Create a new config per test as visibility information is stored in the config.
|
||||
config := TestArchConfig(buildDir, nil)
|
||||
|
||||
ctx := NewTestArchContext()
|
||||
ctx.RegisterModuleType("mock_library", ModuleFactoryAdaptor(newMockLibraryModule))
|
||||
ctx.PreDepsMutators(registerVisibilityRuleGatherer)
|
||||
ctx.PostDepsMutators(registerVisibilityRuleEnforcer)
|
||||
ctx.Register()
|
||||
|
||||
ctx.MockFileSystem(fs)
|
||||
|
||||
_, errs := ctx.ParseBlueprintsFiles(".")
|
||||
if len(errs) > 0 {
|
||||
return ctx, errs
|
||||
}
|
||||
|
||||
_, errs = ctx.PrepareBuildActions(config)
|
||||
return ctx, errs
|
||||
}
|
||||
|
||||
type mockLibraryProperties struct {
|
||||
Deps []string
|
||||
}
|
||||
|
||||
type mockLibraryModule struct {
|
||||
ModuleBase
|
||||
properties mockLibraryProperties
|
||||
}
|
||||
|
||||
func newMockLibraryModule() Module {
|
||||
m := &mockLibraryModule{}
|
||||
m.AddProperties(&m.properties)
|
||||
InitAndroidArchModule(m, HostAndDeviceSupported, MultilibCommon)
|
||||
return m
|
||||
}
|
||||
|
||||
type dependencyTag struct {
|
||||
blueprint.BaseDependencyTag
|
||||
name string
|
||||
}
|
||||
|
||||
func (j *mockLibraryModule) DepsMutator(ctx BottomUpMutatorContext) {
|
||||
ctx.AddVariationDependencies(nil, dependencyTag{name: "mockdeps"}, j.properties.Deps...)
|
||||
}
|
||||
|
||||
func (p *mockLibraryModule) GenerateAndroidBuildActions(ModuleContext) {
|
||||
}
|
Loading…
Reference in New Issue