From 9aed5bc71560fe88d2f68b0d4a2fcbdeb7a846ba Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 28 Dec 2020 15:15:34 -0800 Subject: [PATCH 1/2] 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 --- android/Android.bp | 2 + android/makevars.go | 2 - android/register.go | 33 ++++++- android/singleton_module.go | 146 ++++++++++++++++++++++++++++++ android/singleton_module_test.go | 149 +++++++++++++++++++++++++++++++ android/testing.go | 6 ++ cmd/soong_build/writedocs.go | 9 +- 7 files changed, 336 insertions(+), 11 deletions(-) create mode 100644 android/singleton_module.go create mode 100644 android/singleton_module_test.go diff --git a/android/Android.bp b/android/Android.bp index 69aa03738..efa70a980 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -59,6 +59,7 @@ bootstrap_go_package { "sandbox.go", "sdk.go", "singleton.go", + "singleton_module.go", "soong_config_modules.go", "test_suites.go", "testing.go", @@ -95,6 +96,7 @@ bootstrap_go_package { "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 546abcfb0..40c0ccded 100644 --- a/android/makevars.go +++ b/android/makevars.go @@ -134,8 +134,6 @@ func RegisterMakeVarsProvider(pctx PackageContext, provider MakeVarsProvider) { // 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 b26f9b97a..61889f6c2 100644 --- a/android/register.go +++ b/android/register.go @@ -16,6 +16,7 @@ package android import ( "fmt" + "reflect" "github.com/google/blueprint" ) @@ -26,6 +27,7 @@ type moduleType struct { } var moduleTypes []moduleType +var moduleTypesForDocs = map[string]reflect.Value{} type singleton struct { name string @@ -69,6 +71,16 @@ func SingletonFactoryAdaptor(ctx *Context, factory SingletonFactory) blueprint.S 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 @@ func ModuleTypeFactories() map[string]ModuleFactory { 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 InitRegistrationContext RegistrationContext = &initRegistrationContext{ 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 @@ func (ctx *initRegistrationContext) RegisterModuleType(name string, factory Modu } 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 000000000..235173809 --- /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 000000000..9232eb42e --- /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 6539063bc..a66b1e19f 100644 --- a/android/testing.go +++ b/android/testing.go @@ -103,6 +103,12 @@ func (ctx *TestContext) RegisterModuleType(name string, factory ModuleFactory) { 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 253979e41..f2c2c9b9c 100644 --- a/cmd/soong_build/writedocs.go +++ b/cmd/soong_build/writedocs.go @@ -20,7 +20,6 @@ import ( "html/template" "io/ioutil" "path/filepath" - "reflect" "sort" "github.com/google/blueprint/bootstrap" @@ -97,12 +96,8 @@ func moduleTypeDocsToTemplates(moduleTypeList []*bpdoc.ModuleType) []moduleTypeT } 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 { From b5f6fa678dbb3ee08b33941263821a26eacf4255 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 6 Jan 2021 17:05:04 -0800 Subject: [PATCH 2/2] Create LLNDK vendor variants when DeviceVndkVersion is not set The LLNDK vendor variants need to exist even when the VNDK is not being used in order for the next patch to list them once the global maps are removed. Bug: 176904285 Test: m checkbuild Change-Id: Ib29ede455d5b6a4b7d3f4685db8fba6d32025314 --- cc/cc_test.go | 17 +++++++++++++ cc/image.go | 59 +++++++++++++++++++-------------------------- cc/llndk_library.go | 12 +++++---- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/cc/cc_test.go b/cc/cc_test.go index 3502d5f36..be0391309 100644 --- a/cc/cc_test.go +++ b/cc/cc_test.go @@ -738,12 +738,29 @@ func TestVndkWhenVndkVersionIsNotSet(t *testing.T) { }, nocrt: true, } + + cc_library { + name: "libllndk", + llndk_stubs: "libllndk.llndk", + } + + llndk_library { + name: "libllndk.llndk", + symbol_file: "", + export_llndk_headers: ["libllndk_headers"], + } + + llndk_headers { + name: "libllndk_headers", + export_include_dirs: ["include"], + } `) checkVndkOutput(t, ctx, "vndk/vndk.libraries.txt", []string{ "LLNDK: libc.so", "LLNDK: libdl.so", "LLNDK: libft2.so", + "LLNDK: libllndk.so", "LLNDK: libm.so", "VNDK-SP: libc++.so", "VNDK-core: libvndk-private.so", diff --git a/cc/image.go b/cc/image.go index 12bd65b67..86c7e60a7 100644 --- a/cc/image.go +++ b/cc/image.go @@ -282,31 +282,34 @@ func (m *Module) ImageMutatorBegin(mctx android.BaseModuleContext) { productVndkVersion = platformVndkVersion } - if boardVndkVersion == "" { + _, isLLNDKLibrary := m.linker.(*llndkStubDecorator) + _, isLLNDKHeaders := m.linker.(*llndkHeadersDecorator) + lib := moduleLibraryInterface(m) + hasLLNDKStubs := lib != nil && lib.hasLLNDKStubs() + + if isLLNDKLibrary || isLLNDKHeaders || hasLLNDKStubs { + // This is an LLNDK library. The implementation of the library will be on /system, + // and vendor and product variants will be created with LLNDK stubs. + // The LLNDK libraries need vendor variants even if there is no VNDK. + // The obsolete llndk_library and llndk_headers modules also need the vendor variants + // so the cc_library LLNDK stubs can depend on them. + if hasLLNDKStubs { + coreVariantNeeded = true + } + if platformVndkVersion != "" { + vendorVariants = append(vendorVariants, platformVndkVersion) + productVariants = append(productVariants, platformVndkVersion) + } + if boardVndkVersion != "" { + vendorVariants = append(vendorVariants, boardVndkVersion) + } + if productVndkVersion != "" { + productVariants = append(productVariants, productVndkVersion) + } + } else if boardVndkVersion == "" { // If the device isn't compiling against the VNDK, we always // use the core mode. coreVariantNeeded = true - } else if _, ok := m.linker.(*llndkStubDecorator); ok { - // LL-NDK stubs only exist in the vendor and product variants, - // since the real libraries will be used in the core variant. - vendorVariants = append(vendorVariants, - platformVndkVersion, - boardVndkVersion, - ) - productVariants = append(productVariants, - platformVndkVersion, - productVndkVersion, - ) - } else if _, ok := m.linker.(*llndkHeadersDecorator); ok { - // ... and LL-NDK headers as well - vendorVariants = append(vendorVariants, - platformVndkVersion, - boardVndkVersion, - ) - productVariants = append(productVariants, - platformVndkVersion, - productVndkVersion, - ) } else if m.isSnapshotPrebuilt() { // Make vendor variants only for the versions in BOARD_VNDK_VERSION and // PRODUCT_EXTRA_VNDK_VERSIONS. @@ -364,18 +367,6 @@ func (m *Module) ImageMutatorBegin(mctx android.BaseModuleContext) { } else { vendorVariants = append(vendorVariants, platformVndkVersion) } - } else if lib := moduleLibraryInterface(m); lib != nil && lib.hasLLNDKStubs() { - // This is an LLNDK library. The implementation of the library will be on /system, - // and vendor and product variants will be created with LLNDK stubs. - coreVariantNeeded = true - vendorVariants = append(vendorVariants, - platformVndkVersion, - boardVndkVersion, - ) - productVariants = append(productVariants, - platformVndkVersion, - productVndkVersion, - ) } else { // This is either in /system (or similar: /data), or is a // modules built with the NDK. Modules built with the NDK diff --git a/cc/llndk_library.go b/cc/llndk_library.go index bd48501a3..a46b31c8b 100644 --- a/cc/llndk_library.go +++ b/cc/llndk_library.go @@ -161,6 +161,13 @@ type llndkHeadersDecorator struct { *libraryDecorator } +func (llndk *llndkHeadersDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps { + deps.HeaderLibs = append(deps.HeaderLibs, llndk.Properties.Llndk.Export_llndk_headers...) + deps.ReexportHeaderLibHeaders = append(deps.ReexportHeaderLibHeaders, + llndk.Properties.Llndk.Export_llndk_headers...) + return deps +} + // llndk_headers contains a set of c/c++ llndk headers files which are imported // by other soongs cc modules. func llndkHeadersFactory() android.Module { @@ -178,11 +185,6 @@ func llndkHeadersFactory() android.Module { module.installer = nil module.library = decorator - module.AddProperties( - &module.Properties, - &library.MutatedProperties, - &library.flagExporter.Properties) - module.Init() return module