Add a new SingletonModule type
A SingletonModule is halfway between a Singleton and a Module. It has
access to visiting other modules via its GenerateSingletonBuildActions
method, but must be defined in an Android.bp file and can also be
depended on like a module.
Bug: 176904285
Test: singleton_module_test.go
Change-Id: I1b2bfdfb3927c1eabf431c53213cb7c581e33ca4
diff --git a/android/Android.bp b/android/Android.bp
index 69aa037..efa70a9 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -59,6 +59,7 @@
"sandbox.go",
"sdk.go",
"singleton.go",
+ "singleton_module.go",
"soong_config_modules.go",
"test_suites.go",
"testing.go",
@@ -95,6 +96,7 @@
"paths_test.go",
"prebuilt_test.go",
"rule_builder_test.go",
+ "singleton_module_test.go",
"soong_config_modules_test.go",
"util_test.go",
"variable_test.go",
diff --git a/android/makevars.go b/android/makevars.go
index 546abcf..40c0ccd 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -134,8 +134,6 @@
// SingletonMakeVarsProvider is a Singleton with an extra method to provide extra values to be exported to Make.
type SingletonMakeVarsProvider interface {
- Singleton
-
// MakeVars uses a MakeVarsContext to provide extra values to be exported to Make.
MakeVars(ctx MakeVarsContext)
}
diff --git a/android/register.go b/android/register.go
index b26f9b9..61889f6 100644
--- a/android/register.go
+++ b/android/register.go
@@ -16,6 +16,7 @@
import (
"fmt"
+ "reflect"
"github.com/google/blueprint"
)
@@ -26,6 +27,7 @@
}
var moduleTypes []moduleType
+var moduleTypesForDocs = map[string]reflect.Value{}
type singleton struct {
name string
@@ -69,6 +71,16 @@
func RegisterModuleType(name string, factory ModuleFactory) {
moduleTypes = append(moduleTypes, moduleType{name, factory})
+ RegisterModuleTypeForDocs(name, reflect.ValueOf(factory))
+}
+
+// RegisterModuleTypeForDocs associates a module type name with a reflect.Value of the factory
+// function that has documentation for the module type. It is normally called automatically
+// by RegisterModuleType, but can be called manually after RegisterModuleType in order to
+// override the factory method used for documentation, for example if the method passed to
+// RegisterModuleType was a lambda.
+func RegisterModuleTypeForDocs(name string, factory reflect.Value) {
+ moduleTypesForDocs[name] = factory
}
func RegisterSingletonType(name string, factory SingletonFactory) {
@@ -142,12 +154,17 @@
return ret
}
+func ModuleTypeFactoriesForDocs() map[string]reflect.Value {
+ return moduleTypesForDocs
+}
+
// Interface for registering build components.
//
// Provided to allow registration of build components to be shared between the runtime
// and test environments.
type RegistrationContext interface {
RegisterModuleType(name string, factory ModuleFactory)
+ RegisterSingletonModuleType(name string, factory SingletonModuleFactory)
RegisterSingletonType(name string, factory SingletonFactory)
PreArchMutators(f RegisterMutatorFunc)
@@ -186,8 +203,9 @@
var _ RegistrationContext = (*TestContext)(nil)
type initRegistrationContext struct {
- moduleTypes map[string]ModuleFactory
- singletonTypes map[string]SingletonFactory
+ moduleTypes map[string]ModuleFactory
+ singletonTypes map[string]SingletonFactory
+ moduleTypesForDocs map[string]reflect.Value
}
func (ctx *initRegistrationContext) RegisterModuleType(name string, factory ModuleFactory) {
@@ -196,6 +214,17 @@
}
ctx.moduleTypes[name] = factory
RegisterModuleType(name, factory)
+ RegisterModuleTypeForDocs(name, reflect.ValueOf(factory))
+}
+
+func (ctx *initRegistrationContext) RegisterSingletonModuleType(name string, factory SingletonModuleFactory) {
+ s, m := SingletonModuleFactoryAdaptor(name, factory)
+ ctx.RegisterSingletonType(name, s)
+ ctx.RegisterModuleType(name, m)
+ // Overwrite moduleTypesForDocs with the original factory instead of the lambda returned by
+ // SingletonModuleFactoryAdaptor so that docs can find the module type documentation on the
+ // factory method.
+ RegisterModuleTypeForDocs(name, reflect.ValueOf(factory))
}
func (ctx *initRegistrationContext) RegisterSingletonType(name string, factory SingletonFactory) {
diff --git a/android/singleton_module.go b/android/singleton_module.go
new file mode 100644
index 0000000..2351738
--- /dev/null
+++ b/android/singleton_module.go
@@ -0,0 +1,146 @@
+// 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 android
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/google/blueprint"
+)
+
+// A SingletonModule is halfway between a Singleton and a Module. It has access to visiting
+// other modules via its GenerateSingletonBuildActions method, but must be defined in an Android.bp
+// file and can also be depended on like a module. It must be used zero or one times in an
+// Android.bp file, and it can only have a single variant.
+//
+// The SingletonModule's GenerateAndroidBuildActions method will be called before any normal or
+// singleton module that depends on it, but its GenerateSingletonBuildActions method will be called
+// after all modules, in registration order with other singletons and singleton modules.
+// GenerateAndroidBuildActions and GenerateSingletonBuildActions will not be called if the
+// SingletonModule was not instantiated in an Android.bp file.
+//
+// Since the SingletonModule rules likely depend on the modules visited during
+// GenerateSingletonBuildActions, the GenerateAndroidBuildActions is unlikely to produce any
+// rules directly. Instead, it will probably set some providers to paths that will later have rules
+// generated to produce them in GenerateSingletonBuildActions.
+//
+// The expected use case for a SingletonModule is a module that produces files that depend on all
+// modules in the tree and will be used by other modules. For example it could produce a text
+// file that lists all modules that meet a certain criteria, and that text file could be an input
+// to another module. Care must be taken that the ninja rules produced by the SingletonModule
+// don't produce a cycle by referencing output files of rules of modules that depend on the
+// SingletonModule.
+//
+// A SingletonModule must embed a SingletonModuleBase struct, and its factory method must be
+// registered with RegisterSingletonModuleType from an init() function.
+//
+// A SingletonModule can also implement SingletonMakeVarsProvider to export values to Make.
+type SingletonModule interface {
+ Module
+ GenerateSingletonBuildActions(SingletonContext)
+ singletonModuleBase() *SingletonModuleBase
+}
+
+// SingletonModuleBase must be embedded into implementers of the SingletonModule interface.
+type SingletonModuleBase struct {
+ ModuleBase
+
+ lock sync.Mutex
+ bp string
+ variant string
+}
+
+// GenerateBuildActions wraps the ModuleBase GenerateBuildActions method, verifying it was only
+// called once to prevent multiple variants of a SingletonModule.
+func (smb *SingletonModuleBase) GenerateBuildActions(ctx blueprint.ModuleContext) {
+ smb.lock.Lock()
+ if smb.variant != "" {
+ ctx.ModuleErrorf("GenerateAndroidBuildActions already called for variant %q, SingletonModules can only have one variant", smb.variant)
+ }
+ smb.variant = ctx.ModuleSubDir()
+ smb.lock.Unlock()
+
+ smb.ModuleBase.GenerateBuildActions(ctx)
+}
+
+// InitAndroidSingletonModule must be called from the SingletonModule's factory function to
+// initialize SingletonModuleBase.
+func InitAndroidSingletonModule(sm SingletonModule) {
+ InitAndroidModule(sm)
+}
+
+// singletonModuleBase retrieves the embedded SingletonModuleBase from a SingletonModule.
+func (smb *SingletonModuleBase) singletonModuleBase() *SingletonModuleBase { return smb }
+
+// SingletonModuleFactory is a factory method that returns a SingletonModule.
+type SingletonModuleFactory func() SingletonModule
+
+// SingletonModuleFactoryAdaptor converts a SingletonModuleFactory into a SingletonFactory and a
+// ModuleFactory.
+func SingletonModuleFactoryAdaptor(name string, factory SingletonModuleFactory) (SingletonFactory, ModuleFactory) {
+ // The sm variable acts as a static holder of the only SingletonModule instance. Calls to the
+ // returned SingletonFactory and ModuleFactory lambdas will always return the same sm value.
+ // The SingletonFactory is only expected to be called once, but the ModuleFactory may be
+ // called multiple times if the module is replaced with a clone of itself at the end of
+ // blueprint.ResolveDependencies.
+ var sm SingletonModule
+ s := func() Singleton {
+ sm = factory()
+ return &singletonModuleSingletonAdaptor{sm}
+ }
+ m := func() Module {
+ if sm == nil {
+ panic(fmt.Errorf("Singleton %q for SingletonModule was not instantiated", name))
+ }
+
+ // Check for multiple uses of a SingletonModule in a LoadHook. Checking directly in the
+ // factory would incorrectly flag when the factory was called again when the module is
+ // replaced with a clone of itself at the end of blueprint.ResolveDependencies.
+ AddLoadHook(sm, func(ctx LoadHookContext) {
+ smb := sm.singletonModuleBase()
+ smb.lock.Lock()
+ defer smb.lock.Unlock()
+ if smb.bp != "" {
+ ctx.ModuleErrorf("Duplicate SingletonModule %q, previously used in %s", name, smb.bp)
+ }
+ smb.bp = ctx.BlueprintsFile()
+ })
+ return sm
+ }
+ return s, m
+}
+
+// singletonModuleSingletonAdaptor makes a SingletonModule into a Singleton by translating the
+// GenerateSingletonBuildActions method to Singleton.GenerateBuildActions.
+type singletonModuleSingletonAdaptor struct {
+ sm SingletonModule
+}
+
+// GenerateBuildActions calls the SingletonModule's GenerateSingletonBuildActions method, but only
+// if the module was defined in an Android.bp file.
+func (smsa *singletonModuleSingletonAdaptor) GenerateBuildActions(ctx SingletonContext) {
+ if smsa.sm.singletonModuleBase().bp != "" {
+ smsa.sm.GenerateSingletonBuildActions(ctx)
+ }
+}
+
+func (smsa *singletonModuleSingletonAdaptor) MakeVars(ctx MakeVarsContext) {
+ if smsa.sm.singletonModuleBase().bp != "" {
+ if makeVars, ok := smsa.sm.(SingletonMakeVarsProvider); ok {
+ makeVars.MakeVars(ctx)
+ }
+ }
+}
diff --git a/android/singleton_module_test.go b/android/singleton_module_test.go
new file mode 100644
index 0000000..9232eb4
--- /dev/null
+++ b/android/singleton_module_test.go
@@ -0,0 +1,149 @@
+// Copyright 2021 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 android
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+type testSingletonModule struct {
+ SingletonModuleBase
+ ops []string
+}
+
+func (tsm *testSingletonModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+ tsm.ops = append(tsm.ops, "GenerateAndroidBuildActions")
+}
+
+func (tsm *testSingletonModule) GenerateSingletonBuildActions(ctx SingletonContext) {
+ tsm.ops = append(tsm.ops, "GenerateSingletonBuildActions")
+}
+
+func (tsm *testSingletonModule) MakeVars(ctx MakeVarsContext) {
+ tsm.ops = append(tsm.ops, "MakeVars")
+}
+
+func testSingletonModuleFactory() SingletonModule {
+ tsm := &testSingletonModule{}
+ InitAndroidSingletonModule(tsm)
+ return tsm
+}
+
+func runSingletonModuleTest(bp string) (*TestContext, []error) {
+ config := TestConfig(buildDir, nil, bp, nil)
+ // Enable Kati output to test SingletonModules with MakeVars.
+ config.katiEnabled = true
+ ctx := NewTestContext(config)
+ ctx.RegisterSingletonModuleType("test_singleton_module", testSingletonModuleFactory)
+ ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc)
+ ctx.Register()
+
+ _, errs := ctx.ParseBlueprintsFiles("Android.bp")
+ if len(errs) > 0 {
+ return ctx, errs
+ }
+
+ _, errs = ctx.PrepareBuildActions(config)
+ return ctx, errs
+}
+
+func TestSingletonModule(t *testing.T) {
+ bp := `
+ test_singleton_module {
+ name: "test_singleton_module",
+ }
+ `
+ ctx, errs := runSingletonModuleTest(bp)
+ if len(errs) > 0 {
+ t.Fatal(errs)
+ }
+
+ ops := ctx.ModuleForTests("test_singleton_module", "").Module().(*testSingletonModule).ops
+ wantOps := []string{"GenerateAndroidBuildActions", "GenerateSingletonBuildActions", "MakeVars"}
+ if !reflect.DeepEqual(ops, wantOps) {
+ t.Errorf("Expected operations %q, got %q", wantOps, ops)
+ }
+}
+
+func TestDuplicateSingletonModule(t *testing.T) {
+ bp := `
+ test_singleton_module {
+ name: "test_singleton_module",
+ }
+
+ test_singleton_module {
+ name: "test_singleton_module2",
+ }
+ `
+ _, errs := runSingletonModuleTest(bp)
+ if len(errs) == 0 {
+ t.Fatal("expected duplicate SingletonModule error")
+ }
+ if len(errs) != 1 || !strings.Contains(errs[0].Error(), `Duplicate SingletonModule "test_singleton_module", previously used in`) {
+ t.Fatalf("expected duplicate SingletonModule error, got %q", errs)
+ }
+}
+
+func TestUnusedSingletonModule(t *testing.T) {
+ bp := ``
+ ctx, errs := runSingletonModuleTest(bp)
+ if len(errs) > 0 {
+ t.Fatal(errs)
+ }
+
+ singleton := ctx.SingletonForTests("test_singleton_module").Singleton()
+ sm := singleton.(*singletonModuleSingletonAdaptor).sm
+ ops := sm.(*testSingletonModule).ops
+ if ops != nil {
+ t.Errorf("Expected no operations, got %q", ops)
+ }
+}
+
+func testVariantSingletonModuleMutator(ctx BottomUpMutatorContext) {
+ if _, ok := ctx.Module().(*testSingletonModule); ok {
+ ctx.CreateVariations("a", "b")
+ }
+}
+
+func TestVariantSingletonModule(t *testing.T) {
+ bp := `
+ test_singleton_module {
+ name: "test_singleton_module",
+ }
+ `
+
+ config := TestConfig(buildDir, nil, bp, nil)
+ ctx := NewTestContext(config)
+ ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+ ctx.BottomUp("test_singleton_module_mutator", testVariantSingletonModuleMutator)
+ })
+ ctx.RegisterSingletonModuleType("test_singleton_module", testSingletonModuleFactory)
+ ctx.Register()
+
+ _, errs := ctx.ParseBlueprintsFiles("Android.bp")
+
+ if len(errs) == 0 {
+ _, errs = ctx.PrepareBuildActions(config)
+ }
+
+ if len(errs) == 0 {
+ t.Fatal("expected duplicate SingletonModule error")
+ }
+ if len(errs) != 1 || !strings.Contains(errs[0].Error(), `GenerateAndroidBuildActions already called for variant`) {
+ t.Fatalf("expected duplicate SingletonModule error, got %q", errs)
+ }
+}
diff --git a/android/testing.go b/android/testing.go
index 6539063..a66b1e1 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -103,6 +103,12 @@
ctx.Context.RegisterModuleType(name, ModuleFactoryAdaptor(factory))
}
+func (ctx *TestContext) RegisterSingletonModuleType(name string, factory SingletonModuleFactory) {
+ s, m := SingletonModuleFactoryAdaptor(name, factory)
+ ctx.RegisterSingletonType(name, s)
+ ctx.RegisterModuleType(name, m)
+}
+
func (ctx *TestContext) RegisterSingletonType(name string, factory SingletonFactory) {
ctx.Context.RegisterSingletonType(name, SingletonFactoryAdaptor(ctx.Context, factory))
}
diff --git a/cmd/soong_build/writedocs.go b/cmd/soong_build/writedocs.go
index 253979e..f2c2c9b 100644
--- a/cmd/soong_build/writedocs.go
+++ b/cmd/soong_build/writedocs.go
@@ -20,7 +20,6 @@
"html/template"
"io/ioutil"
"path/filepath"
- "reflect"
"sort"
"github.com/google/blueprint/bootstrap"
@@ -97,12 +96,8 @@
}
func getPackages(ctx *android.Context) ([]*bpdoc.Package, error) {
- moduleTypeFactories := android.ModuleTypeFactories()
- bpModuleTypeFactories := make(map[string]reflect.Value)
- for moduleType, factory := range moduleTypeFactories {
- bpModuleTypeFactories[moduleType] = reflect.ValueOf(factory)
- }
- return bootstrap.ModuleTypeDocs(ctx.Context, bpModuleTypeFactories)
+ moduleTypeFactories := android.ModuleTypeFactoriesForDocs()
+ return bootstrap.ModuleTypeDocs(ctx.Context, moduleTypeFactories)
}
func writeDocs(ctx *android.Context, filename string) error {