Add support for module variant aliases

Adding a dependency on a module with variants can be problematic
if the code adding the dependency is not aware of every mutator
that has created variants.  Add an AliasVariations to
BottomUpMutatorContext, which allows a mutator to alias the
original variant of a module to one of the new variants of the
module, which will allow future dependencies to be added using
the original list of variations.  The aliases are transient,
and only exist until the next mutator that calls CreateVariations
when visiting the module without also calling AliasVariations.

Test: TestAlises
Change-Id: Ieaa04b5a6bdcb5a1ff5114b1e03460de795d4479
This commit is contained in:
Colin Cross 2019-11-13 20:11:14 -08:00
parent 9403b5a790
commit f7beb89df5
4 changed files with 282 additions and 0 deletions

View file

@ -24,6 +24,7 @@ bootstrap_go_package {
testSrcs: [
"context_test.go",
"glob_test.go",
"module_ctx_test.go",
"ninja_strings_test.go",
"ninja_writer_test.go",
"splice_modules_test.go",

View file

@ -157,11 +157,19 @@ type localBuildActions struct {
buildDefs []*buildDef
}
type moduleAlias struct {
variantName string
variant variationMap
dependencyVariant variationMap
target *moduleInfo
}
type moduleGroup struct {
name string
ninjaName string
modules []*moduleInfo
aliases []*moduleAlias
namespace Namespace
}
@ -197,6 +205,7 @@ type moduleInfo struct {
// set during each runMutator
splitModules []*moduleInfo
aliasTarget *moduleInfo
// set during PrepareBuildActions
actionDefs localBuildActions
@ -1272,6 +1281,10 @@ func (c *Context) prettyPrintGroupVariants(group *moduleGroup) string {
for _, mod := range group.modules {
variants = append(variants, c.prettyPrintVariant(mod.variant))
}
for _, mod := range group.aliases {
variants = append(variants, c.prettyPrintVariant(mod.variant)+
"(alias to "+c.prettyPrintVariant(mod.target.variant)+")")
}
sort.Strings(variants)
return strings.Join(variants, "\n ")
}
@ -1443,6 +1456,11 @@ func (c *Context) findMatchingVariant(module *moduleInfo, possible *moduleGroup,
return m
}
}
for _, m := range possible.aliases {
if m.variant.equal(variantToMatch) {
return m.target
}
}
}
return nil
@ -1551,6 +1569,15 @@ func (c *Context) addVariationDependency(module *moduleInfo, variations []Variat
}
}
if foundDep == nil {
for _, m := range possibleDeps.aliases {
if check(m.variant) {
foundDep = m.target
break
}
}
}
if foundDep == nil {
return []error{&BlueprintError{
Err: fmt.Errorf("dependency %q of %q missing variant:\n %s\navailable variants:\n %s",
@ -2172,6 +2199,16 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo,
group.modules, i = spliceModules(group.modules, i, module.splitModules)
}
// Create any new aliases.
if module.aliasTarget != nil {
group.aliases = append(group.aliases, &moduleAlias{
variantName: module.variantName,
variant: module.variant,
dependencyVariant: module.dependencyVariant,
target: module.aliasTarget,
})
}
// Fix up any remaining dependencies on modules that were split into variants
// by replacing them with the first variant
for j, dep := range module.directDeps {
@ -2188,6 +2225,21 @@ func (c *Context) runMutator(config interface{}, mutator *mutatorInfo,
module.directDeps = append(module.directDeps, module.newDirectDeps...)
module.newDirectDeps = nil
}
// Forward or delete any dangling aliases.
for i := 0; i < len(group.aliases); i++ {
alias := group.aliases[i]
if alias.target.logicModule == nil {
if alias.target.aliasTarget != nil {
alias.target = alias.target.aliasTarget
} else {
// The alias was left dangling, remove it.
group.aliases = append(group.aliases[:i], group.aliases[i+1:]...)
i--
}
}
}
}
// Add in any new reverse dependencies that were added by the mutator
@ -2528,6 +2580,12 @@ func (c *Context) moduleMatchingVariant(module *moduleInfo, name string) *module
}
}
for _, m := range group.aliases {
if module.variantName == m.variantName {
return m.target
}
}
return nil
}

