From 4adcf848084565c131a8dd5271a260d8b5234e69 Mon Sep 17 00:00:00 2001 From: Spandan Das Date: Tue, 8 Nov 2022 19:44:01 +0000 Subject: [PATCH] Conditional inclusion of blueprint modules Bug: 270654958 This introduces a new `blueprint_package_includes` module type. If present, other blueprint modules in that file will be analyzed if and only if the requested include tags are met example syntax: ``` Android.bp blueprint_packgage_includes { match_all: ["tag1", "tag2", ...], } other_module1 {...} other_module2 {...} ``` other_module1 and other_module2 will not be analyzed unless tag1,tag2, ... are set This also adds a new object of type `IncludeTags` to the Context object, which is a container for these string keys. Test: In build/blueprint, go test ./ Test: TH Change-Id: I79de0d7da3224a5b2025c27a5137f39d00c7382e Merged-In: I79de0d7da3224a5b2025c27a5137f39d00c7382e (cherry picked from commit fd1f976100fb78d50902bd6feb550e92f70fa962) --- bootstrap/command.go | 1 + context.go | 108 +++++++++++++++++++++++++++++++++++++++++++ context_test.go | 71 ++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) diff --git a/bootstrap/command.go b/bootstrap/command.go index 8c045b4..64bcce0 100644 --- a/bootstrap/command.go +++ b/bootstrap/command.go @@ -92,6 +92,7 @@ func RunBlueprint(args Args, stopBefore StopBefore, ctx *blueprint.Context, conf ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory()) ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory()) ctx.RegisterSingletonType("bootstrap", newSingletonFactory()) + blueprint.RegisterPackageIncludesModuleType(ctx) ctx.BeginEvent("parse_bp") blueprintFiles, errs := ctx.ParseFileList(".", filesToParse, config) diff --git a/context.go b/context.go index 6496948..1296eed 100644 --- a/context.go +++ b/context.go @@ -135,6 +135,31 @@ type Context struct { // Can be set by tests to avoid invalidating Module values after mutators. skipCloneModulesAfterMutators bool + + // String values that can be used to gate build graph traversal + includeTags *IncludeTags +} + +// A container for String keys. The keys can be used to gate build graph traversal +type IncludeTags map[string]bool + +func (tags *IncludeTags) Add(names ...string) { + for _, name := range names { + (*tags)[name] = true + } +} + +func (tags *IncludeTags) Contains(tag string) bool { + _, exists := (*tags)[tag] + return exists +} + +func (c *Context) AddIncludeTags(names ...string) { + c.includeTags.Add(names...) +} + +func (c *Context) ContainsIncludeTag(name string) bool { + return c.includeTags.Contains(name) } // An Error describes a problem that was encountered that is related to a @@ -392,6 +417,7 @@ func newContext() *Context { globs: make(map[globKey]pathtools.GlobResult), fs: pathtools.OsFs, finishedMutators: make(map[*mutatorInfo]bool), + includeTags: &IncludeTags{}, outDir: nil, requiredNinjaMajor: 1, requiredNinjaMinor: 7, @@ -709,6 +735,33 @@ func (c *Context) ParseBlueprintsFiles(rootFile string, return c.ParseFileList(baseDir, pathsToParse, config) } +// Returns a boolean for whether this file should be analyzed +// Evaluates to true if the file either +// 1. does not contain a blueprint_package_includes +// 2. contains a blueprint_package_includes and all requested tags are set +// This should be processed before adding any modules to the build graph +func shouldVisitFile(c *Context, file *parser.File) (bool, []error) { + for _, def := range file.Defs { + switch def := def.(type) { + case *parser.Module: + if def.Type != "blueprint_package_includes" { + continue + } + module, errs := processModuleDef(def, file.Name, c.moduleFactories, nil, c.ignoreUnknownModuleTypes) + if len(errs) > 0 { + // This file contains errors in blueprint_package_includes + // Visit anyways so that we can report errors on other modules in the file + return true, errs + } + logicModule, _ := c.cloneLogicModule(module) + pi := logicModule.(*PackageIncludes) + return pi.MatchesIncludeTags(c), []error{} + } + } + return true, []error{} +} + + func (c *Context) ParseFileList(rootDir string, filePaths []string, config interface{}) (deps []string, errs []error) { @@ -761,6 +814,15 @@ func (c *Context) ParseFileList(rootDir string, filePaths []string, } return nil } + shouldVisit, errs := shouldVisitFile(c, file) + if len(errs) > 0 { + atomic.AddUint32(&numErrs, uint32(len(errs))) + errsCh <- errs + } + if !shouldVisit { + // TODO: Write a file that lists the skipped bp files + return + } for _, def := range file.Defs { switch def := def.(type) { @@ -4230,3 +4292,49 @@ var singletonHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # Singleton: {{.name}} Factory: {{.goFactory}} ` + +// Blueprint module type that can be used to gate blueprint files beneath this directory +type PackageIncludes struct { + properties struct { + // Package will be included if all include tags in this list are set + Match_all []string + } + name *string `blueprint:"mutated"` +} + +func (pi *PackageIncludes) Name() string { + return proptools.String(pi.name) +} + +// This module type does not have any build actions +func (pi *PackageIncludes) GenerateBuildActions(ctx ModuleContext) { +} + +func newPackageIncludesFactory() (Module, []interface{}) { + module := &PackageIncludes{} + AddLoadHook(module, func(ctx LoadHookContext) { + module.name = proptools.StringPtr(ctx.ModuleDir() + "_includes") // Generate a synthetic name + }) + return module, []interface{}{&module.properties} +} + +func RegisterPackageIncludesModuleType(ctx *Context) { + ctx.RegisterModuleType("blueprint_package_includes", newPackageIncludesFactory) +} + +func (pi *PackageIncludes) MatchAll() []string { + return pi.properties.Match_all +} + +// Returns true if all requested include tags are set in the Context object +func (pi *PackageIncludes) MatchesIncludeTags(ctx *Context) bool { + if len(pi.MatchAll()) == 0 { + ctx.ModuleErrorf(pi, "Match_all must be a non-empty list") + } + for _, includeTag := range pi.MatchAll() { + if !ctx.ContainsIncludeTag(includeTag) { + return false + } + } + return true +} diff --git a/context_test.go b/context_test.go index 6308ba9..5b045bd 100644 --- a/context_test.go +++ b/context_test.go @@ -18,6 +18,7 @@ import ( "bytes" "errors" "fmt" + "path/filepath" "reflect" "strings" "sync" @@ -1084,3 +1085,73 @@ func Test_parallelVisit(t *testing.T) { } }) } + +func TestPackageIncludes(t *testing.T) { + dir1_foo_bp := ` + blueprint_package_includes { + match_all: ["use_dir1"], + } + foo_module { + name: "foo", + } + ` + dir2_foo_bp := ` + blueprint_package_includes { + match_all: ["use_dir2"], + } + foo_module { + name: "foo", + } + ` + mockFs := map[string][]byte{ + "dir1/Android.bp": []byte(dir1_foo_bp), + "dir2/Android.bp": []byte(dir2_foo_bp), + } + testCases := []struct{ + desc string + includeTags []string + expectedDir string + expectedErr string + }{ + { + desc: "use_dir1 is set, use dir1 foo", + includeTags: []string{"use_dir1"}, + expectedDir: "dir1", + }, + { + desc: "use_dir2 is set, use dir2 foo", + includeTags: []string{"use_dir2"}, + expectedDir: "dir2", + }, + { + desc: "duplicate module error if both use_dir1 and use_dir2 are set", + includeTags: []string{"use_dir1", "use_dir2"}, + expectedDir: "", + expectedErr: `module "foo" already defined`, + }, + } + for _, tc := range testCases { + ctx := NewContext() + // Register mock FS + ctx.MockFileSystem(mockFs) + // Register module types + ctx.RegisterModuleType("foo_module", newFooModule) + RegisterPackageIncludesModuleType(ctx) + // Add include tags for test case + ctx.AddIncludeTags(tc.includeTags...) + // Run test + _, actualErrs := ctx.ParseFileList(".", []string{"dir1/Android.bp", "dir2/Android.bp"}, nil) + // Evaluate + if !strings.Contains(fmt.Sprintf("%s", actualErrs), fmt.Sprintf("%s", tc.expectedErr)) { + t.Errorf("Expected errors: %s, got errors: %s\n", tc.expectedErr, actualErrs) + } + if tc.expectedErr != "" { + continue // expectedDir check not necessary + } + actualBpFile := ctx.moduleGroupFromName("foo", nil).modules.firstModule().relBlueprintsFile + if tc.expectedDir != filepath.Dir(actualBpFile) { + t.Errorf("Expected foo from %s, got %s\n", tc.expectedDir, filepath.Dir(actualBpFile)) + } + } + +}