Allow default visibility to be set per package
Adds a package module type with a default_visibility property. The package module type can only be specified once per package. Bug: 133290645 Test: m droid Change-Id: Ibb2fb499c9ea88ecaa662d3cd2cbde478e4b9a4b
This commit is contained in:
@@ -58,6 +58,7 @@ bootstrap_go_package {
|
|||||||
"android/notices.go",
|
"android/notices.go",
|
||||||
"android/onceper.go",
|
"android/onceper.go",
|
||||||
"android/override_module.go",
|
"android/override_module.go",
|
||||||
|
"android/package.go",
|
||||||
"android/package_ctx.go",
|
"android/package_ctx.go",
|
||||||
"android/path_properties.go",
|
"android/path_properties.go",
|
||||||
"android/paths.go",
|
"android/paths.go",
|
||||||
@@ -88,6 +89,7 @@ bootstrap_go_package {
|
|||||||
"android/namespace_test.go",
|
"android/namespace_test.go",
|
||||||
"android/neverallow_test.go",
|
"android/neverallow_test.go",
|
||||||
"android/onceper_test.go",
|
"android/onceper_test.go",
|
||||||
|
"android/package_test.go",
|
||||||
"android/path_properties_test.go",
|
"android/path_properties_test.go",
|
||||||
"android/paths_test.go",
|
"android/paths_test.go",
|
||||||
"android/prebuilt_test.go",
|
"android/prebuilt_test.go",
|
||||||
|
42
README.md
42
README.md
@@ -133,6 +133,25 @@ directory) there are two packages, `my/app`, and the subpackage `my/app/tests`.
|
|||||||
|
|
||||||
This is based on the Bazel package concept.
|
This is based on the Bazel package concept.
|
||||||
|
|
||||||
|
The `package` module type allows information to be specified about a package. Only a single
|
||||||
|
`package` module can be specified per package and in the case where there are multiple `.bp` files
|
||||||
|
in the same package directory it is highly recommended that the `package` module (if required) is
|
||||||
|
specified in the `Android.bp` file.
|
||||||
|
|
||||||
|
Unlike most module type `package` does not have a `name` property. Instead the name is set to the
|
||||||
|
name of the package, e.g. if the package is in `top/intermediate/package` then the package name is
|
||||||
|
`//top/intermediate/package`.
|
||||||
|
|
||||||
|
E.g. The following will set the default visibility for all the modules defined in the package
|
||||||
|
(irrespective of whether they are in the same `.bp` file as the `package` module) to be visible to
|
||||||
|
all the subpackages by default.
|
||||||
|
|
||||||
|
```
|
||||||
|
package {
|
||||||
|
default_visibility: [":__subpackages"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Name resolution
|
### Name resolution
|
||||||
|
|
||||||
Soong provides the ability for modules in different directories to specify
|
Soong provides the ability for modules in different directories to specify
|
||||||
@@ -191,13 +210,13 @@ this module. For example, `//project:rule`, `//project/library:lib` or
|
|||||||
`//independent:evil`)
|
`//independent:evil`)
|
||||||
* `["//project"]`: This is shorthand for `["//project:__pkg__"]`
|
* `["//project"]`: This is shorthand for `["//project:__pkg__"]`
|
||||||
* `[":__subpackages__"]`: This is shorthand for `["//project:__subpackages__"]`
|
* `[":__subpackages__"]`: This is shorthand for `["//project:__subpackages__"]`
|
||||||
where `//project` is the module's package. e.g. using `[":__subpackages__"]` in
|
where `//project` is the module's package, e.g. using `[":__subpackages__"]` in
|
||||||
`packages/apps/Settings/Android.bp` is equivalent to
|
`packages/apps/Settings/Android.bp` is equivalent to
|
||||||
`//packages/apps/Settings:__subpackages__`.
|
`//packages/apps/Settings:__subpackages__`.
|
||||||
* `["//visibility:legacy_public"]`: The default visibility, behaves as
|
* `["//visibility:legacy_public"]`: The default visibility, behaves as
|
||||||
`//visibility:public` for now. It is an error if it is used in a module.
|
`//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
|
The visibility rules of `//visibility:public` and `//visibility:private` cannot
|
||||||
be combined with any other visibility specifications, except
|
be combined with any other visibility specifications, except
|
||||||
`//visibility:public` is allowed to override visibility specifications imported
|
`//visibility:public` is allowed to override visibility specifications imported
|
||||||
through the `defaults` property.
|
through the `defaults` property.
|
||||||
@@ -207,13 +226,18 @@ 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
|
say `vendor/google`, instead it must make itself visible to all packages within
|
||||||
`vendor/` using `//vendor:__subpackages__`.
|
`vendor/` using `//vendor:__subpackages__`.
|
||||||
|
|
||||||
If a module does not specify the `visibility` property the module is
|
If a module does not specify the `visibility` property then it uses the
|
||||||
`//visibility:legacy_public`. Once the build has been completely switched over to
|
`default_visibility` property of the `package` module in the module's package.
|
||||||
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
|
If the `default_visibility` property is not set for the module's package then
|
||||||
a `visibility` property will be updated to have
|
the module uses `//visibility:legacy_public`.
|
||||||
`visibility = [//visibility:legacy_public]` added. It will then be the owner's
|
|
||||||
responsibility to replace that with a more appropriate visibility.
|
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 packages that do not currently specify a `default_visibility`
|
||||||
|
property will be updated to have
|
||||||
|
`default_visibility = [//visibility:legacy_public]` added. It will then be the
|
||||||
|
owner's responsibility to replace that with a more appropriate visibility.
|
||||||
|
|
||||||
### Formatter
|
### Formatter
|
||||||
|
|
||||||
|
@@ -202,6 +202,42 @@ type Module interface {
|
|||||||
BuildParamsForTests() []BuildParams
|
BuildParamsForTests() []BuildParams
|
||||||
RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
|
RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
|
||||||
VariablesForTests() map[string]string
|
VariablesForTests() map[string]string
|
||||||
|
|
||||||
|
// Get the qualified module id for this module.
|
||||||
|
qualifiedModuleId(ctx BaseModuleContext) qualifiedModuleName
|
||||||
|
|
||||||
|
// Get information about the properties that can contain visibility rules.
|
||||||
|
visibilityProperties() []visibilityProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, empty string if package.
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q qualifiedModuleName) String() string {
|
||||||
|
if q.name == "" {
|
||||||
|
return "//" + q.pkg
|
||||||
|
}
|
||||||
|
return "//" + q.pkg + ":" + q.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the id for the package containing this module.
|
||||||
|
func (q qualifiedModuleName) getContainingPackageId() qualifiedModuleName {
|
||||||
|
pkg := q.pkg
|
||||||
|
if q.name == "" {
|
||||||
|
panic(fmt.Errorf("Cannot get containing package id of package module %s", pkg))
|
||||||
|
}
|
||||||
|
return newPackageId(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPackageId(pkg string) qualifiedModuleName {
|
||||||
|
// A qualified id for a package module has no name.
|
||||||
|
return qualifiedModuleName{pkg: pkg, name: ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
type nameProperties struct {
|
type nameProperties struct {
|
||||||
@@ -236,6 +272,13 @@ type commonProperties struct {
|
|||||||
// //packages/apps/Settings:__subpackages__.
|
// //packages/apps/Settings:__subpackages__.
|
||||||
// ["//visibility:legacy_public"]: The default visibility, behaves as //visibility:public
|
// ["//visibility:legacy_public"]: The default visibility, behaves as //visibility:public
|
||||||
// for now. It is an error if it is used in a module.
|
// for now. It is an error if it is used in a module.
|
||||||
|
//
|
||||||
|
// If a module does not specify the `visibility` property then it uses the
|
||||||
|
// `default_visibility` property of the `package` module in the module's package.
|
||||||
|
//
|
||||||
|
// If the `default_visibility` property is not set for the module's package then
|
||||||
|
// the module uses `//visibility:legacy_public`.
|
||||||
|
//
|
||||||
// See https://android.googlesource.com/platform/build/soong/+/master/README.md#visibility for
|
// See https://android.googlesource.com/platform/build/soong/+/master/README.md#visibility for
|
||||||
// more details.
|
// more details.
|
||||||
Visibility []string
|
Visibility []string
|
||||||
@@ -571,6 +614,18 @@ func (m *ModuleBase) base() *ModuleBase {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ModuleBase) qualifiedModuleId(ctx BaseModuleContext) qualifiedModuleName {
|
||||||
|
return qualifiedModuleName{pkg: ctx.ModuleDir(), name: ctx.ModuleName()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModuleBase) visibilityProperties() []visibilityProperty {
|
||||||
|
return []visibilityProperty{
|
||||||
|
newVisibilityProperty("visibility", func() []string {
|
||||||
|
return m.base().commonProperties.Visibility
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *ModuleBase) SetTarget(target Target, multiTargets []Target, primary bool) {
|
func (m *ModuleBase) SetTarget(target Target, multiTargets []Target, primary bool) {
|
||||||
m.commonProperties.CompileTarget = target
|
m.commonProperties.CompileTarget = target
|
||||||
m.commonProperties.CompileMultiTargets = multiTargets
|
m.commonProperties.CompileMultiTargets = multiTargets
|
||||||
|
@@ -75,6 +75,8 @@ type RegisterMutatorFunc func(RegisterMutatorsContext)
|
|||||||
var preArch = []RegisterMutatorFunc{
|
var preArch = []RegisterMutatorFunc{
|
||||||
registerLoadHookMutator,
|
registerLoadHookMutator,
|
||||||
RegisterNamespaceMutator,
|
RegisterNamespaceMutator,
|
||||||
|
// Rename package module types.
|
||||||
|
registerPackageRenamer,
|
||||||
RegisterPrebuiltsPreArchMutators,
|
RegisterPrebuiltsPreArchMutators,
|
||||||
registerVisibilityRuleChecker,
|
registerVisibilityRuleChecker,
|
||||||
RegisterDefaultsPreArchMutators,
|
RegisterDefaultsPreArchMutators,
|
||||||
|
194
android/package.go
Normal file
194
android/package.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// 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"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/google/blueprint"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterModuleType("package", PackageFactory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The information maintained about each package.
|
||||||
|
type packageInfo struct {
|
||||||
|
// The module from which this information was populated. If `duplicated` = true then this is the
|
||||||
|
// module that has been renamed and must be used to report errors.
|
||||||
|
module *packageModule
|
||||||
|
|
||||||
|
// If true this indicates that there are two package statements in the same package which is not
|
||||||
|
// allowed and will cause the build to fail. This flag is set by packageRenamer and checked in
|
||||||
|
// packageErrorReporter
|
||||||
|
duplicated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type packageProperties struct {
|
||||||
|
Name string `blueprint:"mutated"`
|
||||||
|
|
||||||
|
// Specifies the default visibility for all modules defined in this package.
|
||||||
|
Default_visibility []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type packageModule struct {
|
||||||
|
ModuleBase
|
||||||
|
|
||||||
|
properties packageProperties
|
||||||
|
packageInfo *packageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packageModule) GenerateAndroidBuildActions(ModuleContext) {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packageModule) GenerateBuildActions(ctx blueprint.ModuleContext) {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packageModule) qualifiedModuleId(ctx BaseModuleContext) qualifiedModuleName {
|
||||||
|
// Override to create a package id.
|
||||||
|
return newPackageId(ctx.ModuleDir())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override to ensure that the default_visibility rules are checked by the visibility module during
|
||||||
|
// its checking phase.
|
||||||
|
func (p *packageModule) visibilityProperties() []visibilityProperty {
|
||||||
|
return []visibilityProperty{
|
||||||
|
newVisibilityProperty("default_visibility", func() []string {
|
||||||
|
return p.properties.Default_visibility
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packageModule) Name() string {
|
||||||
|
return p.properties.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packageModule) setName(name string) {
|
||||||
|
p.properties.Name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter to ensure package modules are created with a unique name within whatever namespace they
|
||||||
|
// belong.
|
||||||
|
var packageCount uint32 = 0
|
||||||
|
|
||||||
|
func PackageFactory() Module {
|
||||||
|
module := &packageModule{}
|
||||||
|
|
||||||
|
// Get a unique if for the package. Has to be done atomically as the creation of the modules are
|
||||||
|
// done in parallel.
|
||||||
|
id := atomic.AddUint32(&packageCount, 1)
|
||||||
|
name := fmt.Sprintf("soong_package_%d", id)
|
||||||
|
|
||||||
|
module.properties.Name = name
|
||||||
|
|
||||||
|
module.AddProperties(&module.properties)
|
||||||
|
return module
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registers the function that renames the packages.
|
||||||
|
func registerPackageRenamer(ctx RegisterMutatorsContext) {
|
||||||
|
ctx.BottomUp("packageRenamer", packageRenamer).Parallel()
|
||||||
|
ctx.BottomUp("packageErrorReporter", packageErrorReporter).Parallel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renames the package to match the package directory.
|
||||||
|
//
|
||||||
|
// This also creates a PackageInfo object for each package and uses that to detect and remember
|
||||||
|
// duplicates for later error reporting.
|
||||||
|
func packageRenamer(ctx BottomUpMutatorContext) {
|
||||||
|
m, ok := ctx.Module().(*packageModule)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
packageName := "//" + ctx.ModuleDir()
|
||||||
|
|
||||||
|
pi := newPackageInfo(ctx, packageName, m)
|
||||||
|
if pi.module != m {
|
||||||
|
// Remember that the package was duplicated but do not rename as that will cause an error to
|
||||||
|
// be logged with the generated name. Similarly, reporting the error here will use the generated
|
||||||
|
// name as renames are only processed after this phase.
|
||||||
|
pi.duplicated = true
|
||||||
|
} else {
|
||||||
|
// This is the first package module in this package so rename it to match the package name.
|
||||||
|
m.setName(packageName)
|
||||||
|
ctx.Rename(packageName)
|
||||||
|
|
||||||
|
// Store a package info reference in the module.
|
||||||
|
m.packageInfo = pi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs any deferred errors.
|
||||||
|
func packageErrorReporter(ctx BottomUpMutatorContext) {
|
||||||
|
m, ok := ctx.Module().(*packageModule)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
packageDir := ctx.ModuleDir()
|
||||||
|
packageName := "//" + packageDir
|
||||||
|
|
||||||
|
// Get the PackageInfo for the package. Should have been populated in the packageRenamer phase.
|
||||||
|
pi := findPackageInfo(ctx, packageName)
|
||||||
|
if pi == nil {
|
||||||
|
ctx.ModuleErrorf("internal error, expected package info to be present for package '%s'",
|
||||||
|
packageName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pi.module != m {
|
||||||
|
// The package module has been duplicated but this is not the module that has been renamed so
|
||||||
|
// ignore it. An error will be logged for the renamed module which will ensure that the error
|
||||||
|
// message uses the correct name.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see whether there are duplicate package modules in the package.
|
||||||
|
if pi.duplicated {
|
||||||
|
ctx.ModuleErrorf("package {...} specified multiple times")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultPackageInfoKey string
|
||||||
|
|
||||||
|
func newPackageInfo(
|
||||||
|
ctx BaseModuleContext, packageName string, module *packageModule) *packageInfo {
|
||||||
|
key := NewCustomOnceKey(defaultPackageInfoKey(packageName))
|
||||||
|
|
||||||
|
return ctx.Config().Once(key, func() interface{} {
|
||||||
|
return &packageInfo{module: module}
|
||||||
|
}).(*packageInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the PackageInfo for the package name (starts with //, no trailing /), is nil if no package
|
||||||
|
// module type was specified.
|
||||||
|
func findPackageInfo(ctx BaseModuleContext, packageName string) *packageInfo {
|
||||||
|
key := NewCustomOnceKey(defaultPackageInfoKey(packageName))
|
||||||
|
|
||||||
|
pi := ctx.Config().Once(key, func() interface{} {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if pi == nil {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return pi.(*packageInfo)
|
||||||
|
}
|
||||||
|
}
|
111
android/package_test.go
Normal file
111
android/package_test.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package android
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var packageTests = []struct {
|
||||||
|
name string
|
||||||
|
fs map[string][]byte
|
||||||
|
expectedErrors []string
|
||||||
|
}{
|
||||||
|
// Package default_visibility handling is tested in visibility_test.go
|
||||||
|
{
|
||||||
|
name: "package must not accept visibility and name properties",
|
||||||
|
fs: map[string][]byte{
|
||||||
|
"top/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
name: "package",
|
||||||
|
visibility: ["//visibility:private"],
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
expectedErrors: []string{
|
||||||
|
`top/Blueprints:3:10: mutated field name cannot be set in a Blueprint file`,
|
||||||
|
`top/Blueprints:4:16: unrecognized property "visibility"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple packages in separate directories",
|
||||||
|
fs: map[string][]byte{
|
||||||
|
"top/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
}`),
|
||||||
|
"other/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
}`),
|
||||||
|
"other/nested/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "package must not be specified more than once per package",
|
||||||
|
fs: map[string][]byte{
|
||||||
|
"top/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
default_visibility: ["//visibility:private"],
|
||||||
|
}
|
||||||
|
|
||||||
|
package {
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
expectedErrors: []string{
|
||||||
|
`module "//top": package {...} specified multiple times`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackage(t *testing.T) {
|
||||||
|
buildDir, err := ioutil.TempDir("", "soong_package_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(buildDir)
|
||||||
|
|
||||||
|
for _, test := range packageTests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
_, errs := testPackage(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 testPackage(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("package", ModuleFactoryAdaptor(PackageFactory))
|
||||||
|
ctx.PreArchMutators(registerPackageRenamer)
|
||||||
|
ctx.Register()
|
||||||
|
|
||||||
|
ctx.MockFileSystem(fs)
|
||||||
|
|
||||||
|
_, errs := ctx.ParseBlueprintsFiles(".")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return ctx, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
_, errs = ctx.PrepareBuildActions(config)
|
||||||
|
return ctx, errs
|
||||||
|
}
|
@@ -23,14 +23,21 @@ import (
|
|||||||
|
|
||||||
// Enforces visibility rules between modules.
|
// Enforces visibility rules between modules.
|
||||||
//
|
//
|
||||||
// Two stage process:
|
// Multi stage process:
|
||||||
// * First stage works bottom up to extract visibility information from the modules, parse it,
|
// * First stage works bottom up, before defaults expansion, to check the syntax of the visibility
|
||||||
|
// rules that have been specified.
|
||||||
|
//
|
||||||
|
// * Second stage works bottom up to extract the package info for each package and store them in a
|
||||||
|
// map by package name. See package.go for functionality for this.
|
||||||
|
//
|
||||||
|
// * Third 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
|
// 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
|
// 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
|
// 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.
|
// and so can be run in parallel. If a module has no visibility specified then it uses the
|
||||||
|
// default package visibility if specified.
|
||||||
//
|
//
|
||||||
// * Second stage works top down and iterates over all the deps for each module. If the dep is in
|
// * Fourth 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
|
// 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
|
// 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
|
// publicly visible. Otherwise, it calls the visibility rule to check that the module can see
|
||||||
@@ -48,19 +55,6 @@ const (
|
|||||||
|
|
||||||
var visibilityRuleRegexp = regexp.MustCompile(visibilityRulePattern)
|
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
|
// 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.
|
// to, i.e. which other modules can depend on the rule's module.
|
||||||
type visibilityRule interface {
|
type visibilityRule interface {
|
||||||
@@ -71,6 +65,32 @@ type visibilityRule interface {
|
|||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Describes the properties provided by a module that contain visibility rules.
|
||||||
|
type visibilityPropertyImpl struct {
|
||||||
|
name string
|
||||||
|
stringsGetter func() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type visibilityProperty interface {
|
||||||
|
getName() string
|
||||||
|
getStrings() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVisibilityProperty(name string, stringsGetter func() []string) visibilityProperty {
|
||||||
|
return visibilityPropertyImpl{
|
||||||
|
name: name,
|
||||||
|
stringsGetter: stringsGetter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p visibilityPropertyImpl) getName() string {
|
||||||
|
return p.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p visibilityPropertyImpl) getStrings() []string {
|
||||||
|
return p.stringsGetter()
|
||||||
|
}
|
||||||
|
|
||||||
// A compositeRule is a visibility rule composed from a list of atomic visibility rules.
|
// A compositeRule is a visibility rule composed from a list of atomic visibility rules.
|
||||||
//
|
//
|
||||||
// The list corresponds to the list of strings in the visibility property after defaults expansion.
|
// The list corresponds to the list of strings in the visibility property after defaults expansion.
|
||||||
@@ -96,9 +116,9 @@ func (c compositeRule) matches(m qualifiedModuleName) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r compositeRule) String() string {
|
func (c compositeRule) String() string {
|
||||||
s := make([]string, 0, len(r))
|
s := make([]string, 0, len(c))
|
||||||
for _, r := range r {
|
for _, r := range c {
|
||||||
s = append(s, r.String())
|
s = append(s, r.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,9 +193,12 @@ func registerVisibilityRuleChecker(ctx RegisterMutatorsContext) {
|
|||||||
ctx.BottomUp("visibilityRuleChecker", visibilityRuleChecker).Parallel()
|
ctx.BottomUp("visibilityRuleChecker", visibilityRuleChecker).Parallel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Registers the function that gathers the visibility rules for each module.
|
||||||
|
//
|
||||||
// Visibility is not dependent on arch so this must be registered before the arch phase to avoid
|
// 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. This goes after defaults expansion to gather
|
// having to process multiple variants for each module. This goes after defaults expansion to gather
|
||||||
// the complete visibility lists from flat lists.
|
// the complete visibility lists from flat lists and after the package info is gathered to ensure
|
||||||
|
// that default_visibility is available.
|
||||||
func registerVisibilityRuleGatherer(ctx RegisterMutatorsContext) {
|
func registerVisibilityRuleGatherer(ctx RegisterMutatorsContext) {
|
||||||
ctx.BottomUp("visibilityRuleGatherer", visibilityRuleGatherer).Parallel()
|
ctx.BottomUp("visibilityRuleGatherer", visibilityRuleGatherer).Parallel()
|
||||||
}
|
}
|
||||||
@@ -193,33 +216,36 @@ func visibilityRuleChecker(ctx BottomUpMutatorContext) {
|
|||||||
for _, props := range d.properties() {
|
for _, props := range d.properties() {
|
||||||
if cp, ok := props.(*commonProperties); ok {
|
if cp, ok := props.(*commonProperties); ok {
|
||||||
if visibility := cp.Visibility; visibility != nil {
|
if visibility := cp.Visibility; visibility != nil {
|
||||||
checkRules(ctx, qualified.pkg, visibility)
|
checkRules(ctx, qualified.pkg, "visibility", visibility)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if m, ok := ctx.Module().(Module); ok {
|
} else if m, ok := ctx.Module().(Module); ok {
|
||||||
if visibility := m.base().commonProperties.Visibility; visibility != nil {
|
visibilityProperties := m.visibilityProperties()
|
||||||
checkRules(ctx, qualified.pkg, visibility)
|
for _, p := range visibilityProperties {
|
||||||
|
if visibility := p.getStrings(); visibility != nil {
|
||||||
|
checkRules(ctx, qualified.pkg, p.getName(), visibility)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkRules(ctx BottomUpMutatorContext, currentPkg string, visibility []string) {
|
func checkRules(ctx BaseModuleContext, currentPkg, property string, visibility []string) {
|
||||||
ruleCount := len(visibility)
|
ruleCount := len(visibility)
|
||||||
if ruleCount == 0 {
|
if ruleCount == 0 {
|
||||||
// This prohibits an empty list as its meaning is unclear, e.g. it could mean no visibility and
|
// 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
|
// it could mean public visibility. Requiring at least one rule makes the owner's intent
|
||||||
// clearer.
|
// clearer.
|
||||||
ctx.PropertyErrorf("visibility", "must contain at least one visibility rule")
|
ctx.PropertyErrorf(property, "must contain at least one visibility rule")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range visibility {
|
for _, v := range visibility {
|
||||||
ok, pkg, name := splitRule(ctx, v, currentPkg)
|
ok, pkg, name := splitRule(v, currentPkg)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Visibility rule is invalid so ignore it. Keep going rather than aborting straight away to
|
// Visibility rule is invalid so ignore it. Keep going rather than aborting straight away to
|
||||||
// ensure all the rules on this module are checked.
|
// ensure all the rules on this module are checked.
|
||||||
ctx.PropertyErrorf("visibility",
|
ctx.PropertyErrorf(property,
|
||||||
"invalid visibility pattern %q must match"+
|
"invalid visibility pattern %q must match"+
|
||||||
" //<package>:<module>, //<package> or :<module>",
|
" //<package>:<module>, //<package> or :<module>",
|
||||||
v)
|
v)
|
||||||
@@ -230,14 +256,14 @@ func checkRules(ctx BottomUpMutatorContext, currentPkg string, visibility []stri
|
|||||||
switch name {
|
switch name {
|
||||||
case "private", "public":
|
case "private", "public":
|
||||||
case "legacy_public":
|
case "legacy_public":
|
||||||
ctx.PropertyErrorf("visibility", "//visibility:legacy_public must not be used")
|
ctx.PropertyErrorf(property, "//visibility:legacy_public must not be used")
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
ctx.PropertyErrorf("visibility", "unrecognized visibility rule %q", v)
|
ctx.PropertyErrorf(property, "unrecognized visibility rule %q", v)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ruleCount != 1 {
|
if ruleCount != 1 {
|
||||||
ctx.PropertyErrorf("visibility", "cannot mix %q with any other visibility rules", v)
|
ctx.PropertyErrorf(property, "cannot mix %q with any other visibility rules", v)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +272,7 @@ func checkRules(ctx BottomUpMutatorContext, currentPkg string, visibility []stri
|
|||||||
// restrictions on the rules.
|
// restrictions on the rules.
|
||||||
if !isAncestor("vendor", currentPkg) {
|
if !isAncestor("vendor", currentPkg) {
|
||||||
if !isAllowedFromOutsideVendor(pkg, name) {
|
if !isAllowedFromOutsideVendor(pkg, name) {
|
||||||
ctx.PropertyErrorf("visibility",
|
ctx.PropertyErrorf(property,
|
||||||
"%q is not allowed. Packages outside //vendor cannot make themselves visible to specific"+
|
"%q is not allowed. Packages outside //vendor cannot make themselves visible to specific"+
|
||||||
" targets within //vendor, they can only use //vendor:__subpackages__.", v)
|
" targets within //vendor, they can only use //vendor:__subpackages__.", v)
|
||||||
continue
|
continue
|
||||||
@@ -265,23 +291,27 @@ func visibilityRuleGatherer(ctx BottomUpMutatorContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
qualified := createQualifiedModuleName(ctx)
|
qualifiedModuleId := m.qualifiedModuleId(ctx)
|
||||||
|
currentPkg := qualifiedModuleId.pkg
|
||||||
|
|
||||||
visibility := m.base().commonProperties.Visibility
|
// Parse all the properties into rules and store them.
|
||||||
if visibility != nil {
|
visibilityProperties := m.visibilityProperties()
|
||||||
rule := parseRules(ctx, qualified.pkg, visibility)
|
for _, p := range visibilityProperties {
|
||||||
|
if visibility := p.getStrings(); visibility != nil {
|
||||||
|
rule := parseRules(ctx, currentPkg, visibility)
|
||||||
if rule != nil {
|
if rule != nil {
|
||||||
moduleToVisibilityRuleMap(ctx).Store(qualified, rule)
|
moduleToVisibilityRuleMap(ctx).Store(qualifiedModuleId, rule)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRules(ctx BottomUpMutatorContext, currentPkg string, visibility []string) compositeRule {
|
func parseRules(ctx BaseModuleContext, currentPkg string, visibility []string) compositeRule {
|
||||||
rules := make(compositeRule, 0, len(visibility))
|
rules := make(compositeRule, 0, len(visibility))
|
||||||
hasPrivateRule := false
|
hasPrivateRule := false
|
||||||
hasNonPrivateRule := false
|
hasNonPrivateRule := false
|
||||||
for _, v := range visibility {
|
for _, v := range visibility {
|
||||||
ok, pkg, name := splitRule(ctx, v, currentPkg)
|
ok, pkg, name := splitRule(v, currentPkg)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -336,7 +366,7 @@ func isAllowedFromOutsideVendor(pkg string, name string) bool {
|
|||||||
return !isAncestor("vendor", pkg)
|
return !isAncestor("vendor", pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitRule(ctx BaseModuleContext, ruleExpression string, currentPkg string) (bool, string, string) {
|
func splitRule(ruleExpression string, currentPkg string) (bool, string, string) {
|
||||||
// Make sure that the rule is of the correct format.
|
// Make sure that the rule is of the correct format.
|
||||||
matches := visibilityRuleRegexp.FindStringSubmatch(ruleExpression)
|
matches := visibilityRuleRegexp.FindStringSubmatch(ruleExpression)
|
||||||
if ruleExpression == "" || matches == nil {
|
if ruleExpression == "" || matches == nil {
|
||||||
@@ -378,12 +408,20 @@ func visibilityRuleEnforcer(ctx TopDownMutatorContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rule, ok := moduleToVisibilityRule.Load(depQualified)
|
value, ok := moduleToVisibilityRule.Load(depQualified)
|
||||||
|
var rule compositeRule
|
||||||
if ok {
|
if ok {
|
||||||
if !rule.(compositeRule).matches(qualified) {
|
rule = value.(compositeRule)
|
||||||
ctx.ModuleErrorf("depends on %s which is not visible to this module", depQualified)
|
} else {
|
||||||
|
packageQualifiedId := depQualified.getContainingPackageId()
|
||||||
|
value, ok = moduleToVisibilityRule.Load(packageQualifiedId)
|
||||||
|
if ok {
|
||||||
|
rule = value.(compositeRule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rule != nil && !rule.matches(qualified) {
|
||||||
|
ctx.ModuleErrorf("depends on %s which is not visible to this module", depQualified)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -658,6 +658,109 @@ var visibilityTests = []struct {
|
|||||||
` visible to this module`,
|
` visible to this module`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Package default_visibility tests
|
||||||
|
{
|
||||||
|
name: "package default_visibility property is checked",
|
||||||
|
fs: map[string][]byte{
|
||||||
|
"top/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
default_visibility: ["//visibility:invalid"],
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
expectedErrors: []string{`default_visibility: unrecognized visibility rule "//visibility:invalid"`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// This test relies on the default visibility being legacy_public.
|
||||||
|
name: "package default_visibility property used when no visibility specified",
|
||||||
|
fs: map[string][]byte{
|
||||||
|
"top/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
default_visibility: ["//visibility:private"],
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_library {
|
||||||
|
name: "libexample",
|
||||||
|
}`),
|
||||||
|
"outsider/Blueprints": []byte(`
|
||||||
|
mock_library {
|
||||||
|
name: "liboutsider",
|
||||||
|
deps: ["libexample"],
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
expectedErrors: []string{
|
||||||
|
`module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
|
||||||
|
` visible to this module`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "package default_visibility public does not override visibility private",
|
||||||
|
fs: map[string][]byte{
|
||||||
|
"top/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
default_visibility: ["//visibility:public"],
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_library {
|
||||||
|
name: "libexample",
|
||||||
|
visibility: ["//visibility:private"],
|
||||||
|
}`),
|
||||||
|
"outsider/Blueprints": []byte(`
|
||||||
|
mock_library {
|
||||||
|
name: "liboutsider",
|
||||||
|
deps: ["libexample"],
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
expectedErrors: []string{
|
||||||
|
`module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
|
||||||
|
` visible to this module`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "package default_visibility private does not override visibility public",
|
||||||
|
fs: map[string][]byte{
|
||||||
|
"top/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
default_visibility: ["//visibility:private"],
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_library {
|
||||||
|
name: "libexample",
|
||||||
|
visibility: ["//visibility:public"],
|
||||||
|
}`),
|
||||||
|
"outsider/Blueprints": []byte(`
|
||||||
|
mock_library {
|
||||||
|
name: "liboutsider",
|
||||||
|
deps: ["libexample"],
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "package default_visibility :__subpackages__",
|
||||||
|
fs: map[string][]byte{
|
||||||
|
"top/Blueprints": []byte(`
|
||||||
|
package {
|
||||||
|
default_visibility: [":__subpackages__"],
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_library {
|
||||||
|
name: "libexample",
|
||||||
|
}`),
|
||||||
|
"top/nested/Blueprints": []byte(`
|
||||||
|
mock_library {
|
||||||
|
name: "libnested",
|
||||||
|
deps: ["libexample"],
|
||||||
|
}`),
|
||||||
|
"outsider/Blueprints": []byte(`
|
||||||
|
mock_library {
|
||||||
|
name: "liboutsider",
|
||||||
|
deps: ["libexample"],
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
expectedErrors: []string{
|
||||||
|
`module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
|
||||||
|
` visible to this module`,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVisibility(t *testing.T) {
|
func TestVisibility(t *testing.T) {
|
||||||
@@ -692,8 +795,10 @@ func testVisibility(buildDir string, fs map[string][]byte) (*TestContext, []erro
|
|||||||
config := TestArchConfig(buildDir, nil)
|
config := TestArchConfig(buildDir, nil)
|
||||||
|
|
||||||
ctx := NewTestArchContext()
|
ctx := NewTestArchContext()
|
||||||
|
ctx.RegisterModuleType("package", ModuleFactoryAdaptor(PackageFactory))
|
||||||
ctx.RegisterModuleType("mock_library", ModuleFactoryAdaptor(newMockLibraryModule))
|
ctx.RegisterModuleType("mock_library", ModuleFactoryAdaptor(newMockLibraryModule))
|
||||||
ctx.RegisterModuleType("mock_defaults", ModuleFactoryAdaptor(defaultsFactory))
|
ctx.RegisterModuleType("mock_defaults", ModuleFactoryAdaptor(defaultsFactory))
|
||||||
|
ctx.PreArchMutators(registerPackageRenamer)
|
||||||
ctx.PreArchMutators(registerVisibilityRuleChecker)
|
ctx.PreArchMutators(registerVisibilityRuleChecker)
|
||||||
ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
|
ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
|
||||||
ctx.PreArchMutators(registerVisibilityRuleGatherer)
|
ctx.PreArchMutators(registerVisibilityRuleGatherer)
|
||||||
|
Reference in New Issue
Block a user