View file

@ -785,6 +785,13 @@ type BottomUpMutatorContext interface {
// specified name with the current variant of this module. Replacements don't take effect until
// after the mutator pass is finished.
ReplaceDependencies(string)
// AliasVariation takes a variationName that was passed to CreateVariations for this module, and creates an
// alias from the current variant to the new variant. The alias will be valid until the next time a mutator
// calls CreateVariations or CreateLocalVariations on this module without also calling AliasVariation. The
// alias can be used to add dependencies on the newly created variant using the variant map from before
// CreateVariations was run.
AliasVariation(variationName string)
}
// A Mutator function is called for each Module, and can use
@ -857,6 +864,25 @@ func (mctx *mutatorContext) createVariations(variationNames []string, local bool
return ret
}
func (mctx *mutatorContext) AliasVariation(variationName string) {
if mctx.module.aliasTarget != nil {
panic(fmt.Errorf("AliasVariation already called"))
}
for _, variant := range mctx.newVariations {
if variant.variant[mctx.name] == variationName {
mctx.module.aliasTarget = variant
return
}
}
var foundVariations []string
for _, variant := range mctx.newVariations {
foundVariations = append(foundVariations, variant.variant[mctx.name])
}
panic(fmt.Errorf("no %q variation in module variations %q", variationName, foundVariations))
}
func (mctx *mutatorContext) SetDependencyVariation(variationName string) {
mctx.context.convertDepsToVariation(mctx.module, mctx.name, variationName, nil)
}

197
module_ctx_test.go Normal file
View file

