blob: 79bdeb82973420b55a06badf704b5e6c33d138b5 [file] [log] [blame]
// 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 (
"fmt"
"path/filepath"
"testing"
)
type soongConfigTestDefaultsModule struct {
ModuleBase
DefaultsModuleBase
}
func soongConfigTestDefaultsModuleFactory() Module {
m := &soongConfigTestDefaultsModule{}
m.AddProperties(&soongConfigTestModuleProperties{})
InitDefaultsModule(m)
return m
}
type soongConfigTestModule struct {
ModuleBase
DefaultableModuleBase
props soongConfigTestModuleProperties
outputPath ModuleOutPath
}
type soongConfigTestModuleProperties struct {
Cflags []string
}
func soongConfigTestModuleFactory() Module {
m := &soongConfigTestModule{}
m.AddProperties(&m.props)
InitAndroidModule(m)
InitDefaultableModule(m)
return m
}
func (t *soongConfigTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
t.outputPath = PathForModuleOut(ctx, "test")
}
var prepareForSoongConfigTestModule = FixtureRegisterWithContext(func(ctx RegistrationContext) {
ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
})
func TestSoongConfigModule(t *testing.T) {
configBp := `
soong_config_module_type {
name: "acme_test",
module_type: "test",
config_namespace: "acme",
variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
bool_variables: ["feature2", "unused_feature", "always_true"],
value_variables: ["size", "unused_size"],
properties: ["cflags", "srcs", "defaults"],
}
soong_config_string_variable {
name: "board",
values: ["soc_a", "soc_b", "soc_c", "soc_d"],
}
soong_config_string_variable {
name: "unused_string_var",
values: ["a", "b"],
}
soong_config_bool_variable {
name: "feature1",
}
soong_config_bool_variable {
name: "FEATURE3",
}
`
importBp := `
soong_config_module_type_import {
from: "SoongConfig.bp",
module_types: ["acme_test"],
}
`
bp := `
test_defaults {
name: "foo_defaults",
cflags: ["DEFAULT"],
}
acme_test {
name: "foo",
cflags: ["-DGENERIC"],
defaults: ["foo_defaults"],
soong_config_variables: {
board: {
soc_a: {
cflags: ["-DSOC_A"],
},
soc_b: {
cflags: ["-DSOC_B"],
},
soc_c: {},
conditions_default: {
cflags: ["-DSOC_CONDITIONS_DEFAULT"],
},
},
size: {
cflags: ["-DSIZE=%s"],
conditions_default: {
cflags: ["-DSIZE=CONDITIONS_DEFAULT"],
},
},
feature1: {
conditions_default: {
cflags: ["-DF1_CONDITIONS_DEFAULT"],
},
cflags: ["-DFEATURE1"],
},
feature2: {
cflags: ["-DFEATURE2"],
conditions_default: {
cflags: ["-DF2_CONDITIONS_DEFAULT"],
},
},
FEATURE3: {
cflags: ["-DFEATURE3"],
},
},
}
test_defaults {
name: "foo_defaults_a",
cflags: ["DEFAULT_A"],
}
test_defaults {
name: "foo_defaults_b",
cflags: ["DEFAULT_B"],
}
test_defaults {
name: "foo_defaults_always_true",
cflags: ["DEFAULT_ALWAYS_TRUE"],
}
acme_test {
name: "foo_with_defaults",
cflags: ["-DGENERIC"],
defaults: ["foo_defaults"],
soong_config_variables: {
board: {
soc_a: {
cflags: ["-DSOC_A"],
defaults: ["foo_defaults_a"],
},
soc_b: {
cflags: ["-DSOC_B"],
defaults: ["foo_defaults_b"],
},
soc_c: {},
},
size: {
cflags: ["-DSIZE=%s"],
},
feature1: {
cflags: ["-DFEATURE1"],
},
feature2: {
cflags: ["-DFEATURE2"],
},
FEATURE3: {
cflags: ["-DFEATURE3"],
},
always_true: {
defaults: ["foo_defaults_always_true"],
conditions_default: {
// verify that conditions_default is skipped if the
// soong config variable is true by specifying a
// non-existent module in conditions_default
defaults: ["//nonexistent:defaults"],
}
},
},
}
`
fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
variables.VendorVars = vars
})
}
run := func(t *testing.T, bp string, fs MockFS) {
testCases := []struct {
name string
preparer FixturePreparer
fooExpectedFlags []string
fooDefaultsExpectedFlags []string
}{
{
name: "withValues",
preparer: fixtureForVendorVars(map[string]map[string]string{
"acme": {
"board": "soc_a",
"size": "42",
"feature1": "true",
"feature2": "false",
// FEATURE3 unset
"unused_feature": "true", // unused
"unused_size": "1", // unused
"unused_string_var": "a", // unused
"always_true": "true",
},
}),
fooExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
"-DF2_CONDITIONS_DEFAULT",
"-DSIZE=42",
"-DSOC_A",
"-DFEATURE1",
},
fooDefaultsExpectedFlags: []string{
"DEFAULT_A",
"DEFAULT_ALWAYS_TRUE",
"DEFAULT",
"-DGENERIC",
"-DSIZE=42",
"-DSOC_A",
"-DFEATURE1",
},
},
{
name: "empty_prop_for_string_var",
preparer: fixtureForVendorVars(map[string]map[string]string{
"acme": {
"board": "soc_c",
"always_true": "true",
}}),
fooExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
"-DF2_CONDITIONS_DEFAULT",
"-DSIZE=CONDITIONS_DEFAULT",
"-DF1_CONDITIONS_DEFAULT",
},
fooDefaultsExpectedFlags: []string{
"DEFAULT_ALWAYS_TRUE",
"DEFAULT",
"-DGENERIC",
},
},
{
name: "unused_string_var",
preparer: fixtureForVendorVars(map[string]map[string]string{
"acme": {
"board": "soc_d",
"always_true": "true",
}}),
fooExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
"-DF2_CONDITIONS_DEFAULT",
"-DSIZE=CONDITIONS_DEFAULT",
"-DSOC_CONDITIONS_DEFAULT", // foo does not contain a prop "soc_d", so we use the default
"-DF1_CONDITIONS_DEFAULT",
},
fooDefaultsExpectedFlags: []string{
"DEFAULT_ALWAYS_TRUE",
"DEFAULT",
"-DGENERIC",
},
},
{
name: "conditions_default",
preparer: fixtureForVendorVars(map[string]map[string]string{
"acme": {
"always_true": "true",
}}),
fooExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
"-DF2_CONDITIONS_DEFAULT",
"-DSIZE=CONDITIONS_DEFAULT",
"-DSOC_CONDITIONS_DEFAULT",
"-DF1_CONDITIONS_DEFAULT",
},
fooDefaultsExpectedFlags: []string{
"DEFAULT_ALWAYS_TRUE",
"DEFAULT",
"-DGENERIC",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := GroupFixturePreparers(
tc.preparer,
PrepareForTestWithDefaults,
PrepareForTestWithSoongConfigModuleBuildComponents,
prepareForSoongConfigTestModule,
fs.AddToFixture(),
FixtureWithRootAndroidBp(bp),
).RunTest(t)
foo := result.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
AssertDeepEquals(t, "foo cflags", tc.fooExpectedFlags, foo.props.Cflags)
fooDefaults := result.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule)
AssertDeepEquals(t, "foo_with_defaults cflags", tc.fooDefaultsExpectedFlags, fooDefaults.props.Cflags)
})
}
}
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),
})
})
}
func TestNonExistentPropertyInSoongConfigModule(t *testing.T) {
bp := `
soong_config_module_type {
name: "acme_test",
module_type: "test",
config_namespace: "acme",
bool_variables: ["feature1"],
properties: ["made_up_property"],
}
acme_test {
name: "foo",
cflags: ["-DGENERIC"],
soong_config_variables: {
feature1: {
made_up_property: true,
},
},
}
`
fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
variables.VendorVars = vars
})
}
GroupFixturePreparers(
fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
PrepareForTestWithDefaults,
PrepareForTestWithSoongConfigModuleBuildComponents,
prepareForSoongConfigTestModule,
FixtureWithRootAndroidBp(bp),
).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
// TODO(b/171232169): improve the error message for non-existent properties
`unrecognized property "soong_config_variables`,
})).RunTest(t)
}
func TestDuplicateStringValueInSoongConfigStringVariable(t *testing.T) {
bp := `
soong_config_string_variable {
name: "board",
values: ["soc_a", "soc_b", "soc_c", "soc_a"],
}
soong_config_module_type {
name: "acme_test",
module_type: "test",
config_namespace: "acme",
variables: ["board"],
properties: ["cflags", "srcs", "defaults"],
}
`
fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
variables.VendorVars = vars
})
}
GroupFixturePreparers(
fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
PrepareForTestWithDefaults,
PrepareForTestWithSoongConfigModuleBuildComponents,
prepareForSoongConfigTestModule,
FixtureWithRootAndroidBp(bp),
).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
// TODO(b/171232169): improve the error message for non-existent properties
`Android.bp: soong_config_string_variable: values property error: duplicate value: "soc_a"`,
})).RunTest(t)
}
type soongConfigTestSingletonModule struct {
SingletonModuleBase
props soongConfigTestSingletonModuleProperties
}
type soongConfigTestSingletonModuleProperties struct {
Fragments []struct {
Apex string
Module string
}
}
func soongConfigTestSingletonModuleFactory() SingletonModule {
m := &soongConfigTestSingletonModule{}
m.AddProperties(&m.props)
InitAndroidModule(m)
return m
}
func (t *soongConfigTestSingletonModule) GenerateAndroidBuildActions(ModuleContext) {}
func (t *soongConfigTestSingletonModule) GenerateSingletonBuildActions(SingletonContext) {}
var prepareForSoongConfigTestSingletonModule = FixtureRegisterWithContext(func(ctx RegistrationContext) {
ctx.RegisterSingletonModuleType("test_singleton", soongConfigTestSingletonModuleFactory)
})
func TestSoongConfigModuleSingletonModule(t *testing.T) {
bp := `
soong_config_module_type {
name: "acme_test_singleton",
module_type: "test_singleton",
config_namespace: "acme",
bool_variables: ["coyote"],
properties: ["fragments"],
}
acme_test_singleton {
name: "wiley",
fragments: [
{
apex: "com.android.acme",
module: "road-runner",
},
],
soong_config_variables: {
coyote: {
fragments: [
{
apex: "com.android.acme",
module: "wiley",
},
],
},
},
}
`
for _, test := range []struct {
coyote bool
expectedFragments string
}{
{
coyote: false,
expectedFragments: "[{Apex:com.android.acme Module:road-runner}]",
},
{
coyote: true,
expectedFragments: "[{Apex:com.android.acme Module:road-runner} {Apex:com.android.acme Module:wiley}]",
},
} {
t.Run(fmt.Sprintf("coyote:%t", test.coyote), func(t *testing.T) {
result := GroupFixturePreparers(
PrepareForTestWithSoongConfigModuleBuildComponents,
prepareForSoongConfigTestSingletonModule,
FixtureWithRootAndroidBp(bp),
FixtureModifyProductVariables(func(variables FixtureProductVariables) {
variables.VendorVars = map[string]map[string]string{
"acme": {
"coyote": fmt.Sprintf("%t", test.coyote),
},
}
}),
).RunTest(t)
// Make sure that the singleton was created.
result.SingletonForTests("test_singleton")
m := result.ModuleForTests("wiley", "").module.(*soongConfigTestSingletonModule)
AssertStringEquals(t, "fragments", test.expectedFragments, fmt.Sprintf("%+v", m.props.Fragments))
})
}
}
func TestSoongConfigModuleTrace(t *testing.T) {
bp := `
soong_config_module_type {
name: "acme_test",
module_type: "test",
config_namespace: "acme",
variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
bool_variables: ["feature2", "unused_feature", "always_true"],
value_variables: ["size", "unused_size"],
properties: ["cflags", "srcs", "defaults"],
}
soong_config_module_type {
name: "acme_test_defaults",
module_type: "test_defaults",
config_namespace: "acme",
variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
bool_variables: ["feature2", "unused_feature", "always_true"],
value_variables: ["size", "unused_size"],
properties: ["cflags", "srcs", "defaults"],
}
soong_config_string_variable {
name: "board",
values: ["soc_a", "soc_b", "soc_c"],
}
soong_config_string_variable {
name: "unused_string_var",
values: ["a", "b"],
}
soong_config_bool_variable {
name: "feature1",
}
soong_config_bool_variable {
name: "FEATURE3",
}
test_defaults {
name: "test_defaults",
cflags: ["DEFAULT"],
}
test {
name: "normal",
defaults: ["test_defaults"],
}
acme_test {
name: "board_1",
defaults: ["test_defaults"],
soong_config_variables: {
board: {
soc_a: {
cflags: ["-DSOC_A"],
},
},
},
}
acme_test {
name: "board_2",
defaults: ["test_defaults"],
soong_config_variables: {
board: {
soc_a: {
cflags: ["-DSOC_A"],
},
},
},
}
acme_test {
name: "size",
defaults: ["test_defaults"],
soong_config_variables: {
size: {
cflags: ["-DSIZE=%s"],
},
},
}
acme_test {
name: "board_and_size",
defaults: ["test_defaults"],
soong_config_variables: {
board: {
soc_a: {
cflags: ["-DSOC_A"],
},
},
size: {
cflags: ["-DSIZE=%s"],
},
},
}
acme_test_defaults {
name: "board_defaults",
soong_config_variables: {
board: {
soc_a: {
cflags: ["-DSOC_A"],
},
},
},
}
acme_test_defaults {
name: "size_defaults",
soong_config_variables: {
size: {
cflags: ["-DSIZE=%s"],
},
},
}
test {
name: "board_and_size_with_defaults",
defaults: ["board_defaults", "size_defaults"],
}
`
fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
variables.VendorVars = vars
})
}
preparer := fixtureForVendorVars(map[string]map[string]string{
"acme": {
"board": "soc_a",
"size": "42",
"feature1": "true",
"feature2": "false",
// FEATURE3 unset
"unused_feature": "true", // unused
"unused_size": "1", // unused
"unused_string_var": "a", // unused
"always_true": "true",
},
})
t.Run("soong config trace hash", func(t *testing.T) {
result := GroupFixturePreparers(
preparer,
PrepareForTestWithDefaults,
PrepareForTestWithSoongConfigModuleBuildComponents,
prepareForSoongConfigTestModule,
FixtureRegisterWithContext(func(ctx RegistrationContext) {
ctx.FinalDepsMutators(registerSoongConfigTraceMutator)
}),
FixtureWithRootAndroidBp(bp),
).RunTest(t)
// Hashes of modules not using soong config should be empty
normal := result.ModuleForTests("normal", "").Module().(*soongConfigTestModule)
AssertDeepEquals(t, "normal hash", normal.base().commonProperties.SoongConfigTraceHash, "")
AssertDeepEquals(t, "normal hash out", normal.outputPath.RelativeToTop().String(), "out/soong/.intermediates/normal/test")
board1 := result.ModuleForTests("board_1", "").Module().(*soongConfigTestModule)
board2 := result.ModuleForTests("board_2", "").Module().(*soongConfigTestModule)
size := result.ModuleForTests("size", "").Module().(*soongConfigTestModule)
// Trace mutator sets soong config trace hash correctly
board1Hash := board1.base().commonProperties.SoongConfigTrace.hash()
board1Output := board1.outputPath.RelativeToTop().String()
AssertDeepEquals(t, "board hash calc", board1Hash, board1.base().commonProperties.SoongConfigTraceHash)
AssertDeepEquals(t, "board hash path", board1Output, filepath.Join("out/soong/.intermediates/board_1", board1Hash, "test"))
sizeHash := size.base().commonProperties.SoongConfigTrace.hash()
sizeOutput := size.outputPath.RelativeToTop().String()
AssertDeepEquals(t, "size hash calc", sizeHash, size.base().commonProperties.SoongConfigTraceHash)
AssertDeepEquals(t, "size hash path", sizeOutput, filepath.Join("out/soong/.intermediates/size", sizeHash, "test"))
// Trace should be identical for modules using the same set of variables
AssertDeepEquals(t, "board trace", board1.base().commonProperties.SoongConfigTrace, board2.base().commonProperties.SoongConfigTrace)
AssertDeepEquals(t, "board hash", board1.base().commonProperties.SoongConfigTraceHash, board2.base().commonProperties.SoongConfigTraceHash)
// Trace hash should be different for different sets of soong variables
AssertBoolEquals(t, "board hash not equal to size hash", board1.base().commonProperties.SoongConfigTraceHash == size.commonProperties.SoongConfigTraceHash, false)
boardSize := result.ModuleForTests("board_and_size", "").Module().(*soongConfigTestModule)
boardSizeDefaults := result.ModuleForTests("board_and_size_with_defaults", "").Module()
// Trace should propagate
AssertDeepEquals(t, "board_size hash calc", boardSize.base().commonProperties.SoongConfigTrace.hash(), boardSize.base().commonProperties.SoongConfigTraceHash)
AssertDeepEquals(t, "board_size trace", boardSize.base().commonProperties.SoongConfigTrace, boardSizeDefaults.base().commonProperties.SoongConfigTrace)
AssertDeepEquals(t, "board_size hash", boardSize.base().commonProperties.SoongConfigTraceHash, boardSizeDefaults.base().commonProperties.SoongConfigTraceHash)
})
}