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

Commit 140180dd authored by Cole Faust's avatar Cole Faust Committed by Gerrit Code Review
Browse files

Merge "Select statements" into main

parents 08bef285 5a231bd8
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
}