Merge pull request #309 from colincross/providers

Add Providers to Blueprint
This commit is contained in:
colincross 2020-09-23 10:27:40 -07:00 committed by GitHub
commit 8a747a0864
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 786 additions and 2 deletions

View file

@ -17,6 +17,7 @@ bootstrap_go_package {
"ninja_strings.go", "ninja_strings.go",
"ninja_writer.go", "ninja_writer.go",
"package_ctx.go", "package_ctx.go",
"provider.go",
"scope.go", "scope.go",
"singleton_ctx.go", "singleton_ctx.go",
], ],
@ -26,6 +27,7 @@ bootstrap_go_package {
"module_ctx_test.go", "module_ctx_test.go",
"ninja_strings_test.go", "ninja_strings_test.go",
"ninja_writer_test.go", "ninja_writer_test.go",
"provider_test.go",
"splice_modules_test.go", "splice_modules_test.go",
"visit_test.go", "visit_test.go",
], ],

View file

@ -117,6 +117,19 @@ type Context struct {
srcDir string srcDir string
fs pathtools.FileSystem fs pathtools.FileSystem
moduleListFile string moduleListFile string
// Mutators indexed by the ID of the provider associated with them. Not all mutators will
// have providers, and not all providers will have a mutator, or if they do the mutator may
// not be registered in this Context.
providerMutators []*mutatorInfo
// The currently running mutator
startedMutator *mutatorInfo
// True for any mutators that have already run over all modules
finishedMutators map[*mutatorInfo]bool
// Can be set by tests to avoid invalidating Module values after mutators.
skipCloneModulesAfterMutators bool
} }
// An Error describes a problem that was encountered that is related to a // An Error describes a problem that was encountered that is related to a
@ -254,6 +267,14 @@ type moduleInfo struct {
// set during PrepareBuildActions // set during PrepareBuildActions
actionDefs localBuildActions actionDefs localBuildActions
providers []interface{}
startedMutator *mutatorInfo
finishedMutator *mutatorInfo
startedGenerateBuildActions bool
finishedGenerateBuildActions bool
} }
type variant struct { type variant struct {
@ -363,6 +384,7 @@ func newContext() *Context {
moduleInfo: make(map[Module]*moduleInfo), moduleInfo: make(map[Module]*moduleInfo),
globs: make(map[string]GlobPath), globs: make(map[string]GlobPath),
fs: pathtools.OsFs, fs: pathtools.OsFs,
finishedMutators: make(map[*mutatorInfo]bool),
ninjaBuildDir: nil, ninjaBuildDir: nil,
requiredNinjaMajor: 1, requiredNinjaMajor: 1,
requiredNinjaMinor: 7, requiredNinjaMinor: 7,
@ -1330,10 +1352,11 @@ func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
m := *origModule m := *origModule
newModule := &m newModule := &m
newModule.directDeps = append([]depInfo{}, origModule.directDeps...) newModule.directDeps = append([]depInfo(nil), origModule.directDeps...)
newModule.logicModule = newLogicModule newModule.logicModule = newLogicModule
newModule.variant = newVariant(origModule, mutatorName, variationName, local) newModule.variant = newVariant(origModule, mutatorName, variationName, local)
newModule.properties = newProperties newModule.properties = newProperties
newModule.providers = append([]interface{}(nil), origModule.providers...)
newModules = append(newModules, newModule) newModules = append(newModules, newModule)
@ -1518,6 +1541,8 @@ func (c *Context) ResolveDependencies(config interface{}) (deps []string, errs [
func (c *Context) resolveDependencies(ctx context.Context, config interface{}) (deps []string, errs []error) { func (c *Context) resolveDependencies(ctx context.Context, config interface{}) (deps []string, errs []error) {
pprof.Do(ctx, pprof.Labels("blueprint", "ResolveDependencies"), func(ctx context.Context) { pprof.Do(ctx, pprof.Labels("blueprint", "ResolveDependencies"), func(ctx context.Context) {
c.initProviders()
c.liveGlobals = newLiveTracker(config) c.liveGlobals = newLiveTracker(config)
deps, errs = c.generateSingletonBuildActions(config, c.preSingletonInfo, c.liveGlobals) deps, errs = c.generateSingletonBuildActions(config, c.preSingletonInfo, c.liveGlobals)
@ -1537,7 +1562,9 @@ func (c *Context) resolveDependencies(ctx context.Context, config interface{}) (
} }
deps = append(deps, mutatorDeps...) deps = append(deps, mutatorDeps...)
c.cloneModules() if !c.skipCloneModulesAfterMutators {
c.cloneModules()
}
c.dependenciesReady = true c.dependenciesReady = true
}) })
@ -2419,6 +2446,8 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo,
pauseCh: pause, pauseCh: pause,
} }
module.startedMutator = mutator
func() { func() {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -2434,6 +2463,8 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo,
direction.run(mutator, mctx) direction.run(mutator, mctx)
}() }()
module.finishedMutator = mutator
if len(mctx.errs) > 0 { if len(mctx.errs) > 0 {
errsCh <- mctx.errs errsCh <- mctx.errs
return true return true
@ -2482,6 +2513,8 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo,
} }
}() }()
c.startedMutator = mutator
var visitErrs []error var visitErrs []error
if mutator.parallel { if mutator.parallel {
visitErrs = parallelVisit(c.modulesSorted, direction.orderer(), parallelVisitLimit, visit) visitErrs = parallelVisit(c.modulesSorted, direction.orderer(), parallelVisitLimit, visit)
@ -2493,6 +2526,8 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo,
return nil, visitErrs return nil, visitErrs
} }
c.finishedMutators[mutator] = true
done <- true done <- true
if len(errs) > 0 { if len(errs) > 0 {
@ -2702,6 +2737,8 @@ func (c *Context) generateModuleBuildActions(config interface{},
handledMissingDeps: module.missingDeps == nil, handledMissingDeps: module.missingDeps == nil,
} }
mctx.module.startedGenerateBuildActions = true
func() { func() {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -2717,6 +2754,8 @@ func (c *Context) generateModuleBuildActions(config interface{},
mctx.module.logicModule.GenerateBuildActions(mctx) mctx.module.logicModule.GenerateBuildActions(mctx)
}() }()
mctx.module.finishedGenerateBuildActions = true
if len(mctx.errs) > 0 { if len(mctx.errs) > 0 {
errsCh <- mctx.errs errsCh <- mctx.errs
return true return true
@ -3287,6 +3326,25 @@ func (c *Context) ModuleType(logicModule Module) string {
return module.typeName return module.typeName
} }
// ModuleProvider returns the value, if any, for the provider for a module. If the value for the
// provider was not set it returns the zero value of the type of the provider, which means the
// return value can always be type-asserted to the type of the provider. The return value should
// always be considered read-only. It panics if called before the appropriate mutator or
// GenerateBuildActions pass for the provider on the module. The value returned may be a deep
// copy of the value originally passed to SetProvider.
func (c *Context) ModuleProvider(logicModule Module, provider ProviderKey) interface{} {
module := c.moduleInfo[logicModule]
value, _ := c.provider(module, provider)
return value
}
// ModuleHasProvider returns true if the provider for the given module has been set.
func (c *Context) ModuleHasProvider(logicModule Module, provider ProviderKey) bool {
module := c.moduleInfo[logicModule]
_, ok := c.provider(module, provider)
return ok
}
func (c *Context) BlueprintFile(logicModule Module) string { func (c *Context) BlueprintFile(logicModule Module) string {
module := c.moduleInfo[logicModule] module := c.moduleInfo[logicModule]
return module.relBlueprintsFile return module.relBlueprintsFile

View file

@ -306,6 +306,31 @@ type BaseModuleContext interface {
// other words, it checks for the module AddReverseDependency would add a // other words, it checks for the module AddReverseDependency would add a
// dependency on with the same argument. // dependency on with the same argument.
OtherModuleReverseDependencyVariantExists(name string) bool OtherModuleReverseDependencyVariantExists(name string) bool
// OtherModuleProvider returns the value for a provider for the given module. If the value is
// not set it returns the zero value of the type of the provider, so the return value can always
// be type asserted to the type of the provider. The value returned may be a deep copy of the
// value originally passed to SetProvider.
OtherModuleProvider(m Module, provider ProviderKey) interface{}
// OtherModuleHasProvider returns true if the provider for the given module has been set.
OtherModuleHasProvider(m Module, provider ProviderKey) bool
// Provider returns the value for a provider for the current module. If the value is
// not set it returns the zero value of the type of the provider, so the return value can always
// be type asserted to the type of the provider. It panics if called before the appropriate
// mutator or GenerateBuildActions pass for the provider. The value returned may be a deep
// copy of the value originally passed to SetProvider.
Provider(provider ProviderKey) interface{}
// HasProvider returns true if the provider for the current module has been set.
HasProvider(provider ProviderKey) bool
// SetProvider sets the value for a provider for the current module. It panics if not called
// during the appropriate mutator or GenerateBuildActions pass for the provider, if the value
// is not of the appropriate type, or if the value has already been set. The value should not
// be modified after being passed to SetProvider.
SetProvider(provider ProviderKey, value interface{})
} }
type DynamicDependerModuleContext BottomUpMutatorContext type DynamicDependerModuleContext BottomUpMutatorContext
@ -523,6 +548,32 @@ func (m *baseModuleContext) OtherModuleReverseDependencyVariantExists(name strin
return found != nil return found != nil
} }
func (m *baseModuleContext) OtherModuleProvider(logicModule Module, provider ProviderKey) interface{} {
module := m.context.moduleInfo[logicModule]
value, _ := m.context.provider(module, provider)
return value
}
func (m *baseModuleContext) OtherModuleHasProvider(logicModule Module, provider ProviderKey) bool {
module := m.context.moduleInfo[logicModule]
_, ok := m.context.provider(module, provider)
return ok
}
func (m *baseModuleContext) Provider(provider ProviderKey) interface{} {
value, _ := m.context.provider(m.module, provider)
return value
}
func (m *baseModuleContext) HasProvider(provider ProviderKey) bool {
_, ok := m.context.provider(m.module, provider)
return ok
}
func (m *baseModuleContext) SetProvider(provider ProviderKey, value interface{}) {
m.context.setProvider(m.module, provider, value)
}
func (m *baseModuleContext) GetDirectDep(name string) (Module, DependencyTag) { func (m *baseModuleContext) GetDirectDep(name string) (Module, DependencyTag) {
for _, dep := range m.module.directDeps { for _, dep := range m.module.directDeps {
if dep.module.Name() == name { if dep.module.Name() == name {
@ -882,6 +933,14 @@ type BottomUpMutatorContext interface {
// be used to add dependencies on the toVariationName variant using the fromVariationName // be used to add dependencies on the toVariationName variant using the fromVariationName
// variant. // variant.
CreateAliasVariation(fromVariationName, toVariationName string) CreateAliasVariation(fromVariationName, toVariationName string)
// SetVariationProvider sets the value for a provider for the given newly created variant of
// the current module, i.e. one of the Modules returned by CreateVariations.. It panics if
// not called during the appropriate mutator or GenerateBuildActions pass for the provider,
// if the value is not of the appropriate type, or if the module is not a newly created
// variant of the current module. The value should not be modified after being passed to
// SetVariationProvider.
SetVariationProvider(module Module, provider ProviderKey, value interface{})
} }
// A Mutator function is called for each Module, and can use // A Mutator function is called for each Module, and can use
@ -925,6 +984,16 @@ func (mctx *mutatorContext) CreateLocalVariations(variationNames ...string) []Mo
return mctx.createVariations(variationNames, true) return mctx.createVariations(variationNames, true)
} }
func (mctx *mutatorContext) SetVariationProvider(module Module, provider ProviderKey, value interface{}) {
for _, variant := range mctx.newVariations {
if m := variant.module(); m != nil && m.logicModule == module {
mctx.context.setProvider(m, provider, value)
return
}
}
panic(fmt.Errorf("module %q is not a newly created variant of %q", module, mctx.module))
}
type pendingAlias struct { type pendingAlias struct {
fromVariant variant fromVariant variant
target *moduleInfo target *moduleInfo

216
provider.go Normal file
View file

@ -0,0 +1,216 @@
// Copyright 2020 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 blueprint
import (
"fmt"
"reflect"
)
// This file implements Providers, modelled after Bazel
// (https://docs.bazel.build/versions/master/skylark/rules.html#providers).
// Each provider can be associated with a mutator, in which case the value for the provider for a
// module can only be set during the mutator call for the module, and the value can only be
// retrieved after the mutator call for the module. For providers not associated with a mutator, the
// value can for the provider for a module can only be set during GenerateBuildActions for the
// module, and the value can only be retrieved after GenerateBuildActions for the module.
//
// Providers are globally registered during init() and given a unique ID. The value of a provider
// for a module is stored in an []interface{} indexed by the ID. If the value of a provider has
// not been set, the value in the []interface{} will be nil.
//
// If the storage used by the provider value arrays becomes too large:
// sizeof([]interface) * number of providers * number of modules that have a provider value set
// then the storage can be replaced with something like a bitwise trie.
//
// The purpose of providers is to provide a serializable checkpoint between modules to enable
// Blueprint to skip parts of the analysis phase when inputs haven't changed. To that end,
// values passed to providers should be treated as immutable by callers to both the getters and
// setters. Go doesn't provide any way to enforce immutability on arbitrary types, so it may be
// necessary for the getters and setters to make deep copies of the values, likely extending
// proptools.CloneProperties to do so.
type provider struct {
id int
typ reflect.Type
zero interface{}
mutator string
}
type ProviderKey *provider
var providerRegistry []ProviderKey
// NewProvider returns a ProviderKey for the type of the given example value. The example value
// is otherwise unused.
//
// The returned ProviderKey can be used to set a value of the ProviderKey's type for a module
// inside GenerateBuildActions for the module, and to get the value from GenerateBuildActions from
// any module later in the build graph.
//
// Once Go has generics the exampleValue parameter will not be necessary:
// NewProvider(type T)() ProviderKey(T)
func NewProvider(exampleValue interface{}) ProviderKey {
return NewMutatorProvider(exampleValue, "")
}
// NewMutatorProvider returns a ProviderKey for the type of the given example value. The example
// value is otherwise unused.
//
// The returned ProviderKey can be used to set a value of the ProviderKey's type for a module inside
// the given mutator for the module, and to get the value from GenerateBuildActions from any
// module later in the build graph in the same mutator, or any module in a later mutator or during
// GenerateBuildActions.
//
// Once Go has generics the exampleValue parameter will not be necessary:
// NewMutatorProvider(type T)(mutator string) ProviderKey(T)
func NewMutatorProvider(exampleValue interface{}, mutator string) ProviderKey {
checkCalledFromInit()
typ := reflect.TypeOf(exampleValue)
zero := reflect.Zero(typ).Interface()
provider := &provider{
id: len(providerRegistry),
typ: typ,
zero: zero,
mutator: mutator,
}
providerRegistry = append(providerRegistry, provider)
return provider
}
// initProviders fills c.providerMutators with the *mutatorInfo associated with each provider ID,
// if any.
func (c *Context) initProviders() {
c.providerMutators = make([]*mutatorInfo, len(providerRegistry))
for _, provider := range providerRegistry {
for _, mutator := range c.mutatorInfo {
if mutator.name == provider.mutator {
c.providerMutators[provider.id] = mutator
}
}
}
}
// setProvider sets the value for a provider on a moduleInfo. Verifies that it is called during the
// appropriate mutator or GenerateBuildActions pass for the provider, and that the value is of the
// appropriate type. The value should not be modified after being passed to setProvider.
//
// Once Go has generics the value parameter can be typed:
// setProvider(type T)(m *moduleInfo, provider ProviderKey(T), value T)
func (c *Context) setProvider(m *moduleInfo, provider ProviderKey, value interface{}) {
if provider.mutator == "" {
if !m.startedGenerateBuildActions {
panic(fmt.Sprintf("Can't set value of provider %s before GenerateBuildActions started",
provider.typ))
} else if m.finishedGenerateBuildActions {
panic(fmt.Sprintf("Can't set value of provider %s after GenerateBuildActions finished",
provider.typ))
}
} else {
expectedMutator := c.providerMutators[provider.id]
if expectedMutator == nil {
panic(fmt.Sprintf("Can't set value of provider %s associated with unregistered mutator %s",
provider.typ, provider.mutator))
} else if c.mutatorFinishedForModule(expectedMutator, m) {
panic(fmt.Sprintf("Can't set value of provider %s after mutator %s finished",
provider.typ, provider.mutator))
} else if !c.mutatorStartedForModule(expectedMutator, m) {
panic(fmt.Sprintf("Can't set value of provider %s before mutator %s started",
provider.typ, provider.mutator))
}
}
if typ := reflect.TypeOf(value); typ != provider.typ {
panic(fmt.Sprintf("Value for provider has incorrect type, wanted %s, got %s",
provider.typ, typ))
}
if m.providers == nil {
m.providers = make([]interface{}, len(providerRegistry))
}
if m.providers[provider.id] != nil {
panic(fmt.Sprintf("Value of provider %s is already set", provider.typ))
}
m.providers[provider.id] = value
}
// provider returns the value, if any, for a given provider for a module. Verifies that it is
// called after the appropriate mutator or GenerateBuildActions pass for the provider on the module.
// If the value for the provider was not set it returns the zero value of the type of the provider,
// which means the return value can always be type-asserted to the type of the provider. The return
// value should always be considered read-only.
//
// Once Go has generics the return value can be typed and the type assert by callers can be dropped:
// provider(type T)(m *moduleInfo, provider ProviderKey(T)) T
func (c *Context) provider(m *moduleInfo, provider ProviderKey) (interface{}, bool) {
if provider.mutator == "" {
if !m.finishedGenerateBuildActions {
panic(fmt.Sprintf("Can't get value of provider %s before GenerateBuildActions finished",
provider.typ))
}
} else {
expectedMutator := c.providerMutators[provider.id]
if expectedMutator != nil && !c.mutatorFinishedForModule(expectedMutator, m) {
panic(fmt.Sprintf("Can't get value of provider %s before mutator %s finished",
provider.typ, provider.mutator))
}
}
if len(m.providers) > provider.id {
if p := m.providers[provider.id]; p != nil {
return p, true
}
}
return provider.zero, false
}
func (c *Context) mutatorFinishedForModule(mutator *mutatorInfo, m *moduleInfo) bool {
if c.finishedMutators[mutator] {
// mutator pass finished for all modules
return true
}
if c.startedMutator == mutator {
// mutator pass started, check if it is finished for this module
return m.finishedMutator == mutator
}
// mutator pass hasn't started
return false
}
func (c *Context) mutatorStartedForModule(mutator *mutatorInfo, m *moduleInfo) bool {
if c.finishedMutators[mutator] {
// mutator pass finished for all modules
return true
}
if c.startedMutator == mutator {
// mutator pass is currently running
if m.startedMutator == mutator {
// mutator has started for this module
return true
}
}
return false
}

420
provider_test.go Normal file
View file

@ -0,0 +1,420 @@
// Copyright 2020 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 blueprint
import (
"fmt"
"reflect"
"strings"
"testing"
)
type providerTestModule struct {
SimpleName
properties struct {
Deps []string
}
mutatorProviderValues []string
generateBuildActionsProviderValues []string
}
func newProviderTestModule() (Module, []interface{}) {
m := &providerTestModule{}
return m, []interface{}{&m.properties, &m.SimpleName.Properties}
}
type providerTestMutatorInfo struct {
Values []string
}
type providerTestGenerateBuildActionsInfo struct {
Value string
}
type providerTestUnsetInfo string
var providerTestMutatorInfoProvider = NewMutatorProvider(&providerTestMutatorInfo{}, "provider_mutator")
var providerTestGenerateBuildActionsInfoProvider = NewProvider(&providerTestGenerateBuildActionsInfo{})
var providerTestUnsetInfoProvider = NewMutatorProvider((providerTestUnsetInfo)(""), "provider_mutator")
var providerTestUnusedMutatorProvider = NewMutatorProvider(&struct{ unused string }{}, "nonexistent_mutator")
func (p *providerTestModule) GenerateBuildActions(ctx ModuleContext) {
unset := ctx.Provider(providerTestUnsetInfoProvider).(providerTestUnsetInfo)
if unset != "" {
panic(fmt.Sprintf("expected zero value for providerTestGenerateBuildActionsInfoProvider before it was set, got %q",
unset))
}
_ = ctx.Provider(providerTestUnusedMutatorProvider)
ctx.SetProvider(providerTestGenerateBuildActionsInfoProvider, &providerTestGenerateBuildActionsInfo{
Value: ctx.ModuleName(),
})
mp := ctx.Provider(providerTestMutatorInfoProvider).(*providerTestMutatorInfo)
if mp != nil {
p.mutatorProviderValues = mp.Values
}
ctx.VisitDirectDeps(func(module Module) {
gbap := ctx.OtherModuleProvider(module, providerTestGenerateBuildActionsInfoProvider).(*providerTestGenerateBuildActionsInfo)
if gbap != nil {
p.generateBuildActionsProviderValues = append(p.generateBuildActionsProviderValues, gbap.Value)
}
})
}
func providerTestDepsMutator(ctx BottomUpMutatorContext) {
if p, ok := ctx.Module().(*providerTestModule); ok {
ctx.AddDependency(ctx.Module(), nil, p.properties.Deps...)
}
}
func providerTestMutator(ctx BottomUpMutatorContext) {
values := []string{strings.ToLower(ctx.ModuleName())}
ctx.VisitDirectDeps(func(module Module) {
mp := ctx.OtherModuleProvider(module, providerTestMutatorInfoProvider).(*providerTestMutatorInfo)
if mp != nil {
values = append(values, mp.Values...)
}
})
ctx.SetProvider(providerTestMutatorInfoProvider, &providerTestMutatorInfo{
Values: values,
})
}
func providerTestAfterMutator(ctx BottomUpMutatorContext) {
_ = ctx.Provider(providerTestMutatorInfoProvider)
}
func TestProviders(t *testing.T) {
ctx := NewContext()
ctx.RegisterModuleType("provider_module", newProviderTestModule)
ctx.RegisterBottomUpMutator("provider_deps_mutator", providerTestDepsMutator)
ctx.RegisterBottomUpMutator("provider_mutator", providerTestMutator)
ctx.RegisterBottomUpMutator("provider_after_mutator", providerTestAfterMutator)
ctx.MockFileSystem(map[string][]byte{
"Blueprints": []byte(`
provider_module {
name: "A",
deps: ["B"],
}
provider_module {
name: "B",
deps: ["C", "D"],
}
provider_module {
name: "C",
deps: ["D"],
}
provider_module {
name: "D",
}
`),
})
_, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
if len(errs) == 0 {
_, errs = ctx.ResolveDependencies(nil)
}
if len(errs) == 0 {
_, errs = ctx.PrepareBuildActions(nil)
}
if len(errs) > 0 {
t.Errorf("unexpected errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
aModule := ctx.moduleGroupFromName("A", nil).moduleByVariantName("").logicModule.(*providerTestModule)
if g, w := aModule.generateBuildActionsProviderValues, []string{"B"}; !reflect.DeepEqual(g, w) {
t.Errorf("expected A.generateBuildActionsProviderValues %q, got %q", w, g)
}
if g, w := aModule.mutatorProviderValues, []string{"a", "b", "c", "d", "d"}; !reflect.DeepEqual(g, w) {
t.Errorf("expected A.mutatorProviderValues %q, got %q", w, g)
}
bModule := ctx.moduleGroupFromName("B", nil).moduleByVariantName("").logicModule.(*providerTestModule)
if g, w := bModule.generateBuildActionsProviderValues, []string{"C", "D"}; !reflect.DeepEqual(g, w) {
t.Errorf("expected B.generateBuildActionsProviderValues %q, got %q", w, g)
}
if g, w := bModule.mutatorProviderValues, []string{"b", "c", "d", "d"}; !reflect.DeepEqual(g, w) {
t.Errorf("expected B.mutatorProviderValues %q, got %q", w, g)
}
}
type invalidProviderUsageMutatorInfo string
type invalidProviderUsageGenerateBuildActionsInfo string
var invalidProviderUsageMutatorInfoProvider = NewMutatorProvider(invalidProviderUsageMutatorInfo(""), "mutator_under_test")
var invalidProviderUsageGenerateBuildActionsInfoProvider = NewProvider(invalidProviderUsageGenerateBuildActionsInfo(""))
type invalidProviderUsageTestModule struct {
parent *invalidProviderUsageTestModule
SimpleName
properties struct {
Deps []string
Early_mutator_set_of_mutator_provider bool
Late_mutator_set_of_mutator_provider bool
Late_build_actions_set_of_mutator_provider bool
Early_mutator_set_of_build_actions_provider bool
Early_mutator_get_of_mutator_provider bool
Early_module_get_of_mutator_provider bool
Early_mutator_get_of_build_actions_provider bool
Early_module_get_of_build_actions_provider bool
Duplicate_set bool
}
}
func invalidProviderUsageDepsMutator(ctx BottomUpMutatorContext) {
if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
ctx.AddDependency(ctx.Module(), nil, i.properties.Deps...)
}
}
func invalidProviderUsageParentMutator(ctx TopDownMutatorContext) {
if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
ctx.VisitDirectDeps(func(module Module) {
module.(*invalidProviderUsageTestModule).parent = i
})
}
}
func invalidProviderUsageBeforeMutator(ctx BottomUpMutatorContext) {
if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
if i.properties.Early_mutator_set_of_mutator_provider {
// A mutator attempting to set the value of a provider associated with a later mutator.
ctx.SetProvider(invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
}
if i.properties.Early_mutator_get_of_mutator_provider {
// A mutator attempting to get the value of a provider associated with a later mutator.
_ = ctx.Provider(invalidProviderUsageMutatorInfoProvider)
}
}
}
func invalidProviderUsageMutatorUnderTest(ctx TopDownMutatorContext) {
if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
if i.properties.Early_mutator_set_of_build_actions_provider {
// A mutator attempting to set the value of a non-mutator provider.
ctx.SetProvider(invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
}
if i.properties.Early_mutator_get_of_build_actions_provider {
// A mutator attempting to get the value of a non-mutator provider.
_ = ctx.Provider(invalidProviderUsageGenerateBuildActionsInfoProvider)
}
if i.properties.Early_module_get_of_mutator_provider {
// A mutator attempting to get the value of a provider associated with this mutator on
// a module for which this mutator hasn't run. This is a top down mutator so
// dependencies haven't run yet.
ctx.VisitDirectDeps(func(module Module) {
_ = ctx.OtherModuleProvider(module, invalidProviderUsageMutatorInfoProvider)
})
}
}
}
func invalidProviderUsageAfterMutator(ctx BottomUpMutatorContext) {
if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
if i.properties.Late_mutator_set_of_mutator_provider {
// A mutator trying to set the value of a provider associated with an earlier mutator.
ctx.SetProvider(invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
}
if i.properties.Late_mutator_set_of_mutator_provider {
// A mutator trying to set the value of a provider associated with an earlier mutator.
ctx.SetProvider(invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
}
}
}
func (i *invalidProviderUsageTestModule) GenerateBuildActions(ctx ModuleContext) {
if i.properties.Late_build_actions_set_of_mutator_provider {
// A GenerateBuildActions trying to set the value of a provider associated with a mutator.
ctx.SetProvider(invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
}
if i.properties.Early_module_get_of_build_actions_provider {
// A GenerateBuildActions trying to get the value of a provider on a module for which
// GenerateBuildActions hasn't run.
_ = ctx.OtherModuleProvider(i.parent, invalidProviderUsageGenerateBuildActionsInfoProvider)
}
if i.properties.Duplicate_set {
ctx.SetProvider(invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
ctx.SetProvider(invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
}
}
func TestInvalidProvidersUsage(t *testing.T) {
run := func(t *testing.T, module string, prop string, panicMsg string) {
t.Helper()
ctx := NewContext()
ctx.RegisterModuleType("invalid_provider_usage_test_module", func() (Module, []interface{}) {
m := &invalidProviderUsageTestModule{}
return m, []interface{}{&m.properties, &m.SimpleName.Properties}
})
ctx.RegisterBottomUpMutator("deps", invalidProviderUsageDepsMutator)
ctx.RegisterBottomUpMutator("before", invalidProviderUsageBeforeMutator)
ctx.RegisterTopDownMutator("mutator_under_test", invalidProviderUsageMutatorUnderTest)
ctx.RegisterBottomUpMutator("after", invalidProviderUsageAfterMutator)
ctx.RegisterTopDownMutator("parent", invalidProviderUsageParentMutator)
// Don't invalidate the parent pointer and before GenerateBuildActions.
ctx.skipCloneModulesAfterMutators = true
var parentBP, moduleUnderTestBP, childBP string
prop += ": true,"
switch module {
case "parent":
parentBP = prop
case "module_under_test":
moduleUnderTestBP = prop
case "child":
childBP = prop
}
bp := fmt.Sprintf(`
invalid_provider_usage_test_module {
name: "parent",
deps: ["module_under_test"],
%s
}
invalid_provider_usage_test_module {
name: "module_under_test",
deps: ["child"],
%s
}
invalid_provider_usage_test_module {
name: "child",
%s
}
`,
parentBP,
moduleUnderTestBP,
childBP)
ctx.MockFileSystem(map[string][]byte{
"Blueprints": []byte(bp),
})
_, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
if len(errs) == 0 {
_, errs = ctx.ResolveDependencies(nil)
}
if len(errs) == 0 {
_, errs = ctx.PrepareBuildActions(nil)
}
if len(errs) == 0 {
t.Fatal("expected an error")
}
if len(errs) > 1 {
t.Errorf("expected a single error, got %d:", len(errs))
for i, err := range errs {
t.Errorf("%d: %s", i, err)
}
t.FailNow()
}
if panicErr, ok := errs[0].(panicError); ok {
if panicErr.panic != panicMsg {
t.Fatalf("expected panic %q, got %q", panicMsg, panicErr.panic)
}
} else {
t.Fatalf("expected a panicError, got %T: %s", errs[0], errs[0].Error())
}
}
tests := []struct {
prop string
module string
panicMsg string
skip string
}{
{
prop: "early_mutator_set_of_mutator_provider",
module: "module_under_test",
panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo before mutator mutator_under_test started",
},
{
prop: "late_mutator_set_of_mutator_provider",
module: "module_under_test",
panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo after mutator mutator_under_test finished",
},
{
prop: "late_build_actions_set_of_mutator_provider",
module: "module_under_test",
panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo after mutator mutator_under_test finished",
},
{
prop: "early_mutator_set_of_build_actions_provider",
module: "module_under_test",
panicMsg: "Can't set value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo before GenerateBuildActions started",
},
{
prop: "early_mutator_get_of_mutator_provider",
module: "module_under_test",
panicMsg: "Can't get value of provider blueprint.invalidProviderUsageMutatorInfo before mutator mutator_under_test finished",
},
{
prop: "early_module_get_of_mutator_provider",
module: "module_under_test",
panicMsg: "Can't get value of provider blueprint.invalidProviderUsageMutatorInfo before mutator mutator_under_test finished",
},
{
prop: "early_mutator_get_of_build_actions_provider",
module: "module_under_test",
panicMsg: "Can't get value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo before GenerateBuildActions finished",
},
{
prop: "early_module_get_of_build_actions_provider",
module: "module_under_test",
panicMsg: "Can't get value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo before GenerateBuildActions finished",
},
{
prop: "duplicate_set",
module: "module_under_test",
panicMsg: "Value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo is already set",
},
}
for _, tt := range tests {
t.Run(tt.prop, func(t *testing.T) {
run(t, tt.module, tt.prop, tt.panicMsg)
})
}
}

View file

@ -47,6 +47,16 @@ type SingletonContext interface {
// BlueprintFile returns the path of the Blueprint file that defined the given module. // BlueprintFile returns the path of the Blueprint file that defined the given module.
BlueprintFile(module Module) string BlueprintFile(module Module) string
// ModuleProvider returns the value, if any, for the provider for a module. If the value for the
// provider was not set it returns the zero value of the type of the provider, which means the
// return value can always be type-asserted to the type of the provider. The return value should
// always be considered read-only. It panics if called before the appropriate mutator or
// GenerateBuildActions pass for the provider on the module.
ModuleProvider(module Module, provider ProviderKey) interface{}
// ModuleHasProvider returns true if the provider for the given module has been set.
ModuleHasProvider(m Module, provider ProviderKey) bool
// ModuleErrorf reports an error at the line number of the module type in the module definition. // ModuleErrorf reports an error at the line number of the module type in the module definition.
ModuleErrorf(module Module, format string, args ...interface{}) ModuleErrorf(module Module, format string, args ...interface{})
@ -188,6 +198,15 @@ func (s *singletonContext) ModuleType(logicModule Module) string {
return s.context.ModuleType(logicModule) return s.context.ModuleType(logicModule)
} }
func (s *singletonContext) ModuleProvider(logicModule Module, provider ProviderKey) interface{} {
return s.context.ModuleProvider(logicModule, provider)
}
// ModuleHasProvider returns true if the provider for the given module has been set.
func (s *singletonContext) ModuleHasProvider(logicModule Module, provider ProviderKey) bool {
return s.context.ModuleHasProvider(logicModule, provider)
}
func (s *singletonContext) BlueprintFile(logicModule Module) string { func (s *singletonContext) BlueprintFile(logicModule Module) string {
return s.context.BlueprintFile(logicModule) return s.context.BlueprintFile(logicModule)
} }