Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5a231bd8 authored by Cole Faust's avatar Cole Faust
Browse files

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
parent eefca737
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -135,6 +135,7 @@ bootstrap_go_package {
        "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",
+34 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import (
	"strings"

	"github.com/google/blueprint"
	"github.com/google/blueprint/parser"
	"github.com/google/blueprint/proptools"
)

@@ -212,6 +213,10 @@ type ModuleContext interface {
	// 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) HostRequiredModuleNames() []string {
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")
	}
}
+282 −0
Original line number Diff line number Diff line
// 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
}