platform_build_soong/android/soong_config_modules.go
Jingwen Chen a47f28d28e bp2build: add support for soong_config_module_type.
Test: CI, go unit test
Bug: 198556411
Change-Id: Idf862904d51d822f92af0c072341c31b7a02fc64
2021-11-08 13:38:28 +00:00

459 lines
16 KiB
Go

// 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"
"strings"
"text/scanner"
"github.com/google/blueprint"
"github.com/google/blueprint/parser"
"github.com/google/blueprint/proptools"
"android/soong/android/soongconfig"
)
func init() {
RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
}
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) Nameless() {}
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
}
func (*soongConfigModuleTypeModule) Nameless() {}
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
}
func (*soongConfigStringVariableDummyModule) Nameless() {}
func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
func (m *soongConfigBoolVariableDummyModule) Name() string {
return m.properties.Name
}
func (*soongConfigBoolVariableDummyModule) Nameless() {}
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)
}
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, ctx.Config().runningAsBp2Build)
} 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)
}
// 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 {
conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
if !conditionalFactoryProps.IsValid() {
return factory
}
useBp2buildHook := bp2build && proptools.BoolDefault(moduleType.Bp2buildAvailable, false)
return func() (blueprint.Module, []interface{}) {
module, props := factory()
conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
props = append(props, conditionalProps.Interface())
if useBp2buildHook {
// 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: 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) {
config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
if err != nil {
ctx.ModuleErrorf("%s", err)
return
}
for _, ps := range newProps {
ctx.AppendProperties(ps)
}
})
}
return module, props
}
}