123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- // 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 android
- // This file provides module types that implement wrapper module types that add conditionals on
- // Soong config variables.
- import (
- "fmt"
- "path/filepath"
- "reflect"
- "strings"
- "sync"
- "text/scanner"
- "github.com/google/blueprint"
- "github.com/google/blueprint/parser"
- "github.com/google/blueprint/proptools"
- "android/soong/android/soongconfig"
- )
- func init() {
- RegisterSoongConfigModuleBuildComponents(InitRegistrationContext)
- }
- func RegisterSoongConfigModuleBuildComponents(ctx RegistrationContext) {
- ctx.RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
- ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
- ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
- ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
- }
- var PrepareForTestWithSoongConfigModuleBuildComponents = FixtureRegisterWithContext(RegisterSoongConfigModuleBuildComponents)
- type soongConfigModuleTypeImport struct {
- ModuleBase
- properties soongConfigModuleTypeImportProperties
- }
- type soongConfigModuleTypeImportProperties struct {
- From string
- Module_types []string
- }
- // soong_config_module_type_import imports module types with conditionals on Soong config
- // variables from another Android.bp file. The imported module type will exist for all
- // modules after the import in the Android.bp file.
- //
- // Each soong_config_variable supports an additional value `conditions_default`. The properties
- // specified in `conditions_default` will only be used under the following conditions:
- // bool variable: the variable is unspecified or not set to a true value
- // value variable: the variable is unspecified
- // string variable: the variable is unspecified or the variable is set to a string unused in the
- // given module. For example, string variable `test` takes values: "a" and "b",
- // if the module contains a property `a` and `conditions_default`, when test=b,
- // the properties under `conditions_default` will be used. To specify that no
- // properties should be amended for `b`, you can set `b: {},`.
- //
- // For example, an Android.bp file could have:
- //
- // soong_config_module_type_import {
- // from: "device/acme/Android.bp",
- // module_types: ["acme_cc_defaults"],
- // }
- //
- // acme_cc_defaults {
- // name: "acme_defaults",
- // cflags: ["-DGENERIC"],
- // soong_config_variables: {
- // board: {
- // soc_a: {
- // cflags: ["-DSOC_A"],
- // },
- // soc_b: {
- // cflags: ["-DSOC_B"],
- // },
- // conditions_default: {
- // cflags: ["-DSOC_DEFAULT"],
- // },
- // },
- // feature: {
- // cflags: ["-DFEATURE"],
- // conditions_default: {
- // cflags: ["-DFEATURE_DEFAULT"],
- // },
- // },
- // width: {
- // cflags: ["-DWIDTH=%s"],
- // conditions_default: {
- // cflags: ["-DWIDTH=DEFAULT"],
- // },
- // },
- // },
- // }
- //
- // cc_library {
- // name: "libacme_foo",
- // defaults: ["acme_defaults"],
- // srcs: ["*.cpp"],
- // }
- //
- // And device/acme/Android.bp could have:
- //
- // soong_config_module_type {
- // name: "acme_cc_defaults",
- // module_type: "cc_defaults",
- // config_namespace: "acme",
- // variables: ["board"],
- // bool_variables: ["feature"],
- // value_variables: ["width"],
- // properties: ["cflags", "srcs"],
- // }
- //
- // soong_config_string_variable {
- // name: "board",
- // values: ["soc_a", "soc_b", "soc_c"],
- // }
- //
- // If an acme BoardConfig.mk file contained:
- // $(call add_sonng_config_namespace, acme)
- // $(call add_soong_config_var_value, acme, board, soc_a)
- // $(call add_soong_config_var_value, acme, feature, true)
- // $(call add_soong_config_var_value, acme, width, 200)
- //
- // Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
- //
- // Alternatively, if acme BoardConfig.mk file contained:
- //
- // SOONG_CONFIG_NAMESPACES += acme
- // SOONG_CONFIG_acme += \
- // board \
- // feature \
- //
- // SOONG_CONFIG_acme_feature := false
- //
- // Then libacme_foo would build with cflags:
- // "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
- //
- // Similarly, if acme BoardConfig.mk file contained:
- //
- // SOONG_CONFIG_NAMESPACES += acme
- // SOONG_CONFIG_acme += \
- // board \
- // feature \
- //
- // SOONG_CONFIG_acme_board := soc_c
- //
- // Then libacme_foo would build with cflags:
- // "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
- func SoongConfigModuleTypeImportFactory() Module {
- module := &soongConfigModuleTypeImport{}
- module.AddProperties(&module.properties)
- AddLoadHook(module, func(ctx LoadHookContext) {
- importModuleTypes(ctx, module.properties.From, module.properties.Module_types...)
- })
- initAndroidModuleBase(module)
- return module
- }
- func (m *soongConfigModuleTypeImport) Name() string {
- // The generated name is non-deterministic, but it does not
- // matter because this module does not emit any rules.
- return soongconfig.CanonicalizeToProperty(m.properties.From) +
- "soong_config_module_type_import_" + fmt.Sprintf("%p", m)
- }
- func (*soongConfigModuleTypeImport) Namespaceless() {}
- func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {}
- // Create dummy modules for soong_config_module_type and soong_config_*_variable
- type soongConfigModuleTypeModule struct {
- ModuleBase
- BazelModuleBase
- properties soongconfig.ModuleTypeProperties
- }
- // soong_config_module_type defines module types with conditionals on Soong config
- // variables. The new module type will exist for all modules after the definition
- // in an Android.bp file, and can be imported into other Android.bp files using
- // soong_config_module_type_import.
- //
- // Each soong_config_variable supports an additional value `conditions_default`. The properties
- // specified in `conditions_default` will only be used under the following conditions:
- //
- // bool variable: the variable is unspecified or not set to a true value
- // value variable: the variable is unspecified
- // string variable: the variable is unspecified or the variable is set to a string unused in the
- // given module. For example, string variable `test` takes values: "a" and "b",
- // if the module contains a property `a` and `conditions_default`, when test=b,
- // the properties under `conditions_default` will be used. To specify that no
- // properties should be amended for `b`, you can set `b: {},`.
- //
- // For example, an Android.bp file could have:
- //
- // soong_config_module_type {
- // name: "acme_cc_defaults",
- // module_type: "cc_defaults",
- // config_namespace: "acme",
- // variables: ["board"],
- // bool_variables: ["feature"],
- // value_variables: ["width"],
- // properties: ["cflags", "srcs"],
- // }
- //
- // soong_config_string_variable {
- // name: "board",
- // values: ["soc_a", "soc_b"],
- // }
- //
- // acme_cc_defaults {
- // name: "acme_defaults",
- // cflags: ["-DGENERIC"],
- // soong_config_variables: {
- // board: {
- // soc_a: {
- // cflags: ["-DSOC_A"],
- // },
- // soc_b: {
- // cflags: ["-DSOC_B"],
- // },
- // conditions_default: {
- // cflags: ["-DSOC_DEFAULT"],
- // },
- // },
- // feature: {
- // cflags: ["-DFEATURE"],
- // conditions_default: {
- // cflags: ["-DFEATURE_DEFAULT"],
- // },
- // },
- // width: {
- // cflags: ["-DWIDTH=%s"],
- // conditions_default: {
- // cflags: ["-DWIDTH=DEFAULT"],
- // },
- // },
- // },
- // }
- //
- // cc_library {
- // name: "libacme_foo",
- // defaults: ["acme_defaults"],
- // srcs: ["*.cpp"],
- // }
- //
- // If an acme BoardConfig.mk file contained:
- //
- // SOONG_CONFIG_NAMESPACES += acme
- // SOONG_CONFIG_acme += \
- // board \
- // feature \
- //
- // SOONG_CONFIG_acme_board := soc_a
- // SOONG_CONFIG_acme_feature := true
- // SOONG_CONFIG_acme_width := 200
- //
- // Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
- func SoongConfigModuleTypeFactory() Module {
- module := &soongConfigModuleTypeModule{}
- module.AddProperties(&module.properties)
- AddLoadHook(module, func(ctx LoadHookContext) {
- // A soong_config_module_type module should implicitly import itself.
- importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name)
- })
- initAndroidModuleBase(module)
- return module
- }
- func (m *soongConfigModuleTypeModule) Name() string {
- return m.properties.Name + fmt.Sprintf("%p", m)
- }
- func (*soongConfigModuleTypeModule) Namespaceless() {}
- func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
- type soongConfigStringVariableDummyModule struct {
- ModuleBase
- properties soongconfig.VariableProperties
- stringProperties soongconfig.StringVariableProperties
- }
- type soongConfigBoolVariableDummyModule struct {
- ModuleBase
- properties soongconfig.VariableProperties
- }
- // soong_config_string_variable defines a variable and a set of possible string values for use
- // in a soong_config_module_type definition.
- func SoongConfigStringVariableDummyFactory() Module {
- module := &soongConfigStringVariableDummyModule{}
- module.AddProperties(&module.properties, &module.stringProperties)
- initAndroidModuleBase(module)
- return module
- }
- // soong_config_string_variable defines a variable with true or false values for use
- // in a soong_config_module_type definition.
- func SoongConfigBoolVariableDummyFactory() Module {
- module := &soongConfigBoolVariableDummyModule{}
- module.AddProperties(&module.properties)
- initAndroidModuleBase(module)
- return module
- }
- func (m *soongConfigStringVariableDummyModule) Name() string {
- return m.properties.Name + fmt.Sprintf("%p", m)
- }
- func (*soongConfigStringVariableDummyModule) Namespaceless() {}
- func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
- func (m *soongConfigBoolVariableDummyModule) Name() string {
- return m.properties.Name + fmt.Sprintf("%p", m)
- }
- func (*soongConfigBoolVariableDummyModule) Namespaceless() {}
- func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
- // importModuleTypes registers the module factories for a list of module types defined
- // in an Android.bp file. These module factories are scoped for the current Android.bp
- // file only.
- func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
- from = filepath.Clean(from)
- if filepath.Ext(from) != ".bp" {
- ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from)
- return
- }
- if strings.HasPrefix(from, "../") {
- ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree",
- from)
- return
- }
- moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from)
- if moduleTypeDefinitions == nil {
- return
- }
- for _, moduleType := range moduleTypes {
- if factory, ok := moduleTypeDefinitions[moduleType]; ok {
- ctx.registerScopedModuleType(moduleType, factory)
- } else {
- ctx.PropertyErrorf("module_types", "module type %q not defined in %q",
- moduleType, from)
- }
- }
- }
- // loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
- // result so each file is only parsed once.
- func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory {
- type onceKeyType string
- key := NewCustomOnceKey(onceKeyType(filepath.Clean(from)))
- reportErrors := func(ctx LoadHookContext, filename string, errs ...error) {
- for _, err := range errs {
- if parseErr, ok := err.(*parser.ParseError); ok {
- ctx.Errorf(parseErr.Pos, "%s", parseErr.Err)
- } else {
- ctx.Errorf(scanner.Position{Filename: filename}, "%s", err)
- }
- }
- }
- return ctx.Config().Once(key, func() interface{} {
- ctx.AddNinjaFileDeps(from)
- r, err := ctx.Config().fs.Open(from)
- if err != nil {
- ctx.PropertyErrorf("from", "failed to open %q: %s", from, err)
- return (map[string]blueprint.ModuleFactory)(nil)
- }
- defer r.Close()
- mtDef, errs := soongconfig.Parse(r, from)
- if len(errs) > 0 {
- reportErrors(ctx, from, errs...)
- return (map[string]blueprint.ModuleFactory)(nil)
- }
- if ctx.Config().BuildMode == Bp2build {
- ctx.Config().Bp2buildSoongConfigDefinitions.AddVars(mtDef)
- }
- globalModuleTypes := ctx.moduleFactories()
- factories := make(map[string]blueprint.ModuleFactory)
- for name, moduleType := range mtDef.ModuleTypes {
- factory := globalModuleTypes[moduleType.BaseModuleType]
- if factory != nil {
- factories[name] = configModuleFactory(factory, moduleType, ctx.Config().BuildMode == Bp2build)
- } else {
- reportErrors(ctx, from,
- fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
- }
- }
- if ctx.Failed() {
- return (map[string]blueprint.ModuleFactory)(nil)
- }
- return factories
- }).(map[string]blueprint.ModuleFactory)
- }
- // tracingConfig is a wrapper to soongconfig.SoongConfig which records all accesses to SoongConfig.
- type tracingConfig struct {
- config soongconfig.SoongConfig
- boolSet map[string]bool
- stringSet map[string]string
- isSetSet map[string]bool
- }
- func (c *tracingConfig) Bool(name string) bool {
- c.boolSet[name] = c.config.Bool(name)
- return c.boolSet[name]
- }
- func (c *tracingConfig) String(name string) string {
- c.stringSet[name] = c.config.String(name)
- return c.stringSet[name]
- }
- func (c *tracingConfig) IsSet(name string) bool {
- c.isSetSet[name] = c.config.IsSet(name)
- return c.isSetSet[name]
- }
- func (c *tracingConfig) getTrace() soongConfigTrace {
- ret := soongConfigTrace{}
- for k, v := range c.boolSet {
- ret.Bools = append(ret.Bools, fmt.Sprintf("%q:%t", k, v))
- }
- for k, v := range c.stringSet {
- ret.Strings = append(ret.Strings, fmt.Sprintf("%q:%q", k, v))
- }
- for k, v := range c.isSetSet {
- ret.IsSets = append(ret.IsSets, fmt.Sprintf("%q:%t", k, v))
- }
- return ret
- }
- func newTracingConfig(config soongconfig.SoongConfig) *tracingConfig {
- c := tracingConfig{
- config: config,
- boolSet: make(map[string]bool),
- stringSet: make(map[string]string),
- isSetSet: make(map[string]bool),
- }
- return &c
- }
- var _ soongconfig.SoongConfig = (*tracingConfig)(nil)
- // configModuleFactory takes an existing soongConfigModuleFactory and a
- // ModuleType to create a new ModuleFactory that uses a custom loadhook.
- func configModuleFactory(factory blueprint.ModuleFactory, moduleType *soongconfig.ModuleType, bp2build bool) blueprint.ModuleFactory {
- // Defer creation of conditional properties struct until the first call from the factory
- // method. That avoids having to make a special call to the factory to create the properties
- // structs from which the conditional properties struct is created. This is needed in order to
- // allow singleton modules to be customized by soong_config_module_type as the
- // SingletonModuleFactoryAdaptor factory registers a load hook for the singleton module
- // everytime that it is called. Calling the factory twice causes a build failure as the load
- // hook is called twice, the first time it updates the singleton module to indicate that it has
- // been registered as a module, and the second time it fails because it thinks it has been
- // registered again and a singleton module can only be registered once.
- //
- // This is an issue for singleton modules because:
- // * Load hooks are registered on the module object and are only called when the module object
- // is created by Blueprint while processing the Android.bp file.
- // * The module factory for a singleton module returns the same module object each time it is
- // called, and registers its load hook on that same module object.
- // * When the module factory is called by Blueprint it then calls all the load hooks that have
- // been registered for every call to that module factory.
- //
- // It is not an issue for normal modules because they return a new module object each time the
- // factory is called and so any load hooks registered on module objects which are discarded will
- // not be run.
- once := &sync.Once{}
- conditionalFactoryProps := reflect.Value{}
- getConditionalFactoryProps := func(props []interface{}) reflect.Value {
- once.Do(func() {
- conditionalFactoryProps = soongconfig.CreateProperties(props, moduleType)
- })
- return conditionalFactoryProps
- }
- return func() (blueprint.Module, []interface{}) {
- module, props := factory()
- conditionalFactoryProps := getConditionalFactoryProps(props)
- if !conditionalFactoryProps.IsValid() {
- return module, props
- }
- conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
- props = append(props, conditionalProps.Interface())
- if bp2build {
- // The loadhook is different for bp2build, since we don't want to set a specific
- // set of property values based on a vendor var -- we want __all of them__ to
- // generate select statements, so we put the entire soong_config_variables
- // struct, together with the namespace representing those variables, while
- // creating the custom module with the factory.
- AddLoadHook(module, func(ctx LoadHookContext) {
- if m, ok := module.(Bazelable); ok {
- m.SetBaseModuleType(moduleType.BaseModuleType)
- // Instead of applying all properties, keep the entire conditionalProps struct as
- // part of the custom module so dependent modules can create the selects accordingly
- m.setNamespacedVariableProps(namespacedVariableProperties{
- moduleType.ConfigNamespace: []interface{}{conditionalProps.Interface()},
- })
- }
- })
- } else {
- // Regular Soong operation wraps the existing module factory with a
- // conditional on Soong config variables by reading the product
- // config variables from Make.
- AddLoadHook(module, func(ctx LoadHookContext) {
- tracingConfig := newTracingConfig(ctx.Config().VendorConfig(moduleType.ConfigNamespace))
- newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, tracingConfig)
- if err != nil {
- ctx.ModuleErrorf("%s", err)
- return
- }
- for _, ps := range newProps {
- ctx.AppendProperties(ps)
- }
- module.(Module).base().commonProperties.SoongConfigTrace = tracingConfig.getTrace()
- })
- }
- return module, props
- }
- }
|