diff --git a/Blueprints b/Blueprints index c3c8975..1006721 100644 --- a/Blueprints +++ b/Blueprints @@ -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", diff --git a/context.go b/context.go index cedf3d8..6a53776 100644 --- a/context.go +++ b/context.go @@ -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 @@ -242,6 +251,9 @@ type Variation struct { type variationMap map[string]string func (vm variationMap) clone() variationMap { + if vm == nil { + return nil + } newVm := make(variationMap) for k, v := range vm { newVm[k] = v @@ -1178,6 +1190,9 @@ func (c *Context) createVariations(origModule *moduleInfo, mutatorName string, } newVariant := origModule.variant.clone() + if newVariant == nil { + newVariant = make(variationMap) + } newVariant[mutatorName] = variationName m := *origModule @@ -1261,6 +1276,19 @@ func (c *Context) prettyPrintVariant(variant variationMap) string { return strings.Join(names, ", ") } +func (c *Context) prettyPrintGroupVariants(group *moduleGroup) string { + var variants []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 ") +} + func (c *Context) newModule(factory ModuleFactory) *moduleInfo { logicModule, properties := factory() @@ -1410,9 +1438,9 @@ func blueprintDepsMutator(ctx BottomUpMutatorContext) { // findMatchingVariant searches the moduleGroup for a module with the same variant as module, // and returns the matching module, or nil if one is not found. -func (c *Context) findMatchingVariant(module *moduleInfo, possible []*moduleInfo, reverse bool) *moduleInfo { - if len(possible) == 1 { - return possible[0] +func (c *Context) findMatchingVariant(module *moduleInfo, possible *moduleGroup, reverse bool) *moduleInfo { + if len(possible.modules) == 1 { + return possible.modules[0] } else { var variantToMatch variationMap if !reverse { @@ -1423,11 +1451,16 @@ func (c *Context) findMatchingVariant(module *moduleInfo, possible []*moduleInfo // For reverse dependency, use all the variants variantToMatch = module.variant } - for _, m := range possible { + for _, m := range possible.modules { if m.variant.equal(variantToMatch) { return m } } + for _, m := range possible.aliases { + if m.variant.equal(variantToMatch) { + return m.target + } + } } return nil @@ -1445,7 +1478,7 @@ func (c *Context) addDependency(module *moduleInfo, tag DependencyTag, depName s }} } - possibleDeps := c.modulesFromName(depName, module.namespace()) + possibleDeps := c.moduleGroupFromName(depName, module.namespace()) if possibleDeps == nil { return c.discoveredMissingDependencies(module, depName) } @@ -1456,17 +1489,11 @@ func (c *Context) addDependency(module *moduleInfo, tag DependencyTag, depName s return nil } - variants := make([]string, len(possibleDeps)) - for i, mod := range possibleDeps { - variants[i] = c.prettyPrintVariant(mod.variant) - } - sort.Strings(variants) - return []error{&BlueprintError{ Err: fmt.Errorf("dependency %q of %q missing variant:\n %s\navailable variants:\n %s", depName, module.Name(), c.prettyPrintVariant(module.dependencyVariant), - strings.Join(variants, "\n ")), + c.prettyPrintGroupVariants(possibleDeps)), Pos: module.pos, }} } @@ -1479,7 +1506,7 @@ func (c *Context) findReverseDependency(module *moduleInfo, destName string) (*m }} } - possibleDeps := c.modulesFromName(destName, module.namespace()) + possibleDeps := c.moduleGroupFromName(destName, module.namespace()) if possibleDeps == nil { return nil, []error{&BlueprintError{ Err: fmt.Errorf("%q has a reverse dependency on undefined module %q", @@ -1492,17 +1519,11 @@ func (c *Context) findReverseDependency(module *moduleInfo, destName string) (*m return m, nil } - variants := make([]string, len(possibleDeps)) - for i, mod := range possibleDeps { - variants[i] = c.prettyPrintVariant(mod.variant) - } - sort.Strings(variants) - return nil, []error{&BlueprintError{ Err: fmt.Errorf("reverse dependency %q of %q missing variant:\n %s\navailable variants:\n %s", destName, module.Name(), c.prettyPrintVariant(module.dependencyVariant), - strings.Join(variants, "\n ")), + c.prettyPrintGroupVariants(possibleDeps)), Pos: module.pos, }} } @@ -1513,7 +1534,7 @@ func (c *Context) addVariationDependency(module *moduleInfo, variations []Variat panic("BaseDependencyTag is not allowed to be used directly!") } - possibleDeps := c.modulesFromName(depName, module.namespace()) + possibleDeps := c.moduleGroupFromName(depName, module.namespace()) if possibleDeps == nil { return c.discoveredMissingDependencies(module, depName) } @@ -1524,55 +1545,67 @@ func (c *Context) addVariationDependency(module *moduleInfo, variations []Variat var newVariant variationMap if !far { newVariant = module.dependencyVariant.clone() - } else { - newVariant = make(variationMap) } for _, v := range variations { + if newVariant == nil { + newVariant = make(variationMap) + } newVariant[v.Mutator] = v.Variation } - for _, m := range possibleDeps { - var found bool + check := func(variant variationMap) bool { if far { - found = m.variant.subset(newVariant) + return variant.subset(newVariant) } else { - found = m.variant.equal(newVariant) - } - if found { - if module == m { - return []error{&BlueprintError{ - Err: fmt.Errorf("%q depends on itself", depName), - Pos: module.pos, - }} - } - // AddVariationDependency allows adding a dependency on itself, but only if - // that module is earlier in the module list than this one, since we always - // run GenerateBuildActions in order for the variants of a module - if m.group == module.group && beforeInModuleList(module, m, module.group.modules) { - return []error{&BlueprintError{ - Err: fmt.Errorf("%q depends on later version of itself", depName), - Pos: module.pos, - }} - } - module.newDirectDeps = append(module.newDirectDeps, depInfo{m, tag}) - atomic.AddUint32(&c.depsModified, 1) - return nil + return variant.equal(newVariant) } } - variants := make([]string, len(possibleDeps)) - for i, mod := range possibleDeps { - variants[i] = c.prettyPrintVariant(mod.variant) + var foundDep *moduleInfo + for _, m := range possibleDeps.modules { + if check(m.variant) { + foundDep = m + break + } } - sort.Strings(variants) - return []error{&BlueprintError{ - Err: fmt.Errorf("dependency %q of %q missing variant:\n %s\navailable variants:\n %s", - depName, module.Name(), - c.prettyPrintVariant(newVariant), - strings.Join(variants, "\n ")), - Pos: module.pos, - }} + 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", + depName, module.Name(), + c.prettyPrintVariant(newVariant), + c.prettyPrintGroupVariants(possibleDeps)), + Pos: module.pos, + }} + } + + if module == foundDep { + return []error{&BlueprintError{ + Err: fmt.Errorf("%q depends on itself", depName), + Pos: module.pos, + }} + } + // AddVariationDependency allows adding a dependency on itself, but only if + // that module is earlier in the module list than this one, since we always + // run GenerateBuildActions in order for the variants of a module + if foundDep.group == module.group && beforeInModuleList(module, foundDep, module.group.modules) { + return []error{&BlueprintError{ + Err: fmt.Errorf("%q depends on later version of itself", depName), + Pos: module.pos, + }} + } + module.newDirectDeps = append(module.newDirectDeps, depInfo{foundDep, tag}) + atomic.AddUint32(&c.depsModified, 1) + return nil } func (c *Context) addInterVariantDependency(origModule *moduleInfo, tag DependencyTag, @@ -2166,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 { @@ -2182,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 @@ -2510,18 +2568,24 @@ type rename struct { } func (c *Context) moduleMatchingVariant(module *moduleInfo, name string) *moduleInfo { - targets := c.modulesFromName(name, module.namespace()) + group := c.moduleGroupFromName(name, module.namespace()) - if targets == nil { + if group == nil { return nil } - for _, m := range targets { + for _, m := range group.modules { if module.variantName == m.variantName { return m } } + for _, m := range group.aliases { + if module.variantName == m.variantName { + return m.target + } + } + return nil } @@ -2573,10 +2637,10 @@ func (c *Context) missingDependencyError(module *moduleInfo, depName string) (er } } -func (c *Context) modulesFromName(name string, namespace Namespace) []*moduleInfo { +func (c *Context) moduleGroupFromName(name string, namespace Namespace) *moduleGroup { group, exists := c.nameInterface.ModuleFromName(name, namespace) if exists { - return group.modules + return group.moduleGroup } return nil } diff --git a/context_test.go b/context_test.go index 0d783dc..61e628e 100644 --- a/context_test.go +++ b/context_test.go @@ -188,7 +188,7 @@ func TestWalkDeps(t *testing.T) { var outputDown string var outputUp string - topModule := ctx.modulesFromName("A", nil)[0] + topModule := ctx.moduleGroupFromName("A", nil).modules[0] ctx.walkDeps(topModule, false, func(dep depInfo, parent *moduleInfo) bool { outputDown += ctx.ModuleName(dep.module.logicModule) @@ -280,7 +280,7 @@ func TestWalkDepsDuplicates(t *testing.T) { var outputDown string var outputUp string - topModule := ctx.modulesFromName("A", nil)[0] + topModule := ctx.moduleGroupFromName("A", nil).modules[0] ctx.walkDeps(topModule, true, func(dep depInfo, parent *moduleInfo) bool { outputDown += ctx.ModuleName(dep.module.logicModule) @@ -334,10 +334,10 @@ func TestCreateModule(t *testing.T) { t.FailNow() } - a := ctx.modulesFromName("A", nil)[0].logicModule.(*fooModule) - b := ctx.modulesFromName("B", nil)[0].logicModule.(*barModule) - c := ctx.modulesFromName("C", nil)[0].logicModule.(*barModule) - d := ctx.modulesFromName("D", nil)[0].logicModule.(*fooModule) + a := ctx.moduleGroupFromName("A", nil).modules[0].logicModule.(*fooModule) + b := ctx.moduleGroupFromName("B", nil).modules[0].logicModule.(*barModule) + c := ctx.moduleGroupFromName("C", nil).modules[0].logicModule.(*barModule) + d := ctx.moduleGroupFromName("D", nil).modules[0].logicModule.(*fooModule) checkDeps := func(m Module, expected string) { var deps []string diff --git a/module_ctx.go b/module_ctx.go index be5d974..639cbf7 100644 --- a/module_ctx.go +++ b/module_ctx.go @@ -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 @@ -838,6 +845,9 @@ func (mctx *mutatorContext) createVariations(variationNames []string, local bool for i, module := range modules { ret = append(ret, module.logicModule) if !local { + if module.dependencyVariant == nil { + module.dependencyVariant = make(variationMap) + } module.dependencyVariant[mctx.name] = variationNames[i] } } @@ -854,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) } diff --git a/module_ctx_test.go b/module_ctx_test.go new file mode 100644 index 0000000..e7127ae --- /dev/null +++ b/module_ctx_test.go @@ -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") + }) +} diff --git a/visit_test.go b/visit_test.go index 873e72c..66a5295 100644 --- a/visit_test.go +++ b/visit_test.go @@ -149,13 +149,13 @@ func setupVisitTest(t *testing.T) *Context { func TestVisit(t *testing.T) { ctx := setupVisitTest(t) - topModule := ctx.modulesFromName("A", nil)[0].logicModule.(*visitModule) + topModule := ctx.moduleGroupFromName("A", nil).modules[0].logicModule.(*visitModule) assertString(t, topModule.properties.VisitDepsDepthFirst, "FEDCB") assertString(t, topModule.properties.VisitDepsDepthFirstIf, "FEDC") assertString(t, topModule.properties.VisitDirectDeps, "B") assertString(t, topModule.properties.VisitDirectDepsIf, "") - eModule := ctx.modulesFromName("E", nil)[0].logicModule.(*visitModule) + eModule := ctx.moduleGroupFromName("E", nil).modules[0].logicModule.(*visitModule) assertString(t, eModule.properties.VisitDepsDepthFirst, "F") assertString(t, eModule.properties.VisitDepsDepthFirstIf, "F") assertString(t, eModule.properties.VisitDirectDeps, "FF")