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/testing.go",
|
||||||
"android/util.go",
|
"android/util.go",
|
||||||
"android/variable.go",
|
"android/variable.go",
|
||||||
|
"android/visibility.go",
|
||||||
"android/vts_config.go",
|
"android/vts_config.go",
|
||||||
"android/writedocs.go",
|
"android/writedocs.go",
|
||||||
|
|
||||||
|
@ -90,6 +91,7 @@ bootstrap_go_package {
|
||||||
"android/rule_builder_test.go",
|
"android/rule_builder_test.go",
|
||||||
"android/util_test.go",
|
"android/util_test.go",
|
||||||
"android/variable_test.go",
|
"android/variable_test.go",
|
||||||
|
"android/visibility_test.go",
|
||||||
"android/vts_config_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
|
### Name resolution
|
||||||
|
|
||||||
Soong provides the ability for modules in different directories to specify
|
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
|
built by the `m` command. After we have fully converted from Make to Soong, the
|
||||||
details of enabling namespaces could potentially change.
|
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
|
### Formatter
|
||||||
|
|
||||||
Soong includes a canonical formatter for blueprint files, similar to
|
Soong includes a canonical formatter for blueprint files, similar to
|
||||||
|
|
|
@ -210,6 +210,33 @@ type commonProperties struct {
|
||||||
// emit build rules for this module
|
// emit build rules for this module
|
||||||
Enabled *bool `android:"arch_variant"`
|
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
|
// 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
|
// 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
|
// 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,
|
RegisterPrebuiltsPreArchMutators,
|
||||||
RegisterDefaultsPreArchMutators,
|
RegisterDefaultsPreArchMutators,
|
||||||
RegisterOverridePreArchMutators,
|
RegisterOverridePreArchMutators,
|
||||||
|
registerVisibilityRuleGatherer,
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerArchMutator(ctx RegisterMutatorsContext) {
|
func registerArchMutator(ctx RegisterMutatorsContext) {
|
||||||
|
@ -92,6 +93,7 @@ var preDeps = []RegisterMutatorFunc{
|
||||||
var postDeps = []RegisterMutatorFunc{
|
var postDeps = []RegisterMutatorFunc{
|
||||||
registerPathDepsMutator,
|
registerPathDepsMutator,
|
||||||
RegisterPrebuiltsPostDepsMutators,
|
RegisterPrebuiltsPostDepsMutators,
|
||||||
|
registerVisibilityRuleEnforcer,
|
||||||
registerNeverallowMutator,
|
registerNeverallowMutator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +120,7 @@ type TopDownMutatorContext interface {
|
||||||
Module() Module
|
Module() Module
|
||||||
|
|
||||||
OtherModuleName(m blueprint.Module) string
|
OtherModuleName(m blueprint.Module) string
|
||||||
|
OtherModuleDir(m blueprint.Module) string
|
||||||
OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{})
|
OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{})
|
||||||
OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag
|
OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,6 @@ import (
|
||||||
"github.com/google/blueprint"
|
"github.com/google/blueprint"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file implements namespaces
|
|
||||||
const (
|
|
||||||
namespacePrefix = "//"
|
|
||||||
modulePrefix = ":"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterModuleType("soong_namespace", NamespaceFactory)
|
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
|
// parses a fully-qualified path (like "//namespace_path:module_name") into a namespace name and a
|
||||||
// module name
|
// module name
|
||||||
func (r *NameResolver) parseFullyQualifiedName(name string) (namespaceName string, moduleName string, ok bool) {
|
func (r *NameResolver) parseFullyQualifiedName(name string) (namespaceName string, moduleName string, ok bool) {
|
||||||
if !strings.HasPrefix(name, namespacePrefix) {
|
if !strings.HasPrefix(name, "//") {
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
name = strings.TrimPrefix(name, namespacePrefix)
|
name = strings.TrimPrefix(name, "//")
|
||||||
components := strings.Split(name, modulePrefix)
|
components := strings.Split(name, ":")
|
||||||
if len(components) != 2 {
|
if len(components) != 2 {
|
||||||
return "", "", false
|
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