diff --git a/Blueprints b/Blueprints index ecc0792..25c22ab 100644 --- a/Blueprints +++ b/Blueprints @@ -17,6 +17,7 @@ bootstrap_go_package { "ninja_strings.go", "ninja_writer.go", "package_ctx.go", + "provider.go", "scope.go", "singleton_ctx.go", ], @@ -26,6 +27,7 @@ bootstrap_go_package { "module_ctx_test.go", "ninja_strings_test.go", "ninja_writer_test.go", + "provider_test.go", "splice_modules_test.go", "visit_test.go", ], diff --git a/context.go b/context.go index 0af803d..f12856f 100644 --- a/context.go +++ b/context.go @@ -117,6 +117,19 @@ type Context struct { srcDir string fs pathtools.FileSystem 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 @@ -254,6 +267,14 @@ type moduleInfo struct { // set during PrepareBuildActions actionDefs localBuildActions + + providers []interface{} + + startedMutator *mutatorInfo + finishedMutator *mutatorInfo + + startedGenerateBuildActions bool + finishedGenerateBuildActions bool } type variant struct { @@ -363,6 +384,7 @@ func newContext() *Context { moduleInfo: make(map[Module]*moduleInfo), globs: make(map[string]GlobPath), fs: pathtools.OsFs, + finishedMutators: make(map[*mutatorInfo]bool), ninjaBuildDir: nil, requiredNinjaMajor: 1, requiredNinjaMinor: 7, @@ -1330,10 +1352,11 @@ func (c *Context) createVariations(origModule *moduleInfo, mutatorName string, m := *origModule newModule := &m - newModule.directDeps = append([]depInfo{}, origModule.directDeps...) + newModule.directDeps = append([]depInfo(nil), origModule.directDeps...) newModule.logicModule = newLogicModule newModule.variant = newVariant(origModule, mutatorName, variationName, local) newModule.properties = newProperties + newModule.providers = append([]interface{}(nil), origModule.providers...) 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) { pprof.Do(ctx, pprof.Labels("blueprint", "ResolveDependencies"), func(ctx context.Context) { + c.initProviders() + c.liveGlobals = newLiveTracker(config) 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...) - c.cloneModules() + if !c.skipCloneModulesAfterMutators { + c.cloneModules() + } c.dependenciesReady = true }) @@ -2419,6 +2446,8 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo, pauseCh: pause, } + module.startedMutator = mutator + func() { defer func() { if r := recover(); r != nil { @@ -2434,6 +2463,8 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo, direction.run(mutator, mctx) }() + module.finishedMutator = mutator + if len(mctx.errs) > 0 { errsCh <- mctx.errs return true @@ -2482,6 +2513,8 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo, } }() + c.startedMutator = mutator + var visitErrs []error if mutator.parallel { visitErrs = parallelVisit(c.modulesSorted, direction.orderer(), parallelVisitLimit, visit) @@ -2493,6 +2526,8 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo, return nil, visitErrs } + c.finishedMutators[mutator] = true + done <- true if len(errs) > 0 { @@ -2702,6 +2737,8 @@ func (c *Context) generateModuleBuildActions(config interface{}, handledMissingDeps: module.missingDeps == nil, } + mctx.module.startedGenerateBuildActions = true + func() { defer func() { if r := recover(); r != nil { @@ -2717,6 +2754,8 @@ func (c *Context) generateModuleBuildActions(config interface{}, mctx.module.logicModule.GenerateBuildActions(mctx) }() + mctx.module.finishedGenerateBuildActions = true + if len(mctx.errs) > 0 { errsCh <- mctx.errs return true @@ -3287,6 +3326,25 @@ func (c *Context) ModuleType(logicModule Module) string { 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 { module := c.moduleInfo[logicModule] return module.relBlueprintsFile diff --git a/module_ctx.go b/module_ctx.go index 8cdc9ec..d0f8c39 100644 --- a/module_ctx.go +++ b/module_ctx.go @@ -306,6 +306,31 @@ type BaseModuleContext interface { // other words, it checks for the module AddReverseDependency would add a // dependency on with the same argument. 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 @@ -523,6 +548,32 @@ func (m *baseModuleContext) OtherModuleReverseDependencyVariantExists(name strin 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) { for _, dep := range m.module.directDeps { if dep.module.Name() == name { @@ -882,6 +933,14 @@ type BottomUpMutatorContext interface { // be used to add dependencies on the toVariationName variant using the fromVariationName // variant. 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 @@ -925,6 +984,16 @@ func (mctx *mutatorContext) CreateLocalVariations(variationNames ...string) []Mo 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 { fromVariant variant target *moduleInfo diff --git a/provider.go b/provider.go new file mode 100644 index 0000000..b83e1d4 --- /dev/null +++ b/provider.go @@ -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 +} diff --git a/provider_test.go b/provider_test.go new file mode 100644 index 0000000..8f8def4 --- /dev/null +++ b/provider_test.go @@ -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) + }) + } +} diff --git a/singleton_ctx.go b/singleton_ctx.go index 3c0a24c..a4e7153 100644 --- a/singleton_ctx.go +++ b/singleton_ctx.go @@ -47,6 +47,16 @@ type SingletonContext interface { // BlueprintFile returns the path of the Blueprint file that defined the given module. 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(module Module, format string, args ...interface{}) @@ -188,6 +198,15 @@ func (s *singletonContext) ModuleType(logicModule Module) string { 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 { return s.context.BlueprintFile(logicModule) }