Select statements
See the blueprint cl for more information.
Things still to do:
- Support selecting on product variables and
variants
- Test/Support property struct reflection tags
like arch_variant, path, and variant_prepend
- Test that selects combine well with existing
configurability mechanisms like arch:, target:,
multilib:, python's version:, etc.
Bug: 323382414
Test: go tests
Change-Id: If5d1ea1ad0c4ebabffaea91d62e1a1c6f926a793
diff --git a/android/Android.bp b/android/Android.bp
index e73f355..02b7444 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -135,6 +135,7 @@
"rule_builder_test.go",
"sdk_version_test.go",
"sdk_test.go",
+ "selects_test.go",
"singleton_module_test.go",
"soong_config_modules_test.go",
"util_test.go",
diff --git a/android/module_context.go b/android/module_context.go
index 1cab630..3fc5d01 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -21,6 +21,7 @@
"strings"
"github.com/google/blueprint"
+ "github.com/google/blueprint/parser"
"github.com/google/blueprint/proptools"
)
@@ -212,6 +213,10 @@
// GenerateAndroidBuildActions. If it is called then the struct will be written out and included in
// the module-info.json generated by Make, and Make will not generate its own data for this module.
ModuleInfoJSON() *ModuleInfoJSON
+
+ // EvaluateConfiguration makes ModuleContext a valid proptools.ConfigurableEvaluator, so this context
+ // can be used to evaluate the final value of Configurable properties.
+ EvaluateConfiguration(parser.SelectType, string) (string, bool)
}
type moduleContext struct {
@@ -714,3 +719,32 @@
func (m *moduleContext) TargetRequiredModuleNames() []string {
return m.module.TargetRequiredModuleNames()
}
+
+func (m *moduleContext) EvaluateConfiguration(ty parser.SelectType, condition string) (string, bool) {
+ switch ty {
+ case parser.SelectTypeReleaseVariable:
+ if v, ok := m.Config().productVariables.BuildFlags[condition]; ok {
+ return v, true
+ }
+ return "", false
+ case parser.SelectTypeProductVariable:
+ // TODO: Might add these on a case-by-case basis
+ m.ModuleErrorf("TODO(b/323382414): Product variables are not yet supported in selects")
+ return "", false
+ case parser.SelectTypeSoongConfigVariable:
+ parts := strings.Split(condition, ":")
+ namespace := parts[0]
+ variable := parts[1]
+ if n, ok := m.Config().productVariables.VendorVars[namespace]; ok {
+ if v, ok := n[variable]; ok {
+ return v, true
+ }
+ }
+ return "", false
+ case parser.SelectTypeVariant:
+ m.ModuleErrorf("TODO(b/323382414): Variants are not yet supported in selects")
+ return "", false
+ default:
+ panic("Should be unreachable")
+ }
+}
diff --git a/android/selects_test.go b/android/selects_test.go
new file mode 100644
index 0000000..dca3789
--- /dev/null
+++ b/android/selects_test.go
@@ -0,0 +1,282 @@
+// Copyright 2024 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 (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+)
+
+func TestSelects(t *testing.T) {
+ testCases := []struct {
+ name string
+ bp string
+ provider selectsTestProvider
+ vendorVars map[string]map[string]string
+ expectedError string
+ }{
+ {
+ name: "basic string list",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a": ["a.cpp"],
+ "b": ["b.cpp"],
+ _: ["c.cpp"],
+ }),
+ }
+ `,
+ provider: selectsTestProvider{
+ my_string_list: &[]string{"c.cpp"},
+ },
+ },
+ {
+ name: "basic string",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a": "a.cpp",
+ "b": "b.cpp",
+ _: "c.cpp",
+ }),
+ }
+ `,
+ provider: selectsTestProvider{
+ my_string: proptools.StringPtr("c.cpp"),
+ },
+ },
+ {
+ name: "basic bool",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a": true,
+ "b": false,
+ _: true,
+ }),
+ }
+ `,
+ provider: selectsTestProvider{
+ my_bool: proptools.BoolPtr(true),
+ },
+ },
+ {
+ name: "Differing types",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a": "a.cpp",
+ "b": true,
+ _: "c.cpp",
+ }),
+ }
+ `,
+ expectedError: `can't assign bool value to string property "my_string\[1\]"`,
+ },
+ {
+ name: "String list non-default",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a": ["a.cpp"],
+ "b": ["b.cpp"],
+ _: ["c.cpp"],
+ }),
+ }
+ `,
+ provider: selectsTestProvider{
+ my_string_list: &[]string{"a.cpp"},
+ },
+ vendorVars: map[string]map[string]string{
+ "my_namespace": {
+ "my_variable": "a",
+ },
+ },
+ },
+ {
+ name: "String list append",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a": ["a.cpp"],
+ "b": ["b.cpp"],
+ _: ["c.cpp"],
+ }) + select(soong_config_variable("my_namespace", "my_variable_2"), {
+ "a2": ["a2.cpp"],
+ "b2": ["b2.cpp"],
+ _: ["c2.cpp"],
+ }),
+ }
+ `,
+ provider: selectsTestProvider{
+ my_string_list: &[]string{"a.cpp", "c2.cpp"},
+ },
+ vendorVars: map[string]map[string]string{
+ "my_namespace": {
+ "my_variable": "a",
+ },
+ },
+ },
+ {
+ name: "String list prepend literal",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_string_list: ["literal.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), {
+ "a2": ["a2.cpp"],
+ "b2": ["b2.cpp"],
+ _: ["c2.cpp"],
+ }),
+ }
+ `,
+ provider: selectsTestProvider{
+ my_string_list: &[]string{"literal.cpp", "c2.cpp"},
+ },
+ },
+ {
+ name: "String list append literal",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a2": ["a2.cpp"],
+ "b2": ["b2.cpp"],
+ _: ["c2.cpp"],
+ }) + ["literal.cpp"],
+ }
+ `,
+ provider: selectsTestProvider{
+ my_string_list: &[]string{"c2.cpp", "literal.cpp"},
+ },
+ },
+ {
+ name: "Can't append bools",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a": true,
+ "b": false,
+ _: true,
+ }) + false,
+ }
+ `,
+ expectedError: "my_bool: Cannot append bools",
+ },
+ {
+ name: "Append string",
+ bp: `
+ my_module_type {
+ name: "foo",
+ my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+ "a": "a",
+ "b": "b",
+ _: "c",
+ }) + ".cpp",
+ }
+ `,
+ provider: selectsTestProvider{
+ my_string: proptools.StringPtr("c.cpp"),
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ fixtures := GroupFixturePreparers(
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.RegisterModuleType("my_module_type", newSelectsMockModule)
+ }),
+ FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+ variables.VendorVars = tc.vendorVars
+ }),
+ )
+ if tc.expectedError != "" {
+ fixtures = fixtures.ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(tc.expectedError))
+ }
+ result := fixtures.RunTestWithBp(t, tc.bp)
+
+ if tc.expectedError == "" {
+ m := result.ModuleForTests("foo", "")
+ p, _ := OtherModuleProvider[selectsTestProvider](result.testContext.OtherModuleProviderAdaptor(), m.Module(), selectsTestProviderKey)
+ if !reflect.DeepEqual(p, tc.provider) {
+ t.Errorf("Expected:\n %q\ngot:\n %q", tc.provider.String(), p.String())
+ }
+ }
+ })
+ }
+}
+
+type selectsTestProvider struct {
+ my_bool *bool
+ my_string *string
+ my_string_list *[]string
+}
+
+func (p *selectsTestProvider) String() string {
+ myBoolStr := "nil"
+ if p.my_bool != nil {
+ myBoolStr = fmt.Sprintf("%t", *p.my_bool)
+ }
+ myStringStr := "nil"
+ if p.my_string != nil {
+ myStringStr = *p.my_string
+ }
+ return fmt.Sprintf(`selectsTestProvider {
+ my_bool: %v,
+ my_string: %s,
+ my_string_list: %s,
+}`, myBoolStr, myStringStr, p.my_string_list)
+}
+
+var selectsTestProviderKey = blueprint.NewProvider[selectsTestProvider]()
+
+type selectsMockModuleProperties struct {
+ My_bool proptools.Configurable[bool]
+ My_string proptools.Configurable[string]
+ My_string_list proptools.Configurable[[]string]
+}
+
+type selectsMockModule struct {
+ ModuleBase
+ DefaultableModuleBase
+ properties selectsMockModuleProperties
+}
+
+func (p *selectsMockModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+ SetProvider[selectsTestProvider](ctx, selectsTestProviderKey, selectsTestProvider{
+ my_bool: p.properties.My_bool.Evaluate(ctx),
+ my_string: p.properties.My_string.Evaluate(ctx),
+ my_string_list: p.properties.My_string_list.Evaluate(ctx),
+ })
+}
+
+func newSelectsMockModule() Module {
+ m := &selectsMockModule{}
+ m.AddProperties(&m.properties)
+ InitAndroidArchModule(m, HostAndDeviceSupported, MultilibCommon)
+ InitDefaultableModule(m)
+ return m
+}