diff --git a/android/bazel.go b/android/bazel.go index cf27cb464..3bc104d1d 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 @@ 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) diff --git a/android/config.go b/android/config.go index 1f96649d4..bd13c454f 100644 --- a/android/config.go +++ b/android/config.go @@ -155,6 +155,7 @@ type config struct { 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 a1f8e6367..fa40d1fb3 100644 --- a/android/prebuilt_test.go +++ b/android/prebuilt_test.go @@ -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 { diff --git a/android/register.go b/android/register.go index 59848627a..424439806 100644 --- a/android/register.go +++ b/android/register.go @@ -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. diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go index 17f6d66a8..065440d1f 100644 --- a/android/soong_config_modules.go +++ b/android/soong_config_modules.go @@ -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 } } diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go index 0ec9bcbd5..acb9d180e 100644 --- a/android/soong_config_modules_test.go +++ b/android/soong_config_modules_test.go @@ -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) }), diff --git a/android/soongconfig/Android.bp b/android/soongconfig/Android.bp index e7fa5a036..9bf334480 100644 --- a/android/soongconfig/Android.bp +++ b/android/soongconfig/Android.bp @@ -9,6 +9,7 @@ bootstrap_go_package { "blueprint", "blueprint-parser", "blueprint-proptools", + "soong-bazel", ], srcs: [ "config.go", diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go index 34b180d7d..1af89ba5d 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 @@ 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 { diff --git a/android/testing.go b/android/testing.go index b9d8fa878..6290d4317 100644 --- a/android/testing.go +++ b/android/testing.go @@ -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) } diff --git a/android/variable.go b/android/variable.go index e9436401c..6ad58c3f2 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 @@ 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(), + ) } } } diff --git a/bazel/configurability.go b/bazel/configurability.go index f05c8e55c..1993f76fc 100644 --- a/bazel/configurability.go +++ b/bazel/configurability.go @@ -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)) } } diff --git a/bazel/properties.go b/bazel/properties.go index facbedd9a..a438481be 100644 --- a/bazel/properties.go +++ b/bazel/properties.go @@ -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 { 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 000000000..41184794f --- /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 1b1385480..2059f5e53 100644 --- a/cc/bp2build.go +++ b/cc/bp2build.go @@ -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)) } } } diff --git a/cc/library.go b/cc/library.go index a394409f9..dbf927d61 100644 --- a/cc/library.go +++ b/cc/library.go @@ -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") + } } diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go index e9eabd37c..dfc4eae7a 100644 --- a/cmd/soong_build/main.go +++ b/cmd/soong_build/main.go @@ -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())