Simplify vendor conditionals
Support vendor conditionals with no Go code. Test: TestSoongConfigModule Change-Id: I42546e7f17324921ada80f4d8e1cd399830f8dfc
This commit is contained in:
parent
f0f747c949
commit
9d34f35815
12 changed files with 1479 additions and 57 deletions
17
Android.bp
17
Android.bp
|
@ -36,6 +36,7 @@ bootstrap_go_package {
|
|||
"blueprint",
|
||||
"blueprint-bootstrap",
|
||||
"soong",
|
||||
"soong-android-soongconfig",
|
||||
"soong-env",
|
||||
"soong-shared",
|
||||
],
|
||||
|
@ -73,6 +74,7 @@ bootstrap_go_package {
|
|||
"android/sdk.go",
|
||||
"android/sh_binary.go",
|
||||
"android/singleton.go",
|
||||
"android/soong_config_modules.go",
|
||||
"android/testing.go",
|
||||
"android/util.go",
|
||||
"android/variable.go",
|
||||
|
@ -101,6 +103,7 @@ bootstrap_go_package {
|
|||
"android/prebuilt_test.go",
|
||||
"android/prebuilt_etc_test.go",
|
||||
"android/rule_builder_test.go",
|
||||
"android/soong_config_modules_test.go",
|
||||
"android/util_test.go",
|
||||
"android/variable_test.go",
|
||||
"android/visibility_test.go",
|
||||
|
@ -108,6 +111,20 @@ bootstrap_go_package {
|
|||
],
|
||||
}
|
||||
|
||||
bootstrap_go_package {
|
||||
name: "soong-android-soongconfig",
|
||||
pkgPath: "android/soong/android/soongconfig",
|
||||
deps: [
|
||||
"blueprint",
|
||||
"blueprint-parser",
|
||||
"blueprint-proptools",
|
||||
],
|
||||
srcs: [
|
||||
"android/soongconfig/config.go",
|
||||
"android/soongconfig/modules.go",
|
||||
],
|
||||
}
|
||||
|
||||
bootstrap_go_package {
|
||||
name: "soong-cc-config",
|
||||
pkgPath: "android/soong/cc/config",
|
||||
|
|
128
README.md
128
README.md
|
@ -376,36 +376,14 @@ The androidmk converter will produce multiple conflicting modules, which must
|
|||
be resolved by hand to a single module with any differences inside
|
||||
`target: { android: { }, host: { } }` blocks.
|
||||
|
||||
## Build logic
|
||||
### Conditionals
|
||||
|
||||
The build logic is written in Go using the
|
||||
[blueprint](http://godoc.org/github.com/google/blueprint) framework. Build
|
||||
logic receives module definitions parsed into Go structures using reflection
|
||||
and produces build rules. The build rules are collected by blueprint and
|
||||
written to a [ninja](http://ninja-build.org) build file.
|
||||
|
||||
## Other documentation
|
||||
|
||||
* [Best Practices](docs/best_practices.md)
|
||||
* [Build Performance](docs/perf.md)
|
||||
* [Generating CLion Projects](docs/clion.md)
|
||||
* [Generating YouCompleteMe/VSCode compile\_commands.json file](docs/compdb.md)
|
||||
* Make-specific documentation: [build/make/README.md](https://android.googlesource.com/platform/build/+/master/README.md)
|
||||
|
||||
## FAQ
|
||||
|
||||
### How do I write conditionals?
|
||||
|
||||
Soong deliberately does not support conditionals in Android.bp files. We
|
||||
Soong deliberately does not support most conditionals in Android.bp files. We
|
||||
suggest removing most conditionals from the build. See
|
||||
[Best Practices](docs/best_practices.md#removing-conditionals) for some
|
||||
examples on how to remove conditionals.
|
||||
|
||||
In cases where build time conditionals are unavoidable, complexity in build
|
||||
rules that would require conditionals are handled in Go through Soong plugins.
|
||||
This allows Go language features to be used for better readability and
|
||||
testability, and implicit dependencies introduced by conditionals can be
|
||||
tracked. Most conditionals supported natively by Soong are converted to a map
|
||||
Most conditionals supported natively by Soong are converted to a map
|
||||
property. When building the module one of the properties in the map will be
|
||||
selected, and its values appended to the property with the same name at the
|
||||
top level of the module.
|
||||
|
@ -430,6 +408,106 @@ When building the module for arm the `generic.cpp` and `arm.cpp` sources will
|
|||
be built. When building for x86 the `generic.cpp` and 'x86.cpp' sources will
|
||||
be built.
|
||||
|
||||
#### Soong Config Variables
|
||||
|
||||
When converting vendor modules that contain conditionals, simple conditionals
|
||||
can be supported through Soong config variables using `soong_config_*`
|
||||
modules that describe the module types, variables and possible values:
|
||||
|
||||
```
|
||||
soong_config_module_type {
|
||||
name: "acme_cc_defaults",
|
||||
module_type: "cc_defaults",
|
||||
config_namespace: "acme",
|
||||
variables: ["board", "feature"],
|
||||
properties: ["cflags", "srcs"],
|
||||
}
|
||||
|
||||
soong_config_string_variable {
|
||||
name: "board",
|
||||
values: ["soc_a", "soc_b"],
|
||||
}
|
||||
|
||||
soong_config_bool_variable {
|
||||
name: "feature",
|
||||
}
|
||||
```
|
||||
|
||||
This example describes a new `acme_cc_defaults` module type that extends the
|
||||
`cc_defaults` module type, with two additional conditionals based on variables
|
||||
`board` and `feature`, which can affect properties `cflags` and `srcs`.
|
||||
|
||||
The values of the variables can be set from a product's `BoardConfig.mk` file:
|
||||
```
|
||||
SOONG_CONFIG_NAMESPACES += acme
|
||||
SOONG_CONFIG_acme += \
|
||||
board \
|
||||
feature \
|
||||
|
||||
SOONG_CONFIG_acme_board := soc_a
|
||||
SOONG_CONFIG_acme_feature := true
|
||||
```
|
||||
|
||||
The `acme_cc_defaults` module type can be used anywhere after the definition in
|
||||
the file where it is defined, or can be imported into another file with:
|
||||
```
|
||||
soong_config_module_type_import {
|
||||
from: "device/acme/Android.bp",
|
||||
module_types: ["acme_cc_defaults"],
|
||||
}
|
||||
```
|
||||
|
||||
It can used like any other module type:
|
||||
```
|
||||
acme_cc_defaults {
|
||||
name: "acme_defaults",
|
||||
cflags: ["-DGENERIC"],
|
||||
soong_config_variables: {
|
||||
board: {
|
||||
soc_a: {
|
||||
cflags: ["-DSOC_A"],
|
||||
},
|
||||
soc_b: {
|
||||
cflags: ["-DSOC_B"],
|
||||
},
|
||||
},
|
||||
feature: {
|
||||
cflags: ["-DFEATURE"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cc_library {
|
||||
name: "libacme_foo",
|
||||
defaults: ["acme_defaults"],
|
||||
srcs: ["*.cpp"],
|
||||
}
|
||||
```
|
||||
|
||||
With the `BoardConfig.mk` snippet above, libacme_foo would build with
|
||||
cflags "-DGENERIC -DSOC_A -DFEATURE".
|
||||
|
||||
`soong_config_module_type` modules will work best when used to wrap defaults
|
||||
modules (`cc_defaults`, `java_defaults`, etc.), which can then be referenced
|
||||
by all of the vendor's other modules using the normal namespace and visibility
|
||||
rules.
|
||||
|
||||
## Build logic
|
||||
|
||||
The build logic is written in Go using the
|
||||
[blueprint](http://godoc.org/github.com/google/blueprint) framework. Build
|
||||
logic receives module definitions parsed into Go structures using reflection
|
||||
and produces build rules. The build rules are collected by blueprint and
|
||||
written to a [ninja](http://ninja-build.org) build file.
|
||||
|
||||
## Other documentation
|
||||
|
||||
* [Best Practices](docs/best_practices.md)
|
||||
* [Build Performance](docs/perf.md)
|
||||
* [Generating CLion Projects](docs/clion.md)
|
||||
* [Generating YouCompleteMe/VSCode compile\_commands.json file](docs/compdb.md)
|
||||
* Make-specific documentation: [build/make/README.md](https://android.googlesource.com/platform/build/+/master/README.md)
|
||||
|
||||
## Developing for Soong
|
||||
|
||||
To load Soong code in a Go-aware IDE, create a directory outside your android tree and then:
|
||||
|
|
|
@ -29,6 +29,8 @@ import (
|
|||
"github.com/google/blueprint/bootstrap"
|
||||
"github.com/google/blueprint/pathtools"
|
||||
"github.com/google/blueprint/proptools"
|
||||
|
||||
"android/soong/android/soongconfig"
|
||||
)
|
||||
|
||||
var Bool = proptools.Bool
|
||||
|
@ -66,19 +68,7 @@ type DeviceConfig struct {
|
|||
*deviceConfig
|
||||
}
|
||||
|
||||
type VendorConfig interface {
|
||||
// Bool interprets the variable named `name` as a boolean, returning true if, after
|
||||
// lowercasing, it matches one of "1", "y", "yes", "on", or "true". Unset, or any other
|
||||
// value will return false.
|
||||
Bool(name string) bool
|
||||
|
||||
// String returns the string value of `name`. If the variable was not set, it will
|
||||
// return the empty string.
|
||||
String(name string) string
|
||||
|
||||
// IsSet returns whether the variable `name` was set by Make.
|
||||
IsSet(name string) bool
|
||||
}
|
||||
type VendorConfig soongconfig.SoongConfig
|
||||
|
||||
type config struct {
|
||||
FileConfigurableOptions
|
||||
|
@ -128,8 +118,6 @@ type deviceConfig struct {
|
|||
OncePer
|
||||
}
|
||||
|
||||
type vendorConfig map[string]string
|
||||
|
||||
type jsonConfigurable interface {
|
||||
SetDefaultConfig()
|
||||
}
|
||||
|
@ -1137,21 +1125,7 @@ func (c *config) XOMDisabledForPath(path string) bool {
|
|||
}
|
||||
|
||||
func (c *config) VendorConfig(name string) VendorConfig {
|
||||
return vendorConfig(c.productVariables.VendorVars[name])
|
||||
}
|
||||
|
||||
func (c vendorConfig) Bool(name string) bool {
|
||||
v := strings.ToLower(c[name])
|
||||
return v == "1" || v == "y" || v == "yes" || v == "on" || v == "true"
|
||||
}
|
||||
|
||||
func (c vendorConfig) String(name string) string {
|
||||
return c[name]
|
||||
}
|
||||
|
||||
func (c vendorConfig) IsSet(name string) bool {
|
||||
_, ok := c[name]
|
||||
return ok
|
||||
return soongconfig.Config(c.productVariables.VendorVars[name])
|
||||
}
|
||||
|
||||
func (c *config) NdkAbis() bool {
|
||||
|
|
|
@ -34,6 +34,9 @@ type LoadHookContext interface {
|
|||
AppendProperties(...interface{})
|
||||
PrependProperties(...interface{})
|
||||
CreateModule(ModuleFactory, ...interface{}) Module
|
||||
|
||||
registerScopedModuleType(name string, factory blueprint.ModuleFactory)
|
||||
moduleFactories() map[string]blueprint.ModuleFactory
|
||||
}
|
||||
|
||||
func AddLoadHook(m blueprint.Module, hook func(LoadHookContext)) {
|
||||
|
@ -52,6 +55,10 @@ type loadHookContext struct {
|
|||
module Module
|
||||
}
|
||||
|
||||
func (l *loadHookContext) moduleFactories() map[string]blueprint.ModuleFactory {
|
||||
return l.bp.ModuleFactories()
|
||||
}
|
||||
|
||||
func (l *loadHookContext) AppendProperties(props ...interface{}) {
|
||||
for _, p := range props {
|
||||
err := proptools.AppendMatchingProperties(l.Module().base().customizableProperties,
|
||||
|
@ -101,6 +108,10 @@ func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface
|
|||
return module
|
||||
}
|
||||
|
||||
func (l *loadHookContext) registerScopedModuleType(name string, factory blueprint.ModuleFactory) {
|
||||
l.bp.RegisterScopedModuleType(name, factory)
|
||||
}
|
||||
|
||||
type InstallHookContext interface {
|
||||
ModuleContext
|
||||
Path() InstallPath
|
||||
|
|
|
@ -62,6 +62,7 @@ type EarlyModuleContext interface {
|
|||
ModuleName() string
|
||||
ModuleDir() string
|
||||
ModuleType() string
|
||||
BlueprintsFile() string
|
||||
|
||||
ContainsProperty(name string) bool
|
||||
Errorf(pos scanner.Position, fmt string, args ...interface{})
|
||||
|
@ -519,9 +520,13 @@ func (k moduleKind) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
func initAndroidModuleBase(m Module) {
|
||||
m.base().module = m
|
||||
}
|
||||
|
||||
func InitAndroidModule(m Module) {
|
||||
initAndroidModuleBase(m)
|
||||
base := m.base()
|
||||
base.module = m
|
||||
|
||||
m.AddProperties(
|
||||
&base.nameProperties,
|
||||
|
|
|
@ -162,6 +162,12 @@ func (r *NameResolver) findNamespace(path string) (namespace *Namespace) {
|
|||
return namespace
|
||||
}
|
||||
|
||||
// A NamelessModule can never be looked up by name. It must still implement Name(), but the return
|
||||
// value doesn't have to be unique.
|
||||
type NamelessModule interface {
|
||||
Nameless()
|
||||
}
|
||||
|
||||
func (r *NameResolver) NewModule(ctx blueprint.NamespaceContext, moduleGroup blueprint.ModuleGroup, module blueprint.Module) (namespace blueprint.Namespace, errs []error) {
|
||||
// if this module is a namespace, then save it to our list of namespaces
|
||||
newNamespace, ok := module.(*NamespaceModule)
|
||||
|
@ -173,6 +179,10 @@ func (r *NameResolver) NewModule(ctx blueprint.NamespaceContext, moduleGroup blu
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
if _, ok := module.(NamelessModule); ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if this module is not a namespace, then save it into the appropriate namespace
|
||||
ns := r.findNamespaceFromCtx(ctx)
|
||||
|
||||
|
|
369
android/soong_config_modules.go
Normal file
369
android/soong_config_modules.go
Normal file
|
@ -0,0 +1,369 @@
|
|||
// Copyright 2019 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package android
|
||||
|
||||
// This file provides module types that implement wrapper module types that add conditionals on
|
||||
// Soong config variables.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/google/blueprint"
|
||||
"github.com/google/blueprint/parser"
|
||||
"github.com/google/blueprint/proptools"
|
||||
|
||||
"android/soong/android/soongconfig"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
|
||||
RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
|
||||
RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
|
||||
RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
|
||||
}
|
||||
|
||||
type soongConfigModuleTypeImport struct {
|
||||
ModuleBase
|
||||
properties soongConfigModuleTypeImportProperties
|
||||
}
|
||||
|
||||
type soongConfigModuleTypeImportProperties struct {
|
||||
From string
|
||||
Module_types []string
|
||||
}
|
||||
|
||||
// soong_config_module_type_import imports module types with conditionals on Soong config
|
||||
// variables from another Android.bp file. The imported module type will exist for all
|
||||
// modules after the import in the Android.bp file.
|
||||
//
|
||||
// For example, an Android.bp file could have:
|
||||
//
|
||||
// soong_config_module_type_import {
|
||||
// from: "device/acme/Android.bp.bp",
|
||||
// module_types: ["acme_cc_defaults"],
|
||||
// }
|
||||
//
|
||||
// acme_cc_defaults {
|
||||
// name: "acme_defaults",
|
||||
// cflags: ["-DGENERIC"],
|
||||
// soong_config_variables: {
|
||||
// board: {
|
||||
// soc_a: {
|
||||
// cflags: ["-DSOC_A"],
|
||||
// },
|
||||
// soc_b: {
|
||||
// cflags: ["-DSOC_B"],
|
||||
// },
|
||||
// },
|
||||
// feature: {
|
||||
// cflags: ["-DFEATURE"],
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// cc_library {
|
||||
// name: "libacme_foo",
|
||||
// defaults: ["acme_defaults"],
|
||||
// srcs: ["*.cpp"],
|
||||
// }
|
||||
//
|
||||
// And device/acme/Android.bp could have:
|
||||
//
|
||||
// soong_config_module_type {
|
||||
// name: "acme_cc_defaults",
|
||||
// module_type: "cc_defaults",
|
||||
// config_namespace: "acme",
|
||||
// variables: ["board", "feature"],
|
||||
// properties: ["cflags", "srcs"],
|
||||
// }
|
||||
//
|
||||
// soong_config_string_variable {
|
||||
// name: "board",
|
||||
// values: ["soc_a", "soc_b"],
|
||||
// }
|
||||
//
|
||||
// soong_config_bool_variable {
|
||||
// name: "feature",
|
||||
// }
|
||||
//
|
||||
// If an acme BoardConfig.mk file contained:
|
||||
//
|
||||
// SOONG_CONFIG_NAMESPACES += acme
|
||||
// SOONG_CONFIG_acme += \
|
||||
// board \
|
||||
// feature \
|
||||
//
|
||||
// SOONG_CONFIG_acme_board := soc_a
|
||||
// SOONG_CONFIG_acme_feature := true
|
||||
//
|
||||
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
|
||||
func soongConfigModuleTypeImportFactory() Module {
|
||||
module := &soongConfigModuleTypeImport{}
|
||||
|
||||
module.AddProperties(&module.properties)
|
||||
AddLoadHook(module, func(ctx LoadHookContext) {
|
||||
importModuleTypes(ctx, module.properties.From, module.properties.Module_types...)
|
||||
})
|
||||
|
||||
initAndroidModuleBase(module)
|
||||
return module
|
||||
}
|
||||
|
||||
func (m *soongConfigModuleTypeImport) Name() string {
|
||||
return "soong_config_module_type_import_" + soongconfig.CanonicalizeToProperty(m.properties.From)
|
||||
}
|
||||
|
||||
func (*soongConfigModuleTypeImport) Nameless() {}
|
||||
func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {}
|
||||
|
||||
// Create dummy modules for soong_config_module_type and soong_config_*_variable
|
||||
|
||||
type soongConfigModuleTypeModule struct {
|
||||
ModuleBase
|
||||
properties soongconfig.ModuleTypeProperties
|
||||
}
|
||||
|
||||
// soong_config_module_type defines module types with conditionals on Soong config
|
||||
// variables from another Android.bp file. The new module type will exist for all
|
||||
// modules after the definition in an Android.bp file, and can be imported into other
|
||||
// Android.bp files using soong_config_module_type_import.
|
||||
//
|
||||
// For example, an Android.bp file could have:
|
||||
//
|
||||
// soong_config_module_type {
|
||||
// name: "acme_cc_defaults",
|
||||
// module_type: "cc_defaults",
|
||||
// config_namespace: "acme",
|
||||
// variables: ["board", "feature"],
|
||||
// properties: ["cflags", "srcs"],
|
||||
// }
|
||||
//
|
||||
// soong_config_string_variable {
|
||||
// name: "board",
|
||||
// values: ["soc_a", "soc_b"],
|
||||
// }
|
||||
//
|
||||
// soong_config_bool_variable {
|
||||
// name: "feature",
|
||||
// }
|
||||
//
|
||||
// acme_cc_defaults {
|
||||
// name: "acme_defaults",
|
||||
// cflags: ["-DGENERIC"],
|
||||
// soong_config_variables: {
|
||||
// board: {
|
||||
// soc_a: {
|
||||
// cflags: ["-DSOC_A"],
|
||||
// },
|
||||
// soc_b: {
|
||||
// cflags: ["-DSOC_B"],
|
||||
// },
|
||||
// },
|
||||
// feature: {
|
||||
// cflags: ["-DFEATURE"],
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// cc_library {
|
||||
// name: "libacme_foo",
|
||||
// defaults: ["acme_defaults"],
|
||||
// srcs: ["*.cpp"],
|
||||
// }
|
||||
//
|
||||
// And device/acme/Android.bp could have:
|
||||
//
|
||||
// If an acme BoardConfig.mk file contained:
|
||||
//
|
||||
// SOONG_CONFIG_NAMESPACES += acme
|
||||
// SOONG_CONFIG_acme += \
|
||||
// board \
|
||||
// feature \
|
||||
//
|
||||
// SOONG_CONFIG_acme_board := soc_a
|
||||
// SOONG_CONFIG_acme_feature := true
|
||||
//
|
||||
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
|
||||
func soongConfigModuleTypeFactory() Module {
|
||||
module := &soongConfigModuleTypeModule{}
|
||||
|
||||
module.AddProperties(&module.properties)
|
||||
|
||||
AddLoadHook(module, func(ctx LoadHookContext) {
|
||||
// A soong_config_module_type module should implicitly import itself.
|
||||
importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name)
|
||||
})
|
||||
|
||||
initAndroidModuleBase(module)
|
||||
|
||||
return module
|
||||
}
|
||||
|
||||
func (m *soongConfigModuleTypeModule) Name() string {
|
||||
return m.properties.Name
|
||||
}
|
||||
func (*soongConfigModuleTypeModule) Nameless() {}
|
||||
func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
|
||||
|
||||
type soongConfigStringVariableDummyModule struct {
|
||||
ModuleBase
|
||||
properties soongconfig.VariableProperties
|
||||
stringProperties soongconfig.StringVariableProperties
|
||||
}
|
||||
|
||||
type soongConfigBoolVariableDummyModule struct {
|
||||
ModuleBase
|
||||
properties soongconfig.VariableProperties
|
||||
}
|
||||
|
||||
// soong_config_string_variable defines a variable and a set of possible string values for use
|
||||
// in a soong_config_module_type definition.
|
||||
func soongConfigStringVariableDummyFactory() Module {
|
||||
module := &soongConfigStringVariableDummyModule{}
|
||||
module.AddProperties(&module.properties, &module.stringProperties)
|
||||
initAndroidModuleBase(module)
|
||||
return module
|
||||
}
|
||||
|
||||
// soong_config_string_variable defines a variable with true or false values for use
|
||||
// in a soong_config_module_type definition.
|
||||
func soongConfigBoolVariableDummyFactory() Module {
|
||||
module := &soongConfigBoolVariableDummyModule{}
|
||||
module.AddProperties(&module.properties)
|
||||
initAndroidModuleBase(module)
|
||||
return module
|
||||
}
|
||||
|
||||
func (m *soongConfigStringVariableDummyModule) Name() string {
|
||||
return m.properties.Name
|
||||
}
|
||||
func (*soongConfigStringVariableDummyModule) Nameless() {}
|
||||
func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
|
||||
|
||||
func (m *soongConfigBoolVariableDummyModule) Name() string {
|
||||
return m.properties.Name
|
||||
}
|
||||
func (*soongConfigBoolVariableDummyModule) Nameless() {}
|
||||
func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
|
||||
|
||||
func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
|
||||
from = filepath.Clean(from)
|
||||
if filepath.Ext(from) != ".bp" {
|
||||
ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(from, "../") {
|
||||
ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree",
|
||||
from)
|
||||
return
|
||||
}
|
||||
|
||||
moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from)
|
||||
if moduleTypeDefinitions == nil {
|
||||
return
|
||||
}
|
||||
for _, moduleType := range moduleTypes {
|
||||
if factory, ok := moduleTypeDefinitions[moduleType]; ok {
|
||||
ctx.registerScopedModuleType(moduleType, factory)
|
||||
} else {
|
||||
ctx.PropertyErrorf("module_types", "module type %q not defined in %q",
|
||||
moduleType, from)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
|
||||
// result so each file is only parsed once.
|
||||
func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory {
|
||||
type onceKeyType string
|
||||
key := NewCustomOnceKey(onceKeyType(filepath.Clean(from)))
|
||||
|
||||
reportErrors := func(ctx LoadHookContext, filename string, errs ...error) {
|
||||
for _, err := range errs {
|
||||
if parseErr, ok := err.(*parser.ParseError); ok {
|
||||
ctx.Errorf(parseErr.Pos, "%s", parseErr.Err)
|
||||
} else {
|
||||
ctx.Errorf(scanner.Position{Filename: filename}, "%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.Config().Once(key, func() interface{} {
|
||||
r, err := ctx.Config().fs.Open(from)
|
||||
if err != nil {
|
||||
ctx.PropertyErrorf("from", "failed to open %q: %s", from, err)
|
||||
return (map[string]blueprint.ModuleFactory)(nil)
|
||||
}
|
||||
|
||||
mtDef, errs := soongconfig.Parse(r, from)
|
||||
|
||||
if len(errs) > 0 {
|
||||
reportErrors(ctx, from, errs...)
|
||||
return (map[string]blueprint.ModuleFactory)(nil)
|
||||
}
|
||||
|
||||
globalModuleTypes := ctx.moduleFactories()
|
||||
|
||||
factories := make(map[string]blueprint.ModuleFactory)
|
||||
|
||||
for name, moduleType := range mtDef.ModuleTypes {
|
||||
factory := globalModuleTypes[moduleType.BaseModuleType]
|
||||
if factory != nil {
|
||||
factories[name] = soongConfigModuleFactory(factory, moduleType)
|
||||
} else {
|
||||
reportErrors(ctx, from,
|
||||
fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Failed() {
|
||||
return (map[string]blueprint.ModuleFactory)(nil)
|
||||
}
|
||||
|
||||
return factories
|
||||
}).(map[string]blueprint.ModuleFactory)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
|
||||
if conditionalFactoryProps.IsValid() {
|
||||
return func() (blueprint.Module, []interface{}) {
|
||||
module, props := factory()
|
||||
|
||||
conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps.Elem())
|
||||
props = append(props, conditionalProps.Interface())
|
||||
|
||||
AddLoadHook(module, func(ctx LoadHookContext) {
|
||||
config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
|
||||
for _, ps := range soongconfig.PropertiesToApply(moduleType, conditionalProps, config) {
|
||||
ctx.AppendProperties(ps)
|
||||
}
|
||||
})
|
||||
|
||||
return module, props
|
||||
}
|
||||
} else {
|
||||
return factory
|
||||
}
|
||||
}
|
141
android/soong_config_modules_test.go
Normal file
141
android/soong_config_modules_test.go
Normal file
|
@ -0,0 +1,141 @@
|
|||
// Copyright 2019 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package android
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type soongConfigTestModule struct {
|
||||
ModuleBase
|
||||
props soongConfigTestModuleProperties
|
||||
}
|
||||
|
||||
type soongConfigTestModuleProperties struct {
|
||||
Cflags []string
|
||||
}
|
||||
|
||||
func soongConfigTestModuleFactory() Module {
|
||||
m := &soongConfigTestModule{}
|
||||
m.AddProperties(&m.props)
|
||||
InitAndroidModule(m)
|
||||
return m
|
||||
}
|
||||
|
||||
func (t soongConfigTestModule) GenerateAndroidBuildActions(ModuleContext) {}
|
||||
|
||||
func TestSoongConfigModule(t *testing.T) {
|
||||
configBp := `
|
||||
soong_config_module_type {
|
||||
name: "acme_test_defaults",
|
||||
module_type: "test_defaults",
|
||||
config_namespace: "acme",
|
||||
variables: ["board", "feature1", "feature2", "feature3"],
|
||||
properties: ["cflags", "srcs"],
|
||||
}
|
||||
|
||||
soong_config_string_variable {
|
||||
name: "board",
|
||||
values: ["soc_a", "soc_b"],
|
||||
}
|
||||
|
||||
soong_config_bool_variable {
|
||||
name: "feature1",
|
||||
}
|
||||
|
||||
soong_config_bool_variable {
|
||||
name: "feature2",
|
||||
}
|
||||
|
||||
soong_config_bool_variable {
|
||||
name: "feature3",
|
||||
}
|
||||
`
|
||||
|
||||
importBp := `
|
||||
soong_config_module_type_import {
|
||||
from: "SoongConfig.bp",
|
||||
module_types: ["acme_test_defaults"],
|
||||
}
|
||||
`
|
||||
|
||||
bp := `
|
||||
acme_test_defaults {
|
||||
name: "foo",
|
||||
cflags: ["-DGENERIC"],
|
||||
soong_config_variables: {
|
||||
board: {
|
||||
soc_a: {
|
||||
cflags: ["-DSOC_A"],
|
||||
},
|
||||
soc_b: {
|
||||
cflags: ["-DSOC_B"],
|
||||
},
|
||||
},
|
||||
feature1: {
|
||||
cflags: ["-DFEATURE1"],
|
||||
},
|
||||
feature2: {
|
||||
cflags: ["-DFEATURE2"],
|
||||
},
|
||||
feature3: {
|
||||
cflags: ["-DFEATURE3"],
|
||||
},
|
||||
},
|
||||
}
|
||||
`
|
||||
|
||||
run := func(t *testing.T, bp string, fs map[string][]byte) {
|
||||
config := TestConfig(buildDir, nil, bp, fs)
|
||||
|
||||
config.TestProductVariables.VendorVars = map[string]map[string]string{
|
||||
"acme": map[string]string{
|
||||
"board": "soc_a",
|
||||
"feature1": "true",
|
||||
"feature2": "false",
|
||||
// FEATURE3 unset
|
||||
},
|
||||
}
|
||||
|
||||
ctx := NewTestContext()
|
||||
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", soongConfigTestModuleFactory)
|
||||
ctx.Register(config)
|
||||
|
||||
_, errs := ctx.ParseBlueprintsFiles("Android.bp")
|
||||
FailIfErrored(t, errs)
|
||||
_, errs = ctx.PrepareBuildActions(config)
|
||||
FailIfErrored(t, errs)
|
||||
|
||||
foo := ctx.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
|
||||
if g, w := foo.props.Cflags, []string{"-DGENERIC", "-DSOC_A", "-DFEATURE1"}; !reflect.DeepEqual(g, w) {
|
||||
t.Errorf("wanted foo cflags %q, got %q", w, g)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("single file", func(t *testing.T) {
|
||||
run(t, configBp+bp, nil)
|
||||
})
|
||||
|
||||
t.Run("import", func(t *testing.T) {
|
||||
run(t, importBp+bp, map[string][]byte{
|
||||
"SoongConfig.bp": []byte(configBp),
|
||||
})
|
||||
})
|
||||
}
|
51
android/soongconfig/config.go
Normal file
51
android/soongconfig/config.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2020 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 soongconfig
|
||||
|
||||
import "strings"
|
||||
|
||||
type SoongConfig interface {
|
||||
// Bool interprets the variable named `name` as a boolean, returning true if, after
|
||||
// lowercasing, it matches one of "1", "y", "yes", "on", or "true". Unset, or any other
|
||||
// value will return false.
|
||||
Bool(name string) bool
|
||||
|
||||
// String returns the string value of `name`. If the variable was not set, it will
|
||||
// return the empty string.
|
||||
String(name string) string
|
||||
|
||||
// IsSet returns whether the variable `name` was set by Make.
|
||||
IsSet(name string) bool
|
||||
}
|
||||
|
||||
func Config(vars map[string]string) SoongConfig {
|
||||
return soongConfig(vars)
|
||||
}
|
||||
|
||||
type soongConfig map[string]string
|
||||
|
||||
func (c soongConfig) Bool(name string) bool {
|
||||
v := strings.ToLower(c[name])
|
||||
return v == "1" || v == "y" || v == "yes" || v == "on" || v == "true"
|
||||
}
|
||||
|
||||
func (c soongConfig) String(name string) string {
|
||||
return c[name]
|
||||
}
|
||||
|
||||
func (c soongConfig) IsSet(name string) bool {
|
||||
_, ok := c[name]
|
||||
return ok
|
||||
}
|
517
android/soongconfig/modules.go
Normal file
517
android/soongconfig/modules.go
Normal file
|
@ -0,0 +1,517 @@
|
|||
// Copyright 2020 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 soongconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/google/blueprint"
|
||||
"github.com/google/blueprint/parser"
|
||||
"github.com/google/blueprint/proptools"
|
||||
)
|
||||
|
||||
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.
|
||||
func Parse(r io.Reader, from string) (*SoongConfigDefinition, []error) {
|
||||
scope := parser.NewScope(nil)
|
||||
file, errs := parser.ParseAndEval(from, r, scope)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
mtDef := &SoongConfigDefinition{
|
||||
ModuleTypes: make(map[string]*ModuleType),
|
||||
variables: make(map[string]soongConfigVariable),
|
||||
}
|
||||
|
||||
for _, def := range file.Defs {
|
||||
switch def := def.(type) {
|
||||
case *parser.Module:
|
||||
newErrs := processImportModuleDef(mtDef, def)
|
||||
|
||||
if len(newErrs) > 0 {
|
||||
errs = append(errs, newErrs...)
|
||||
}
|
||||
|
||||
case *parser.Assignment:
|
||||
// Already handled via Scope object
|
||||
default:
|
||||
panic("unknown definition type")
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
for name, moduleType := range mtDef.ModuleTypes {
|
||||
for _, varName := range moduleType.variableNames {
|
||||
if v, ok := mtDef.variables[varName]; ok {
|
||||
moduleType.Variables = append(moduleType.Variables, v)
|
||||
} else {
|
||||
return nil, []error{
|
||||
fmt.Errorf("unknown variable %q in module type %q", varName, name),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mtDef, nil
|
||||
}
|
||||
|
||||
func processImportModuleDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
|
||||
switch def.Type {
|
||||
case "soong_config_module_type":
|
||||
return processModuleTypeDef(v, def)
|
||||
case "soong_config_string_variable":
|
||||
return processStringVariableDef(v, def)
|
||||
case "soong_config_bool_variable":
|
||||
return processBoolVariableDef(v, def)
|
||||
default:
|
||||
// Unknown module types will be handled when the file is parsed as a normal
|
||||
// Android.bp file.
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ModuleTypeProperties struct {
|
||||
// the name of the new module type. Unlike most modules, this name does not need to be unique,
|
||||
// although only one module type with any name will be importable into an Android.bp file.
|
||||
Name string
|
||||
|
||||
// the module type that this module type will extend.
|
||||
Module_type string
|
||||
|
||||
// the SOONG_CONFIG_NAMESPACE value from a BoardConfig.mk that this module type will read
|
||||
// configuration variables from.
|
||||
Config_namespace string
|
||||
|
||||
// the list of SOONG_CONFIG variables that this module type will read
|
||||
Variables []string
|
||||
|
||||
// the list of properties that this module type will extend.
|
||||
Properties []string
|
||||
}
|
||||
|
||||
func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
|
||||
|
||||
props := &ModuleTypeProperties{}
|
||||
|
||||
_, errs = proptools.UnpackProperties(def.Properties, props)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
if props.Name == "" {
|
||||
errs = append(errs, fmt.Errorf("name property must be set"))
|
||||
}
|
||||
|
||||
if props.Config_namespace == "" {
|
||||
errs = append(errs, fmt.Errorf("config_namespace property must be set"))
|
||||
}
|
||||
|
||||
if props.Module_type == "" {
|
||||
errs = append(errs, fmt.Errorf("module_type property must be set"))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
mt := &ModuleType{
|
||||
affectableProperties: props.Properties,
|
||||
ConfigNamespace: props.Config_namespace,
|
||||
BaseModuleType: props.Module_type,
|
||||
variableNames: props.Variables,
|
||||
}
|
||||
v.ModuleTypes[props.Name] = mt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type VariableProperties struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type StringVariableProperties struct {
|
||||
Values []string
|
||||
}
|
||||
|
||||
func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
|
||||
stringProps := &StringVariableProperties{}
|
||||
|
||||
base, errs := processVariableDef(def, stringProps)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
if len(stringProps.Values) == 0 {
|
||||
return []error{fmt.Errorf("values property must be set")}
|
||||
}
|
||||
|
||||
v.variables[base.variable] = &stringVariable{
|
||||
baseVariable: base,
|
||||
values: CanonicalizeToProperties(stringProps.Values),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processBoolVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
|
||||
base, errs := processVariableDef(def)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
v.variables[base.variable] = &boolVariable{
|
||||
baseVariable: base,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processVariableDef(def *parser.Module,
|
||||
extraProps ...interface{}) (cond baseVariable, errs []error) {
|
||||
|
||||
props := &VariableProperties{}
|
||||
|
||||
allProps := append([]interface{}{props}, extraProps...)
|
||||
|
||||
_, errs = proptools.UnpackProperties(def.Properties, allProps...)
|
||||
if len(errs) > 0 {
|
||||
return baseVariable{}, errs
|
||||
}
|
||||
|
||||
if props.Name == "" {
|
||||
return baseVariable{}, []error{fmt.Errorf("name property must be set")}
|
||||
}
|
||||
|
||||
return baseVariable{
|
||||
variable: props.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type SoongConfigDefinition struct {
|
||||
ModuleTypes map[string]*ModuleType
|
||||
|
||||
variables map[string]soongConfigVariable
|
||||
}
|
||||
|
||||
// CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
|
||||
// property layout for the Soong config variables, with each possible value an interface{} that
|
||||
// contains a nil pointer to another newly constructed type that contains the affectable properties.
|
||||
// The reflect.Value will be cloned for each call to the Soong config module type's factory method.
|
||||
//
|
||||
// For example, the acme_cc_defaults example above would
|
||||
// produce a reflect.Value whose type is:
|
||||
// *struct {
|
||||
// Soong_config_variables struct {
|
||||
// Board struct {
|
||||
// Soc_a interface{}
|
||||
// Soc_b interface{}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// And whose value is:
|
||||
// &{
|
||||
// Soong_config_variables: {
|
||||
// Board: {
|
||||
// Soc_a: (*struct{ Cflags []string })(nil),
|
||||
// Soc_b: (*struct{ Cflags []string })(nil),
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
func CreateProperties(factory blueprint.ModuleFactory, moduleType *ModuleType) reflect.Value {
|
||||
var fields []reflect.StructField
|
||||
|
||||
_, factoryProps := factory()
|
||||
affectablePropertiesType := createAffectablePropertiesType(moduleType.affectableProperties, factoryProps)
|
||||
if affectablePropertiesType == nil {
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
for _, c := range moduleType.Variables {
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: proptools.FieldNameForProperty(c.variableProperty()),
|
||||
Type: c.variableValuesType(),
|
||||
})
|
||||
}
|
||||
|
||||
typ := reflect.StructOf([]reflect.StructField{{
|
||||
Name: soongConfigProperty,
|
||||
Type: reflect.StructOf(fields),
|
||||
}})
|
||||
|
||||
props := reflect.New(typ)
|
||||
structConditions := props.Elem().FieldByName(soongConfigProperty)
|
||||
|
||||
for i, c := range moduleType.Variables {
|
||||
c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
// createAffectablePropertiesType creates a reflect.Type of a struct that has a field for each affectable property
|
||||
// that exists in factoryProps.
|
||||
func createAffectablePropertiesType(affectableProperties []string, factoryProps []interface{}) reflect.Type {
|
||||
affectableProperties = append([]string(nil), affectableProperties...)
|
||||
sort.Strings(affectableProperties)
|
||||
|
||||
var recurse func(prefix string, aps []string) ([]string, reflect.Type)
|
||||
recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
|
||||
var fields []reflect.StructField
|
||||
|
||||
for len(affectableProperties) > 0 {
|
||||
p := affectableProperties[0]
|
||||
if !strings.HasPrefix(affectableProperties[0], prefix) {
|
||||
break
|
||||
}
|
||||
affectableProperties = affectableProperties[1:]
|
||||
|
||||
nestedProperty := strings.TrimPrefix(p, prefix)
|
||||
if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
|
||||
var nestedType reflect.Type
|
||||
nestedPrefix := nestedProperty[:i+1]
|
||||
|
||||
affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
|
||||
|
||||
if nestedType != nil {
|
||||
nestedFieldName := proptools.FieldNameForProperty(strings.TrimSuffix(nestedPrefix, "."))
|
||||
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: nestedFieldName,
|
||||
Type: nestedType,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
typ := typeForPropertyFromPropertyStructs(factoryProps, p)
|
||||
if typ != nil {
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: proptools.FieldNameForProperty(nestedProperty),
|
||||
Type: typ,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var typ reflect.Type
|
||||
if len(fields) > 0 {
|
||||
typ = reflect.StructOf(fields)
|
||||
}
|
||||
return affectableProperties, typ
|
||||
}
|
||||
|
||||
affectableProperties, typ := recurse("", affectableProperties)
|
||||
if len(affectableProperties) > 0 {
|
||||
panic(fmt.Errorf("didn't handle all affectable properties"))
|
||||
}
|
||||
|
||||
if typ != nil {
|
||||
return reflect.PtrTo(typ)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func typeForPropertyFromPropertyStructs(psList []interface{}, property string) reflect.Type {
|
||||
for _, ps := range psList {
|
||||
if typ := typeForPropertyFromPropertyStruct(ps, property); typ != nil {
|
||||
return typ
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.Type {
|
||||
v := reflect.ValueOf(ps)
|
||||
for len(property) > 0 {
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
return nil
|
||||
} else {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
v = reflect.Zero(v.Type().Elem())
|
||||
} else {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
if index := strings.IndexRune(property, '.'); index >= 0 {
|
||||
prefix := property[:index]
|
||||
property = property[index+1:]
|
||||
|
||||
v = v.FieldByName(proptools.FieldNameForProperty(prefix))
|
||||
} else {
|
||||
f := v.FieldByName(proptools.FieldNameForProperty(property))
|
||||
if !f.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return f.Type()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
|
||||
// based on SoongConfig values.
|
||||
func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) []interface{} {
|
||||
var ret []interface{}
|
||||
props = props.Elem().FieldByName(soongConfigProperty)
|
||||
for i, c := range moduleType.Variables {
|
||||
if ps := c.PropertiesToApply(config, props.Field(i)); ps != nil {
|
||||
ret = append(ret, ps)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type ModuleType struct {
|
||||
BaseModuleType string
|
||||
ConfigNamespace string
|
||||
Variables []soongConfigVariable
|
||||
|
||||
affectableProperties []string
|
||||
variableNames []string
|
||||
}
|
||||
|
||||
type soongConfigVariable interface {
|
||||
// variableProperty returns the name of the variable.
|
||||
variableProperty() string
|
||||
|
||||
// conditionalValuesType returns a reflect.Type that contains an interface{} for each possible value.
|
||||
variableValuesType() reflect.Type
|
||||
|
||||
// initializeProperties is passed a reflect.Value of the reflect.Type returned by conditionalValuesType and a
|
||||
// reflect.Type of the affectable properties, and should initialize each interface{} in the reflect.Value with
|
||||
// the zero value of the affectable properties type.
|
||||
initializeProperties(v reflect.Value, typ reflect.Type)
|
||||
|
||||
// PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
|
||||
// to the module.
|
||||
PropertiesToApply(config SoongConfig, values reflect.Value) interface{}
|
||||
}
|
||||
|
||||
type baseVariable struct {
|
||||
variable string
|
||||
}
|
||||
|
||||
func (c *baseVariable) variableProperty() string {
|
||||
return CanonicalizeToProperty(c.variable)
|
||||
}
|
||||
|
||||
type stringVariable struct {
|
||||
baseVariable
|
||||
values []string
|
||||
}
|
||||
|
||||
func (s *stringVariable) variableValuesType() reflect.Type {
|
||||
var fields []reflect.StructField
|
||||
|
||||
for _, v := range s.values {
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: proptools.FieldNameForProperty(v),
|
||||
Type: emptyInterfaceType,
|
||||
})
|
||||
}
|
||||
|
||||
return reflect.StructOf(fields)
|
||||
}
|
||||
|
||||
func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
|
||||
for i := range s.values {
|
||||
v.Field(i).Set(reflect.Zero(typ))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
|
||||
for j, v := range s.values {
|
||||
if config.String(s.variable) == v {
|
||||
return values.Field(j).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type boolVariable struct {
|
||||
baseVariable
|
||||
}
|
||||
|
||||
func (b boolVariable) variableValuesType() reflect.Type {
|
||||
return emptyInterfaceType
|
||||
}
|
||||
|
||||
func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
|
||||
v.Set(reflect.Zero(typ))
|
||||
}
|
||||
|
||||
func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
|
||||
if config.Bool(b.variable) {
|
||||
return values.Interface()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CanonicalizeToProperty(v string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
switch {
|
||||
case r >= 'A' && r <= 'Z',
|
||||
r >= 'a' && r <= 'z',
|
||||
r >= '0' && r <= '9',
|
||||
r == '_':
|
||||
return r
|
||||
default:
|
||||
return '_'
|
||||
}
|
||||
}, v)
|
||||
}
|
||||
|
||||
func CanonicalizeToProperties(values []string) []string {
|
||||
ret := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
ret[i] = CanonicalizeToProperty(v)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type emptyInterfaceStruct struct {
|
||||
i interface{}
|
||||
}
|
||||
|
||||
var emptyInterfaceType = reflect.TypeOf(emptyInterfaceStruct{}).Field(0).Type
|
249
android/soongconfig/modules_test.go
Normal file
249
android/soongconfig/modules_test.go
Normal file
|
@ -0,0 +1,249 @@
|
|||
// Copyright 2020 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 soongconfig
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_CanonicalizeToProperty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "lowercase",
|
||||
arg: "board",
|
||||
want: "board",
|
||||
},
|
||||
{
|
||||
name: "uppercase",
|
||||
arg: "BOARD",
|
||||
want: "BOARD",
|
||||
},
|
||||
{
|
||||
name: "numbers",
|
||||
arg: "BOARD123",
|
||||
want: "BOARD123",
|
||||
},
|
||||
{
|
||||
name: "underscore",
|
||||
arg: "TARGET_BOARD",
|
||||
want: "TARGET_BOARD",
|
||||
},
|
||||
{
|
||||
name: "dash",
|
||||
arg: "TARGET-BOARD",
|
||||
want: "TARGET_BOARD",
|
||||
},
|
||||
{
|
||||
name: "unicode",
|
||||
arg: "boardλ",
|
||||
want: "board_",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := CanonicalizeToProperty(tt.arg); got != tt.want {
|
||||
t.Errorf("canonicalizeToProperty() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_typeForPropertyFromPropertyStruct(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ps interface{}
|
||||
property string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
ps: struct {
|
||||
A string
|
||||
}{},
|
||||
property: "a",
|
||||
want: "string",
|
||||
},
|
||||
{
|
||||
name: "list",
|
||||
ps: struct {
|
||||
A []string
|
||||
}{},
|
||||
property: "a",
|
||||
want: "[]string",
|
||||
},
|
||||
{
|
||||
name: "missing",
|
||||
ps: struct {
|
||||
A []string
|
||||
}{},
|
||||
property: "b",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "nested",
|
||||
ps: struct {
|
||||
A struct {
|
||||
B string
|
||||
}
|
||||
}{},
|
||||
property: "a.b",
|
||||
want: "string",
|
||||
},
|
||||
{
|
||||
name: "missing nested",
|
||||
ps: struct {
|
||||
A struct {
|
||||
B string
|
||||
}
|
||||
}{},
|
||||
property: "a.c",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "not a struct",
|
||||
ps: struct {
|
||||
A string
|
||||
}{},
|
||||
property: "a.b",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "nested pointer",
|
||||
ps: struct {
|
||||
A *struct {
|
||||
B string
|
||||
}
|
||||
}{},
|
||||
property: "a.b",
|
||||
want: "string",
|
||||
},
|
||||
{
|
||||
name: "nested interface",
|
||||
ps: struct {
|
||||
A interface{}
|
||||
}{
|
||||
A: struct {
|
||||
B string
|
||||
}{},
|
||||
},
|
||||
property: "a.b",
|
||||
want: "string",
|
||||
},
|
||||
{
|
||||
name: "nested interface pointer",
|
||||
ps: struct {
|
||||
A interface{}
|
||||
}{
|
||||
A: &struct {
|
||||
B string
|
||||
}{},
|
||||
},
|
||||
property: "a.b",
|
||||
want: "string",
|
||||
},
|
||||
{
|
||||
name: "nested interface nil pointer",
|
||||
ps: struct {
|
||||
A interface{}
|
||||
}{
|
||||
A: (*struct {
|
||||
B string
|
||||
})(nil),
|
||||
},
|
||||
property: "a.b",
|
||||
want: "string",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
typ := typeForPropertyFromPropertyStruct(tt.ps, tt.property)
|
||||
got := ""
|
||||
if typ != nil {
|
||||
got = typ.String()
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("typeForPropertyFromPropertyStruct() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_createAffectablePropertiesType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
affectableProperties []string
|
||||
factoryProps interface{}
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
affectableProperties: []string{"cflags"},
|
||||
factoryProps: struct {
|
||||
Cflags string
|
||||
}{},
|
||||
want: "*struct { Cflags string }",
|
||||
},
|
||||
{
|
||||
name: "list",
|
||||
affectableProperties: []string{"cflags"},
|
||||
factoryProps: struct {
|
||||
Cflags []string
|
||||
}{},
|
||||
want: "*struct { Cflags []string }",
|
||||
},
|
||||
{
|
||||
name: "string pointer",
|
||||
affectableProperties: []string{"cflags"},
|
||||
factoryProps: struct {
|
||||
Cflags *string
|
||||
}{},
|
||||
want: "*struct { Cflags *string }",
|
||||
},
|
||||
{
|
||||
name: "subset",
|
||||
affectableProperties: []string{"cflags"},
|
||||
factoryProps: struct {
|
||||
Cflags string
|
||||
Ldflags string
|
||||
}{},
|
||||
want: "*struct { Cflags string }",
|
||||
},
|
||||
{
|
||||
name: "none",
|
||||
affectableProperties: []string{"cflags"},
|
||||
factoryProps: struct {
|
||||
Ldflags string
|
||||
}{},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
typ := createAffectablePropertiesType(tt.affectableProperties, []interface{}{tt.factoryProps})
|
||||
got := ""
|
||||
if typ != nil {
|
||||
got = typ.String()
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("createAffectablePropertiesType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -148,7 +148,7 @@ func testProductVariableModuleFactoryFactory(props interface{}) func() Module {
|
|||
clonedProps := proptools.CloneProperties(reflect.ValueOf(props)).Interface()
|
||||
m.AddProperties(clonedProps)
|
||||
|
||||
// Set a default variableProperties, this will be used as the input to the property struct filter
|
||||
// Set a default soongConfigVariableProperties, this will be used as the input to the property struct filter
|
||||
// for this test module.
|
||||
m.variableProperties = testProductVariableProperties
|
||||
InitAndroidModule(m)
|
||||
|
|
Loading…
Reference in a new issue