bp2build: add support for soong_config_module_type.

Test: CI, go unit test
Bug: 198556411
Change-Id: Idf862904d51d822f92af0c072341c31b7a02fc64
This commit is contained in:
Jingwen Chen 2021-11-02 16:43:57 +00:00
parent 925942127a
commit a47f28d28e
16 changed files with 710 additions and 96 deletions

View file

@ -15,6 +15,7 @@
package android
import (
"android/soong/bazel"
"fmt"
"io/ioutil"
"path/filepath"
@ -24,34 +25,33 @@ import (
"github.com/google/blueprint/proptools"
)
type bazelModuleProperties struct {
// The label of the Bazel target replacing this Soong module. When run in conversion mode, this
// will import the handcrafted build target into the autogenerated file. Note: this may result in
// a conflict due to duplicate targets if bp2build_available is also set.
Label *string
// If true, bp2build will generate the converted Bazel target for this module. Note: this may
// cause a conflict due to the duplicate targets if label is also set.
//
// This is a bool pointer to support tristates: true, false, not set.
//
// To opt-in a module, set bazel_module: { bp2build_available: true }
// To opt-out a module, set bazel_module: { bp2build_available: false }
// To defer the default setting for the directory, do not set the value.
Bp2build_available *bool
}
// Properties contains common module properties for Bazel migration purposes.
type properties struct {
// In USE_BAZEL_ANALYSIS=1 mode, this represents the Bazel target replacing
// this Soong module.
Bazel_module bazelModuleProperties
Bazel_module bazel.BazelModuleProperties
}
type namespacedVariableProperties map[string]interface{}
// BazelModuleBase contains the property structs with metadata for modules which can be converted to
// Bazel.
type BazelModuleBase struct {
bazelProperties properties
// namespacedVariableProperties is used for soong_config_module_type support
// in bp2build. Soong config modules allow users to set module properties
// based on custom product variables defined in Android.bp files. These
// variables are namespaced to prevent clobbering, especially when set from
// Makefiles.
namespacedVariableProperties namespacedVariableProperties
// baseModuleType is set when this module was created from a module type
// defined by a soong_config_module_type. Every soong_config_module_type
// "wraps" another module type, e.g. a soong_config_module_type can wrap a
// cc_defaults to a custom_cc_defaults, or cc_binary to a custom_cc_binary.
// This baseModuleType is set to the wrapped module type.
baseModuleType string
}
// Bazelable is specifies the interface for modules that can be converted to Bazel.
@ -63,6 +63,12 @@ type Bazelable interface {
ConvertWithBp2build(ctx BazelConversionContext) bool
convertWithBp2build(ctx BazelConversionContext, module blueprint.Module) bool
GetBazelBuildFileContents(c Config, path, name string) (string, error)
// For namespaced config variable support
namespacedVariableProps() namespacedVariableProperties
setNamespacedVariableProps(props namespacedVariableProperties)
BaseModuleType() string
SetBaseModuleType(string)
}
// BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
@ -82,6 +88,22 @@ func (b *BazelModuleBase) bazelProps() *properties {
return &b.bazelProperties
}
func (b *BazelModuleBase) namespacedVariableProps() namespacedVariableProperties {
return b.namespacedVariableProperties
}
func (b *BazelModuleBase) setNamespacedVariableProps(props namespacedVariableProperties) {
b.namespacedVariableProperties = props
}
func (b *BazelModuleBase) BaseModuleType() string {
return b.baseModuleType
}
func (b *BazelModuleBase) SetBaseModuleType(baseModuleType string) {
b.baseModuleType = baseModuleType
}
// HasHandcraftedLabel returns whether this module has a handcrafted Bazel label.
func (b *BazelModuleBase) HasHandcraftedLabel() bool {
return b.bazelProperties.Bazel_module.Label != nil
@ -399,7 +421,15 @@ func (b *BazelModuleBase) convertWithBp2build(ctx BazelConversionContext, module
// prevents mixed builds from using auto-converted modules just by matching
// the package dir; it also has to have a bp2build mutator as well.
if ctx.Config().bp2buildModuleTypeConfig[ctx.OtherModuleType(module)] == false {
return false
if b, ok := module.(Bazelable); ok && b.BaseModuleType() != "" {
// For modules with custom types from soong_config_module_types,
// check that their _base module type_ has a bp2build mutator.
if ctx.Config().bp2buildModuleTypeConfig[b.BaseModuleType()] == false {
return false
}
} else {
return false
}
}
packagePath := ctx.OtherModuleDir(module)

View file

@ -155,6 +155,7 @@ type config struct {
fs pathtools.FileSystem
mockBpList string
runningAsBp2Build bool
bp2buildPackageConfig Bp2BuildConfig
bp2buildModuleTypeConfig map[string]bool

View file

@ -510,9 +510,9 @@ func registerTestPrebuiltModules(ctx RegistrationContext) {
ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
ctx.RegisterModuleType("source", newSourceModule)
ctx.RegisterModuleType("override_source", newOverrideSourceModule)
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_module_type", SoongConfigModuleTypeFactory)
ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
}
type prebuiltModule struct {

View file

@ -161,6 +161,10 @@ func NewContext(config Config) *Context {
return ctx
}
func (ctx *Context) SetRunningAsBp2build() {
ctx.config.runningAsBp2Build = true
}
// RegisterForBazelConversion registers an alternate shadow pipeline of
// singletons, module types and mutators to register for converting Blueprint
// files to semantically equivalent BUILD files.

View file

@ -31,10 +31,10 @@ import (
)
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)
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 {
@ -153,7 +153,7 @@ type soongConfigModuleTypeImportProperties struct {
// Then libacme_foo would build with cflags:
// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
func soongConfigModuleTypeImportFactory() Module {
func SoongConfigModuleTypeImportFactory() Module {
module := &soongConfigModuleTypeImport{}
module.AddProperties(&module.properties)
@ -179,6 +179,7 @@ func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {
type soongConfigModuleTypeModule struct {
ModuleBase
BazelModuleBase
properties soongconfig.ModuleTypeProperties
}
@ -262,7 +263,7 @@ type soongConfigModuleTypeModule struct {
// SOONG_CONFIG_acme_width := 200
//
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
func soongConfigModuleTypeFactory() Module {
func SoongConfigModuleTypeFactory() Module {
module := &soongConfigModuleTypeModule{}
module.AddProperties(&module.properties)
@ -296,7 +297,7 @@ type soongConfigBoolVariableDummyModule struct {
// 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 {
func SoongConfigStringVariableDummyFactory() Module {
module := &soongConfigStringVariableDummyModule{}
module.AddProperties(&module.properties, &module.stringProperties)
initAndroidModuleBase(module)
@ -305,7 +306,7 @@ func soongConfigStringVariableDummyFactory() 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 {
func SoongConfigBoolVariableDummyFactory() Module {
module := &soongConfigBoolVariableDummyModule{}
module.AddProperties(&module.properties)
initAndroidModuleBase(module)
@ -324,6 +325,9 @@ func (m *soongConfigBoolVariableDummyModule) Name() string {
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" {
@ -389,7 +393,7 @@ func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[s
for name, moduleType := range mtDef.ModuleTypes {
factory := globalModuleTypes[moduleType.BaseModuleType]
if factory != nil {
factories[name] = soongConfigModuleFactory(factory, moduleType)
factories[name] = configModuleFactory(factory, moduleType, ctx.Config().runningAsBp2Build)
} else {
reportErrors(ctx, from,
fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
@ -404,20 +408,40 @@ func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[s
}).(map[string]blueprint.ModuleFactory)
}
// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns
// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config
// variables.
func soongConfigModuleFactory(factory blueprint.ModuleFactory,
moduleType *soongconfig.ModuleType) 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 func() (blueprint.Module, []interface{}) {
module, props := factory()
if !conditionalFactoryProps.IsValid() {
return factory
}
useBp2buildHook := bp2build && proptools.BoolDefault(moduleType.Bp2buildAvailable, false)
conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
props = append(props, conditionalProps.Interface())
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)
@ -429,10 +453,7 @@ func soongConfigModuleFactory(factory blueprint.ModuleFactory,
ctx.AppendProperties(ps)
}
})
return module, props
}
} else {
return factory
return module, props
}
}

View file

@ -310,10 +310,10 @@ func TestSoongConfigModule(t *testing.T) {
tc.preparer,
PrepareForTestWithDefaults,
FixtureRegisterWithContext(func(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_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("test_defaults", soongConfigTestDefaultsModuleFactory)
ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
}),
@ -372,10 +372,10 @@ func TestNonExistentPropertyInSoongConfigModule(t *testing.T) {
fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
PrepareForTestWithDefaults,
FixtureRegisterWithContext(func(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_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("test_defaults", soongConfigTestDefaultsModuleFactory)
ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
}),

View file

@ -9,6 +9,7 @@ bootstrap_go_package {
"blueprint",
"blueprint-parser",
"blueprint-proptools",
"soong-bazel",
],
srcs: [
"config.go",

View file

@ -15,6 +15,7 @@
package soongconfig
import (
"android/soong/bazel"
"fmt"
"io"
"reflect"
@ -28,7 +29,7 @@ import (
const conditionsDefault = "conditions_default"
var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
var SoongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
// result so each file is only parsed once.
@ -120,6 +121,8 @@ type ModuleTypeProperties struct {
// the list of properties that this module type will extend.
Properties []string
Bazel_module bazel.BazelModuleProperties
}
func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
@ -271,12 +274,12 @@ func CreateProperties(factory blueprint.ModuleFactory, moduleType *ModuleType) r
}
typ := reflect.StructOf([]reflect.StructField{{
Name: soongConfigProperty,
Name: SoongConfigProperty,
Type: reflect.StructOf(fields),
}})
props := reflect.New(typ)
structConditions := props.Elem().FieldByName(soongConfigProperty)
structConditions := props.Elem().FieldByName(SoongConfigProperty)
for i, c := range moduleType.Variables {
c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
@ -415,7 +418,7 @@ func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.
// soong_config_variables are expected to be in the same order as moduleType.Variables.
func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
var ret []interface{}
props = props.Elem().FieldByName(soongConfigProperty)
props = props.Elem().FieldByName(SoongConfigProperty)
for i, c := range moduleType.Variables {
if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
return nil, err
@ -433,6 +436,7 @@ type ModuleType struct {
affectableProperties []string
variableNames []string
Bp2buildAvailable *bool
}
func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
@ -441,6 +445,7 @@ func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
ConfigNamespace: props.Config_namespace,
BaseModuleType: props.Module_type,
variableNames: props.Variables,
Bp2buildAvailable: props.Bazel_module.Bp2build_available,
}
for _, name := range props.Bool_variables {

View file

@ -458,6 +458,7 @@ func (ctx *TestContext) Register() {
// RegisterForBazelConversion prepares a test context for bp2build conversion.
func (ctx *TestContext) RegisterForBazelConversion() {
ctx.SetRunningAsBp2build()
RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch, ctx.bp2buildMutators)
}

View file

@ -15,6 +15,8 @@
package android
import (
"android/soong/android/soongconfig"
"android/soong/bazel"
"fmt"
"reflect"
"runtime"
@ -487,13 +489,97 @@ type ProductConfigContext interface {
// ProductConfigProperty contains the information for a single property (may be a struct) paired
// with the appropriate ProductConfigVariable.
type ProductConfigProperty struct {
// The name of the product variable, e.g. "safestack", "malloc_not_svelte",
// "board"
ProductConfigVariable string
FullConfig string
Property interface{}
// Namespace of the variable, if this is a soong_config_module_type variable
// e.g. "acme", "ANDROID", "vendor_nae"
Namespace string // for soong config variables
// Unique configuration to identify this product config property (i.e. a
// primary key), as just using the product variable name is not sufficient.
//
// For product variables, this is the product variable name + optional
// archvariant information. e.g.
//
// product_variables: {
// foo: {
// cflags: ["-Dfoo"],
// },
// },
//
// FullConfig would be "foo".
//
// target: {
// android: {
// product_variables: {
// foo: {
// cflags: ["-Dfoo-android"],
// },
// },
// },
// },
//
// FullConfig would be "foo-android".
//
// For soong config variables, this is the namespace + product variable name
// + value of the variable, if applicable. The value can also be
// conditions_default.
//
// e.g.
//
// soong_config_variables: {
// feature1: {
// conditions_default: {
// cflags: ["-DDEFAULT1"],
// },
// cflags: ["-DFEATURE1"],
// },
// }
//
// where feature1 is created in the "acme" namespace, so FullConfig would be
// "acme__feature1" and "acme__feature1__conditions_default".
//
// e.g.
//
// soong_config_variables: {
// board: {
// soc_a: {
// cflags: ["-DSOC_A"],
// },
// soc_b: {
// cflags: ["-DSOC_B"],
// },
// soc_c: {},
// conditions_default: {
// cflags: ["-DSOC_DEFAULT"]
// },
// },
// }
//
// where board is created in the "acme" namespace, so FullConfig would be
// "acme__board__soc_a", "acme__board__soc_b", and
// "acme__board__conditions_default"
FullConfig string
// The actual property value: list, bool, string..
Property interface{}
}
// ProductConfigProperties is a map of property name to a slice of ProductConfigProperty such that
// all it all product variable-specific versions of a property are easily accessed together
func (p *ProductConfigProperty) ConfigurationAxis() bazel.ConfigurationAxis {
if p.Namespace == "" {
return bazel.ProductVariableConfigurationAxis(p.FullConfig)
} else {
// Soong config variables can be uniquely identified by the namespace
// (e.g. acme, android) and the product variable name (e.g. board, size)
return bazel.ProductVariableConfigurationAxis(p.Namespace + "__" + p.ProductConfigVariable)
}
}
// ProductConfigProperties is a map of property name to a slice of
// ProductConfigProperty such that all product variable-specific versions of a
// property are easily accessed together
type ProductConfigProperties map[string]map[string]ProductConfigProperty
// ProductVariableProperties returns a ProductConfigProperties containing only the properties which
@ -504,36 +590,165 @@ func ProductVariableProperties(ctx BazelConversionPathContext) ProductConfigProp
productConfigProperties := ProductConfigProperties{}
if moduleBase.variableProperties == nil {
return productConfigProperties
if moduleBase.variableProperties != nil {
productVariablesProperty := proptools.FieldNameForProperty("product_variables")
productVariableValues(
productVariablesProperty,
moduleBase.variableProperties,
"",
"",
&productConfigProperties)
for _, configToProps := range moduleBase.GetArchVariantProperties(ctx, moduleBase.variableProperties) {
for config, props := range configToProps {
// GetArchVariantProperties is creating an instance of the requested type
// and productVariablesValues expects an interface, so no need to cast
productVariableValues(
productVariablesProperty,
props,
"",
config,
&productConfigProperties)
}
}
}
productVariableValues(moduleBase.variableProperties, "", &productConfigProperties)
for _, configToProps := range moduleBase.GetArchVariantProperties(ctx, moduleBase.variableProperties) {
for config, props := range configToProps {
// GetArchVariantProperties is creating an instance of the requested type
// and productVariablesValues expects an interface, so no need to cast
productVariableValues(props, config, &productConfigProperties)
if m, ok := module.(Bazelable); ok && m.namespacedVariableProps() != nil {
for namespace, namespacedVariableProp := range m.namespacedVariableProps() {
productVariableValues(
soongconfig.SoongConfigProperty,
namespacedVariableProp,
namespace,
"",
&productConfigProperties)
}
}
return productConfigProperties
}
func productVariableValues(variableProps interface{}, suffix string, productConfigProperties *ProductConfigProperties) {
func (p *ProductConfigProperties) AddProductConfigProperty(
propertyName, namespace, productVariableName, config string, property interface{}) {
if (*p)[propertyName] == nil {
(*p)[propertyName] = make(map[string]ProductConfigProperty)
}
// Normalize config to be all lowercase. It's the "primary key" of this
// unique property value. This can be the conditions_default value of the
// product variable as well.
config = strings.ToLower(config)
(*p)[propertyName][config] = ProductConfigProperty{
Namespace: namespace, // e.g. acme, android
ProductConfigVariable: productVariableName, // e.g. size, feature1, feature2, FEATURE3, board
FullConfig: config, // e.g. size, feature1-x86, size__conditions_default
Property: property, // e.g. ["-O3"]
}
}
var (
conditionsDefaultField string = proptools.FieldNameForProperty(bazel.ConditionsDefaultConfigKey)
)
// maybeExtractConfigVarProp attempts to read this value as a config var struct
// wrapped by interfaces and ptrs. If it's not the right type, the second return
// value is false.
func maybeExtractConfigVarProp(v reflect.Value) (reflect.Value, bool) {
if v.Kind() == reflect.Interface {
// The conditions_default value can be either
// 1) an ptr to an interface of a struct (bool config variables and product variables)
// 2) an interface of 1) (config variables with nested structs, like string vars)
v = v.Elem()
}
if v.Kind() != reflect.Ptr {
return v, false
}
v = reflect.Indirect(v)
if v.Kind() == reflect.Interface {
// Extract the struct from the interface
v = v.Elem()
}
if !v.IsValid() {
return v, false
}
if v.Kind() != reflect.Struct {
return v, false
}
return v, true
}
// productVariableValues uses reflection to convert a property struct for
// product_variables and soong_config_variables to structs that can be generated
// as select statements.
func productVariableValues(
fieldName string, variableProps interface{}, namespace, suffix string, productConfigProperties *ProductConfigProperties) {
if suffix != "" {
suffix = "-" + suffix
}
variableValues := reflect.ValueOf(variableProps).Elem().FieldByName("Product_variables")
// variableValues represent the product_variables or soong_config_variables
// struct.
variableValues := reflect.ValueOf(variableProps).Elem().FieldByName(fieldName)
// Example of product_variables:
//
// product_variables: {
// malloc_not_svelte: {
// shared_libs: ["malloc_not_svelte_shared_lib"],
// whole_static_libs: ["malloc_not_svelte_whole_static_lib"],
// exclude_static_libs: [
// "malloc_not_svelte_static_lib_excludes",
// "malloc_not_svelte_whole_static_lib_excludes",
// ],
// },
// },
//
// Example of soong_config_variables:
//
// soong_config_variables: {
// feature1: {
// conditions_default: {
// ...
// },
// cflags: ...
// },
// feature2: {
// cflags: ...
// conditions_default: {
// ...
// },
// },
// board: {
// soc_a: {
// ...
// },
// soc_a: {
// ...
// },
// soc_c: {},
// conditions_default: {
// ...
// },
// },
// }
for i := 0; i < variableValues.NumField(); i++ {
// e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc.
productVariableName := variableValues.Type().Field(i).Name
variableValue := variableValues.Field(i)
// Check if any properties were set for the module
if variableValue.IsZero() {
// e.g. feature1: {}, malloc_not_svelte: {}
continue
}
// e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc.
productVariableName := variableValues.Type().Field(i).Name
// Unlike product variables, config variables require a few more
// indirections to extract the struct from the reflect.Value.
if v, ok := maybeExtractConfigVarProp(variableValue); ok {
variableValue = v
}
for j := 0; j < variableValue.NumField(); j++ {
property := variableValue.Field(j)
// If the property wasn't set, no need to pass it along
@ -543,14 +758,57 @@ func productVariableValues(variableProps interface{}, suffix string, productConf
// e.g. Asflags, Cflags, Enabled, etc.
propertyName := variableValue.Type().Field(j).Name
if (*productConfigProperties)[propertyName] == nil {
(*productConfigProperties)[propertyName] = make(map[string]ProductConfigProperty)
}
config := productVariableName + suffix
(*productConfigProperties)[propertyName][config] = ProductConfigProperty{
ProductConfigVariable: productVariableName,
FullConfig: config,
Property: property.Interface(),
if v, ok := maybeExtractConfigVarProp(property); ok {
// The field is a struct, which is used by:
// 1) soong_config_string_variables
//
// soc_a: {
// cflags: ...,
// }
//
// soc_b: {
// cflags: ...,
// }
//
// 2) conditions_default structs for all soong config variable types.
//
// conditions_default: {
// cflags: ...,
// static_libs: ...
// }
field := v
for k := 0; k < field.NumField(); k++ {
// Iterate over fields of this struct prop.
if field.Field(k).IsZero() {
continue
}
productVariableValue := proptools.PropertyNameForField(propertyName)
config := strings.Join([]string{namespace, productVariableName, productVariableValue}, "__")
actualPropertyName := field.Type().Field(k).Name
productConfigProperties.AddProductConfigProperty(
actualPropertyName, // e.g. cflags, static_libs
namespace, // e.g. acme, android
productVariableName, // e.g. size, feature1, FEATURE2, board
config,
field.Field(k).Interface(), // e.g. ["-DDEFAULT"], ["foo", "bar"]
)
}
} else {
// Not a conditions_default or a struct prop, i.e. regular
// product variables, or not a string-typed config var.
config := productVariableName + suffix
if namespace != "" {
config = namespace + "__" + config
}
productConfigProperties.AddProductConfigProperty(
propertyName,
namespace,
productVariableName,
config,
property.Interface(),
)
}
}
}

View file

@ -158,9 +158,9 @@ func (ct configurationType) validateConfig(config string) {
}
// SelectKey returns the Bazel select key for a given configurationType and config string.
func (ct configurationType) SelectKey(config string) string {
ct.validateConfig(config)
switch ct {
func (ca ConfigurationAxis) SelectKey(config string) string {
ca.validateConfig(config)
switch ca.configurationType {
case noConfig:
panic(fmt.Errorf("SelectKey is unnecessary for noConfig ConfigurationType "))
case arch:
@ -170,12 +170,13 @@ func (ct configurationType) SelectKey(config string) string {
case osArch:
return platformOsArchMap[config]
case productVariables:
if config == ConditionsDefaultConfigKey {
if strings.HasSuffix(config, ConditionsDefaultConfigKey) {
// e.g. "acme__feature1__conditions_default" or "android__board__conditions_default"
return ConditionsDefaultSelectKey
}
return fmt.Sprintf("%s:%s", productVariableBazelPackage, strings.ToLower(config))
return fmt.Sprintf("%s:%s", productVariableBazelPackage, config)
default:
panic(fmt.Errorf("Unrecognized ConfigurationType %d", ct))
panic(fmt.Errorf("Unrecognized ConfigurationType %d", ca.configurationType))
}
}

View file

@ -24,6 +24,23 @@ import (
"github.com/google/blueprint"
)
type BazelModuleProperties struct {
// The label of the Bazel target replacing this Soong module. When run in conversion mode, this
// will import the handcrafted build target into the autogenerated file. Note: this may result in
// a conflict due to duplicate targets if bp2build_available is also set.
Label *string
// If true, bp2build will generate the converted Bazel target for this module. Note: this may
// cause a conflict due to the duplicate targets if label is also set.
//
// This is a bool pointer to support tristates: true, false, not set.
//
// To opt-in a module, set bazel_module: { bp2build_available: true }
// To opt-out a module, set bazel_module: { bp2build_available: false }
// To defer the default setting for the directory, do not set the value.
Bp2build_available *bool
}
// BazelTargetModuleProperties contain properties and metadata used for
// Blueprint to BUILD file conversion.
type BazelTargetModuleProperties struct {

View file

@ -0,0 +1,257 @@
// 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 bp2build
import (
"android/soong/android"
"android/soong/cc"
"testing"
)
func runSoongConfigModuleTypeTest(t *testing.T, tc bp2buildTestCase) {
t.Helper()
runBp2BuildTestCase(t, registerSoongConfigModuleTypes, tc)
}
func registerSoongConfigModuleTypes(ctx android.RegistrationContext) {
cc.RegisterCCBuildComponents(ctx)
ctx.RegisterModuleType("soong_config_module_type_import", android.SoongConfigModuleTypeImportFactory)
ctx.RegisterModuleType("soong_config_module_type", android.SoongConfigModuleTypeFactory)
ctx.RegisterModuleType("soong_config_string_variable", android.SoongConfigStringVariableDummyFactory)
ctx.RegisterModuleType("soong_config_bool_variable", android.SoongConfigBoolVariableDummyFactory)
}
func TestSoongConfigModuleType(t *testing.T) {
bp := `
soong_config_module_type {
name: "custom_cc_library_static",
module_type: "cc_library_static",
config_namespace: "acme",
bool_variables: ["feature1"],
properties: ["cflags"],
bazel_module: { bp2build_available: true },
}
custom_cc_library_static {
name: "foo",
bazel_module: { bp2build_available: true },
soong_config_variables: {
feature1: {
conditions_default: {
cflags: ["-DDEFAULT1"],
},
cflags: ["-DFEATURE1"],
},
},
}
`
runSoongConfigModuleTypeTest(t, bp2buildTestCase{
description: "soong config variables - soong_config_module_type is supported in bp2build",
moduleTypeUnderTest: "cc_library_static",
moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
blueprint: bp,
expectedBazelTargets: []string{`cc_library_static(
name = "foo",
copts = select({
"//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
"//conditions:default": ["-DDEFAULT1"],
}),
local_includes = ["."],
)`}})
}
func TestSoongConfigModuleTypeImport(t *testing.T) {
configBp := `
soong_config_module_type {
name: "custom_cc_library_static",
module_type: "cc_library_static",
config_namespace: "acme",
bool_variables: ["feature1"],
properties: ["cflags"],
bazel_module: { bp2build_available: true },
}
`
bp := `
soong_config_module_type_import {
from: "foo/bar/SoongConfig.bp",
module_types: ["custom_cc_library_static"],
}
custom_cc_library_static {
name: "foo",
bazel_module: { bp2build_available: true },
soong_config_variables: {
feature1: {
conditions_default: {
cflags: ["-DDEFAULT1"],
},
cflags: ["-DFEATURE1"],
},
},
}
`
runSoongConfigModuleTypeTest(t, bp2buildTestCase{
description: "soong config variables - soong_config_module_type_import is supported in bp2build",
moduleTypeUnderTest: "cc_library_static",
moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
filesystem: map[string]string{
"foo/bar/SoongConfig.bp": configBp,
},
blueprint: bp,
expectedBazelTargets: []string{`cc_library_static(
name = "foo",
copts = select({
"//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
"//conditions:default": ["-DDEFAULT1"],
}),
local_includes = ["."],
)`}})
}
func TestSoongConfigModuleType_StringVar(t *testing.T) {
bp := `
soong_config_string_variable {
name: "board",
values: ["soc_a", "soc_b", "soc_c"],
}
soong_config_module_type {
name: "custom_cc_library_static",
module_type: "cc_library_static",
config_namespace: "acme",
variables: ["board"],
properties: ["cflags"],
bazel_module: { bp2build_available: true },
}
custom_cc_library_static {
name: "foo",
bazel_module: { bp2build_available: true },
soong_config_variables: {
board: {
soc_a: {
cflags: ["-DSOC_A"],
},
soc_b: {
cflags: ["-DSOC_B"],
},
soc_c: {},
conditions_default: {
cflags: ["-DSOC_DEFAULT"]
},
},
},
}
`
runSoongConfigModuleTypeTest(t, bp2buildTestCase{
description: "soong config variables - generates selects for string vars",
moduleTypeUnderTest: "cc_library_static",
moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
blueprint: bp,
expectedBazelTargets: []string{`cc_library_static(
name = "foo",
copts = select({
"//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
"//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
"//conditions:default": ["-DSOC_DEFAULT"],
}),
local_includes = ["."],
)`}})
}
func TestSoongConfigModuleType_StringAndBoolVar(t *testing.T) {
bp := `
soong_config_bool_variable {
name: "feature1",
}
soong_config_bool_variable {
name: "feature2",
}
soong_config_string_variable {
name: "board",
values: ["soc_a", "soc_b", "soc_c"],
}
soong_config_module_type {
name: "custom_cc_library_static",
module_type: "cc_library_static",
config_namespace: "acme",
variables: ["feature1", "feature2", "board"],
properties: ["cflags"],
bazel_module: { bp2build_available: true },
}
custom_cc_library_static {
name: "foo",
bazel_module: { bp2build_available: true },
soong_config_variables: {
feature1: {
conditions_default: {
cflags: ["-DDEFAULT1"],
},
cflags: ["-DFEATURE1"],
},
feature2: {
cflags: ["-DFEATURE2"],
conditions_default: {
cflags: ["-DDEFAULT2"],
},
},
board: {
soc_a: {
cflags: ["-DSOC_A"],
},
soc_b: {
cflags: ["-DSOC_B"],
},
soc_c: {},
conditions_default: {
cflags: ["-DSOC_DEFAULT"]
},
},
},
}`
runSoongConfigModuleTypeTest(t, bp2buildTestCase{
description: "soong config variables - generates selects for multiple variable types",
moduleTypeUnderTest: "cc_library_static",
moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
blueprint: bp,
expectedBazelTargets: []string{`cc_library_static(
name = "foo",
copts = select({
"//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
"//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
"//conditions:default": ["-DSOC_DEFAULT"],
}) + select({
"//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
"//conditions:default": ["-DDEFAULT1"],
}) + select({
"//build/bazel/product_variables:acme__feature2": ["-DFEATURE2"],
"//conditions:default": ["-DDEFAULT2"],
}),
local_includes = ["."],
)`}})
}

View file

@ -327,7 +327,7 @@ func (ca *compilerAttributes) convertProductVariables(ctx android.BazelConversio
ctx.ModuleErrorf("Could not convert product variable %s property", proptools.PropertyNameForField(propName))
}
newFlags, _ := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable)
attr.SetSelectValue(bazel.ProductVariableConfigurationAxis(prop.FullConfig), prop.FullConfig, newFlags)
attr.SetSelectValue(prop.ConfigurationAxis(), prop.FullConfig, newFlags)
}
}
}
@ -611,7 +611,7 @@ func (la *linkerAttributes) convertProductVariables(ctx android.BazelConversionP
ctx.ModuleErrorf("Could not convert product variable %s property", dep.excludesField)
}
dep.attribute.SetSelectValue(bazel.ProductVariableConfigurationAxis(config), config, dep.depResolutionFunc(ctx, android.FirstUniqueStrings(includes), excludes))
dep.attribute.SetSelectValue(prop.ConfigurationAxis(), config, dep.depResolutionFunc(ctx, android.FirstUniqueStrings(includes), excludes))
}
}
}

View file

@ -2358,9 +2358,6 @@ func ccSharedOrStaticBp2BuildMutator(ctx android.TopDownMutatorContext, modType
if !module.ConvertWithBp2build(ctx) {
return
}
if ctx.ModuleType() != modType {
return
}
ccSharedOrStaticBp2BuildMutatorInternal(ctx, module, modType)
}
@ -2498,7 +2495,15 @@ type bazelCcLibraryStaticAttributes struct {
}
func CcLibraryStaticBp2Build(ctx android.TopDownMutatorContext) {
ccSharedOrStaticBp2BuildMutator(ctx, "cc_library_static")
isLibraryStatic := ctx.ModuleType() == "cc_library_static"
if b, ok := ctx.Module().(android.Bazelable); ok {
// This is created by a custom soong config module type, so its ctx.ModuleType() is not
// cc_library_static. Check its BaseModuleType.
isLibraryStatic = isLibraryStatic || b.BaseModuleType() == "cc_library_static"
}
if isLibraryStatic {
ccSharedOrStaticBp2BuildMutator(ctx, "cc_library_static")
}
}
// TODO(b/199902614): Can this be factored to share with the other Attributes?
@ -2529,5 +2534,13 @@ type bazelCcLibrarySharedAttributes struct {
}
func CcLibrarySharedBp2Build(ctx android.TopDownMutatorContext) {
ccSharedOrStaticBp2BuildMutator(ctx, "cc_library_shared")
isLibraryShared := ctx.ModuleType() == "cc_library_shared"
if b, ok := ctx.Module().(android.Bazelable); ok {
// This is created by a custom soong config module type, so its ctx.ModuleType() is not
// cc_library_shared. Check its BaseModuleType.
isLibraryShared = isLibraryShared || b.BaseModuleType() == "cc_library_shared"
}
if isLibraryShared {
ccSharedOrStaticBp2BuildMutator(ctx, "cc_library_shared")
}
}

View file

@ -464,6 +464,11 @@ func runBp2Build(configuration android.Config, extraNinjaDeps []string) {
// conversion for Bazel conversion.
bp2buildCtx := android.NewContext(configuration)
// Soong internals like LoadHooks behave differently when running as
// bp2build. This is the bit to differentiate between Soong-as-Soong and
// Soong-as-bp2build.
bp2buildCtx.SetRunningAsBp2build()
// Propagate "allow misssing dependencies" bit. This is normally set in
// newContext(), but we create bp2buildCtx without calling that method.
bp2buildCtx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())