// 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) ctx.RegisterModuleType("soong_config_value_variable", SoongConfigValueVariableDummyFactory) } 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 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 } type soongConfigValueVariableDummyModule 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 } // soong_config_value_variable defines a variable whose value can be expanded into // the value of a string property. func SoongConfigValueVariableDummyFactory() Module { module := &soongConfigValueVariableDummyModule{} 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) {} func (m *soongConfigValueVariableDummyModule) Name() string { return m.properties.Name + fmt.Sprintf("%p", m) } func (*soongConfigValueVariableDummyModule) Namespaceless() {} func (*soongConfigValueVariableDummyModule) 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) } 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) } 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) 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()) // 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 } }