@ -0,0 +1,197 @@
// 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 blueprint
import (
"reflect"
"strings"
"testing"
)
type moduleCtxTestModule struct {
SimpleName
}
func newModuleCtxTestModule() (Module, []interface{}) {
m := &moduleCtxTestModule{}
return m, []interface{}{&m.SimpleName.Properties}
}
func (f *moduleCtxTestModule) GenerateBuildActions(ModuleContext) {
}
func noCreateAliasMutator(name string) func(ctx BottomUpMutatorContext) {
return func(ctx BottomUpMutatorContext) {
if ctx.ModuleName() == name {
ctx.CreateVariations("a", "b")
}
}
}
func createAliasMutator(name string) func(ctx BottomUpMutatorContext) {
return func(ctx BottomUpMutatorContext) {
if ctx.ModuleName() == name {
ctx.CreateVariations("a", "b")
ctx.AliasVariation("b")
}
}
}
func addVariantDepsMutator(variants []Variation, tag DependencyTag, from, to string) func(ctx BottomUpMutatorContext) {
return func(ctx BottomUpMutatorContext) {
if ctx.ModuleName() == from {
ctx.AddVariationDependencies(variants, tag, to)
}
}
}
func TestAliases(t *testing.T) {
runWithFailures := func(ctx *Context, expectedErr string) {
t.Helper()
bp := `
test {
name: "foo",
}
test {
name: "bar",
}
`
mockFS := map[string][]byte{
"Blueprints": []byte(bp),
}
ctx.MockFileSystem(mockFS)
_, errs := ctx.ParseFileList(".", []string{"Blueprints"})
if len(errs) > 0 {
t.Errorf("unexpected parse errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
}
_, errs = ctx.ResolveDependencies(nil)
if len(errs) > 0 {
if expectedErr == "" {
t.Errorf("unexpected dep errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
} else {
for _, err := range errs {
if strings.Contains(err.Error(), expectedErr) {
continue
} else {
t.Errorf("unexpected dep error: %s", err)
}
}
}
} else if expectedErr != "" {
t.Errorf("missing dep error: %s", expectedErr)
}
}
run := func(ctx *Context) {
t.Helper()
runWithFailures(ctx, "")
}
t.Run("simple", func(t *testing.T) {
// Creates a module "bar" with variants "a" and "b" and alias "" -> "b".
// Tests a dependency from "foo" to "bar" variant "b" through alias "".
ctx := NewContext()
ctx.RegisterModuleType("test", newModuleCtxTestModule)
ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
ctx.RegisterBottomUpMutator("2", addVariantDepsMutator(nil, nil, "foo", "bar"))
run(ctx)
foo := ctx.moduleGroupFromName("foo", nil).modules[0]
barB := ctx.moduleGroupFromName("bar", nil).modules[1]
if g, w := barB.variantName, "b"; g != w {
t.Fatalf("expected bar.modules[1] variant to be %q, got %q", w, g)
}
if g, w := foo.forwardDeps, []*moduleInfo{barB}; !reflect.DeepEqual(g, w) {
t.Fatalf("expected foo deps to be %q, got %q", w, g)
}
})
t.Run("chained", func(t *testing.T) {
// Creates a module "bar" with variants "a_a", "a_b", "b_a" and "b_b" and aliases "" -> "b_b",
// "a" -> "a_b", and "b" -> "b_b".
// Tests a dependency from "foo" to "bar" variant "b_b" through alias "".
ctx := NewContext()
ctx.RegisterModuleType("test", newModuleCtxTestModule)
ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
ctx.RegisterBottomUpMutator("2", createAliasMutator("bar"))
ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
run(ctx)
foo := ctx.moduleGroupFromName("foo", nil).modules[0]
barBB := ctx.moduleGroupFromName("bar", nil).modules[3]
if g, w := barBB.variantName, "b_b"; g != w {
t.Fatalf("expected bar.modules[3] variant to be %q, got %q", w, g)
}
if g, w := foo.forwardDeps, []*moduleInfo{barBB}; !reflect.DeepEqual(g, w) {
t.Fatalf("expected foo deps to be %q, got %q", w, g)
}
})
t.Run("chained2", func(t *testing.T) {
// Creates a module "bar" with variants "a_a", "a_b", "b_a" and "b_b" and aliases "" -> "b_b",
// "a" -> "a_b", and "b" -> "b_b".
// Tests a dependency from "foo" to "bar" variant "a_b" through alias "a".
ctx := NewContext()
ctx.RegisterModuleType("test", newModuleCtxTestModule)
ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
ctx.RegisterBottomUpMutator("2", createAliasMutator("bar"))
ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "a"}}, nil, "foo", "bar"))
run(ctx)
foo := ctx.moduleGroupFromName("foo", nil).modules[0]
barAB := ctx.moduleGroupFromName("bar", nil).modules[1]
if g, w := barAB.variantName, "a_b"; g != w {
t.Fatalf("expected bar.modules[1] variant to be %q, got %q", w, g)
}
if g, w := foo.forwardDeps, []*moduleInfo{barAB}; !reflect.DeepEqual(g, w) {
t.Fatalf("expected foo deps to be %q, got %q", w, g)
}
})
t.Run("removed dangling alias", func(t *testing.T) {
// Creates a module "bar" with variants "a" and "b" and aliases "" -> "b", then splits the variants into
// "a_a", "a_b", "b_a" and "b_b" without creating new aliases.
// Tests a dependency from "foo" to removed "bar" alias "" fails.
ctx := NewContext()
ctx.RegisterModuleType("test", newModuleCtxTestModule)
ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
ctx.RegisterBottomUpMutator("2", noCreateAliasMutator("bar"))
ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
runWithFailures(ctx, `dependency "bar" of "foo" missing variant:`+"\n \n"+
"available variants:"+
"\n 1:a, 2:a\n 1:a, 2:b\n 1:b, 2:a\n 1:b, 2:b")
})
}