bp2build: add support for soong_config_module_type.
Test: CI, go unit test
Bug: 198556411
Change-Id: Idf862904d51d822f92af0c072341c31b7a02fc64
diff --git a/android/bazel.go b/android/bazel.go
index cf27cb4..3bc104d 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -15,6 +15,7 @@
package android
import (
+ "android/soong/bazel"
"fmt"
"io/ioutil"
"path/filepath"
@@ -24,34 +25,33 @@
"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 @@
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 @@
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 @@
// 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)
diff --git a/android/config.go b/android/config.go
index 78d43c6..bc5ecc3 100644
--- a/android/config.go
+++ b/android/config.go
@@ -155,6 +155,7 @@
fs pathtools.FileSystem
mockBpList string
+ runningAsBp2Build bool
bp2buildPackageConfig Bp2BuildConfig
bp2buildModuleTypeConfig map[string]bool
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index a1f8e63..fa40d1f 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -510,9 +510,9 @@
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 {
diff --git a/android/register.go b/android/register.go
index 5984862..4244398 100644
--- a/android/register.go
+++ b/android/register.go
@@ -161,6 +161,10 @@
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.
diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go
index 17f6d66..065440d 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -31,10 +31,10 @@
)
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 @@
// 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 @@
type soongConfigModuleTypeModule struct {
ModuleBase
+ BazelModuleBase
properties soongconfig.ModuleTypeProperties
}
@@ -262,7 +263,7 @@
// 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 @@
// 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 @@
// 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 (*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 @@
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 @@
}).(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 @@
ctx.AppendProperties(ps)
}
})
-
- return module, props
}
- } else {
- return factory
+ return module, props
}
}
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index 0ec9bcb..acb9d18 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -310,10 +310,10 @@
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 @@
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)
}),
diff --git a/android/soongconfig/Android.bp b/android/soongconfig/Android.bp
index e7fa5a0..9bf3344 100644
--- a/android/soongconfig/Android.bp
+++ b/android/soongconfig/Android.bp
@@ -9,6 +9,7 @@
"blueprint",
"blueprint-parser",
"blueprint-proptools",
+ "soong-bazel",
],
srcs: [
"config.go",
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index 34b180d..1af89ba 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -15,6 +15,7 @@
package soongconfig
import (
+ "android/soong/bazel"
"fmt"
"io"
"reflect"
@@ -28,7 +29,7 @@
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 @@
// 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 @@
}
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 @@
// 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 @@
affectableProperties []string
variableNames []string
+ Bp2buildAvailable *bool
}
func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
@@ -441,6 +445,7 @@
ConfigNamespace: props.Config_namespace,
BaseModuleType: props.Module_type,
variableNames: props.Variables,
+ Bp2buildAvailable: props.Bazel_module.Bp2build_available,
}
for _, name := range props.Bool_variables {
diff --git a/android/testing.go b/android/testing.go
index b9d8fa8..6290d43 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -458,6 +458,7 @@
// RegisterForBazelConversion prepares a test context for bp2build conversion.
func (ctx *TestContext) RegisterForBazelConversion() {
+ ctx.SetRunningAsBp2build()
RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch, ctx.bp2buildMutators)
}
diff --git a/android/variable.go b/android/variable.go
index e943640..6ad58c3 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -15,6 +15,8 @@
package android
import (
+ "android/soong/android/soongconfig"
+ "android/soong/bazel"
"fmt"
"reflect"
"runtime"
@@ -487,13 +489,97 @@
// 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 @@
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 @@
// 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(),
+ )
}
}
}
diff --git a/bazel/configurability.go b/bazel/configurability.go
index f05c8e5..1993f76 100644
--- a/bazel/configurability.go
+++ b/bazel/configurability.go
@@ -158,9 +158,9 @@
}
// 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 @@
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))
}
}
diff --git a/bazel/properties.go b/bazel/properties.go
index facbedd..a438481 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -24,6 +24,23 @@
"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 {
diff --git a/bp2build/soong_config_module_type_conversion_test.go b/bp2build/soong_config_module_type_conversion_test.go
new file mode 100644
index 0000000..4118479
--- /dev/null
+++ b/bp2build/soong_config_module_type_conversion_test.go
@@ -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 = ["."],
+)`}})
+}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 1b13854..2059f5e 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -327,7 +327,7 @@
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 @@
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))
}
}
}
diff --git a/cc/library.go b/cc/library.go
index a394409..dbf927d 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -2358,9 +2358,6 @@
if !module.ConvertWithBp2build(ctx) {
return
}
- if ctx.ModuleType() != modType {
- return
- }
ccSharedOrStaticBp2BuildMutatorInternal(ctx, module, modType)
}
@@ -2498,7 +2495,15 @@
}
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 @@
}
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")
+ }
}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index e9eabd3..dfc4eae 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -464,6 +464,11 @@
// 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())