• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
18	"fmt"
19	"path/filepath"
20	"testing"
21)
22
23type soongConfigTestDefaultsModule struct {
24	ModuleBase
25	DefaultsModuleBase
26}
27
28func soongConfigTestDefaultsModuleFactory() Module {
29	m := &soongConfigTestDefaultsModule{}
30	m.AddProperties(&soongConfigTestModuleProperties{})
31	InitDefaultsModule(m)
32	return m
33}
34
35type soongConfigTestModule struct {
36	ModuleBase
37	DefaultableModuleBase
38	props      soongConfigTestModuleProperties
39	outputPath ModuleOutPath
40}
41
42type soongConfigTestModuleProperties struct {
43	Cflags []string
44}
45
46func soongConfigTestModuleFactory() Module {
47	m := &soongConfigTestModule{}
48	m.AddProperties(&m.props)
49	InitAndroidModule(m)
50	InitDefaultableModule(m)
51	return m
52}
53
54func (t *soongConfigTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
55	t.outputPath = PathForModuleOut(ctx, "test")
56}
57
58var prepareForSoongConfigTestModule = FixtureRegisterWithContext(func(ctx RegistrationContext) {
59	ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
60	ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
61})
62
63func TestSoongConfigModule(t *testing.T) {
64	configBp := `
65		soong_config_module_type {
66			name: "acme_test",
67			module_type: "test",
68			config_namespace: "acme",
69			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
70			bool_variables: ["feature2", "unused_feature", "always_true"],
71			value_variables: ["size", "unused_size"],
72			properties: ["cflags", "srcs", "defaults"],
73		}
74
75		soong_config_string_variable {
76			name: "board",
77			values: ["soc_a", "soc_b", "soc_c", "soc_d"],
78		}
79
80		soong_config_string_variable {
81			name: "unused_string_var",
82			values: ["a", "b"],
83		}
84
85		soong_config_bool_variable {
86			name: "feature1",
87		}
88
89		soong_config_bool_variable {
90			name: "FEATURE3",
91		}
92	`
93
94	importBp := `
95		soong_config_module_type_import {
96			from: "SoongConfig.bp",
97			module_types: ["acme_test"],
98		}
99	`
100
101	bp := `
102		test_defaults {
103			name: "foo_defaults",
104			cflags: ["DEFAULT"],
105		}
106
107		acme_test {
108			name: "foo",
109			cflags: ["-DGENERIC"],
110			defaults: ["foo_defaults"],
111			soong_config_variables: {
112				board: {
113					soc_a: {
114						cflags: ["-DSOC_A"],
115					},
116					soc_b: {
117						cflags: ["-DSOC_B"],
118					},
119					soc_c: {},
120					conditions_default: {
121						cflags: ["-DSOC_CONDITIONS_DEFAULT"],
122					},
123				},
124				size: {
125					cflags: ["-DSIZE=%s"],
126					conditions_default: {
127						cflags: ["-DSIZE=CONDITIONS_DEFAULT"],
128					},
129				},
130				feature1: {
131					  conditions_default: {
132						  cflags: ["-DF1_CONDITIONS_DEFAULT"],
133					  },
134					cflags: ["-DFEATURE1"],
135				},
136				feature2: {
137					cflags: ["-DFEATURE2"],
138					 conditions_default: {
139						 cflags: ["-DF2_CONDITIONS_DEFAULT"],
140					 },
141				},
142				FEATURE3: {
143					cflags: ["-DFEATURE3"],
144				},
145			},
146		}
147
148		test_defaults {
149			name: "foo_defaults_a",
150			cflags: ["DEFAULT_A"],
151		}
152
153		test_defaults {
154			name: "foo_defaults_b",
155			cflags: ["DEFAULT_B"],
156		}
157
158		test_defaults {
159			name: "foo_defaults_always_true",
160			cflags: ["DEFAULT_ALWAYS_TRUE"],
161		}
162
163		acme_test {
164			name: "foo_with_defaults",
165			cflags: ["-DGENERIC"],
166			defaults: ["foo_defaults"],
167			soong_config_variables: {
168				board: {
169					soc_a: {
170						cflags: ["-DSOC_A"],
171						defaults: ["foo_defaults_a"],
172					},
173					soc_b: {
174						cflags: ["-DSOC_B"],
175						defaults: ["foo_defaults_b"],
176					},
177					soc_c: {},
178				},
179				size: {
180					cflags: ["-DSIZE=%s"],
181				},
182				feature1: {
183					cflags: ["-DFEATURE1"],
184				},
185				feature2: {
186					cflags: ["-DFEATURE2"],
187				},
188				FEATURE3: {
189					cflags: ["-DFEATURE3"],
190				},
191				always_true: {
192					defaults: ["foo_defaults_always_true"],
193					conditions_default: {
194						// verify that conditions_default is skipped if the
195						// soong config variable is true by specifying a
196						// non-existent module in conditions_default
197						defaults: ["//nonexistent:defaults"],
198					}
199				},
200			},
201		}
202    `
203
204	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
205		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
206			variables.VendorVars = vars
207		})
208	}
209
210	run := func(t *testing.T, bp string, fs MockFS) {
211		testCases := []struct {
212			name                     string
213			preparer                 FixturePreparer
214			fooExpectedFlags         []string
215			fooDefaultsExpectedFlags []string
216		}{
217			{
218				name: "withValues",
219				preparer: fixtureForVendorVars(map[string]map[string]string{
220					"acme": {
221						"board":    "soc_a",
222						"size":     "42",
223						"feature1": "true",
224						"feature2": "false",
225						// FEATURE3 unset
226						"unused_feature":    "true", // unused
227						"unused_size":       "1",    // unused
228						"unused_string_var": "a",    // unused
229						"always_true":       "true",
230					},
231				}),
232				fooExpectedFlags: []string{
233					"DEFAULT",
234					"-DGENERIC",
235					"-DF2_CONDITIONS_DEFAULT",
236					"-DSIZE=42",
237					"-DSOC_A",
238					"-DFEATURE1",
239				},
240				fooDefaultsExpectedFlags: []string{
241					"DEFAULT_A",
242					"DEFAULT_ALWAYS_TRUE",
243					"DEFAULT",
244					"-DGENERIC",
245					"-DSIZE=42",
246					"-DSOC_A",
247					"-DFEATURE1",
248				},
249			},
250			{
251				name: "empty_prop_for_string_var",
252				preparer: fixtureForVendorVars(map[string]map[string]string{
253					"acme": {
254						"board":       "soc_c",
255						"always_true": "true",
256					}}),
257				fooExpectedFlags: []string{
258					"DEFAULT",
259					"-DGENERIC",
260					"-DF2_CONDITIONS_DEFAULT",
261					"-DSIZE=CONDITIONS_DEFAULT",
262					"-DF1_CONDITIONS_DEFAULT",
263				},
264				fooDefaultsExpectedFlags: []string{
265					"DEFAULT_ALWAYS_TRUE",
266					"DEFAULT",
267					"-DGENERIC",
268				},
269			},
270			{
271				name: "unused_string_var",
272				preparer: fixtureForVendorVars(map[string]map[string]string{
273					"acme": {
274						"board":       "soc_d",
275						"always_true": "true",
276					}}),
277				fooExpectedFlags: []string{
278					"DEFAULT",
279					"-DGENERIC",
280					"-DF2_CONDITIONS_DEFAULT",
281					"-DSIZE=CONDITIONS_DEFAULT",
282					"-DSOC_CONDITIONS_DEFAULT", // foo does not contain a prop "soc_d", so we use the default
283					"-DF1_CONDITIONS_DEFAULT",
284				},
285				fooDefaultsExpectedFlags: []string{
286					"DEFAULT_ALWAYS_TRUE",
287					"DEFAULT",
288					"-DGENERIC",
289				},
290			},
291
292			{
293				name: "conditions_default",
294				preparer: fixtureForVendorVars(map[string]map[string]string{
295					"acme": {
296						"always_true": "true",
297					}}),
298				fooExpectedFlags: []string{
299					"DEFAULT",
300					"-DGENERIC",
301					"-DF2_CONDITIONS_DEFAULT",
302					"-DSIZE=CONDITIONS_DEFAULT",
303					"-DSOC_CONDITIONS_DEFAULT",
304					"-DF1_CONDITIONS_DEFAULT",
305				},
306				fooDefaultsExpectedFlags: []string{
307					"DEFAULT_ALWAYS_TRUE",
308					"DEFAULT",
309					"-DGENERIC",
310				},
311			},
312		}
313
314		for _, tc := range testCases {
315			t.Run(tc.name, func(t *testing.T) {
316				result := GroupFixturePreparers(
317					tc.preparer,
318					PrepareForTestWithDefaults,
319					PrepareForTestWithSoongConfigModuleBuildComponents,
320					prepareForSoongConfigTestModule,
321					fs.AddToFixture(),
322					FixtureWithRootAndroidBp(bp),
323				).RunTest(t)
324
325				foo := result.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
326				AssertDeepEquals(t, "foo cflags", tc.fooExpectedFlags, foo.props.Cflags)
327
328				fooDefaults := result.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule)
329				AssertDeepEquals(t, "foo_with_defaults cflags", tc.fooDefaultsExpectedFlags, fooDefaults.props.Cflags)
330			})
331		}
332	}
333
334	t.Run("single file", func(t *testing.T) {
335		run(t, configBp+bp, nil)
336	})
337
338	t.Run("import", func(t *testing.T) {
339		run(t, importBp+bp, map[string][]byte{
340			"SoongConfig.bp": []byte(configBp),
341		})
342	})
343}
344
345func TestNonExistentPropertyInSoongConfigModule(t *testing.T) {
346	bp := `
347		soong_config_module_type {
348			name: "acme_test",
349			module_type: "test",
350			config_namespace: "acme",
351			bool_variables: ["feature1"],
352			properties: ["made_up_property"],
353		}
354
355		acme_test {
356			name: "foo",
357			cflags: ["-DGENERIC"],
358			soong_config_variables: {
359				feature1: {
360					made_up_property: true,
361				},
362			},
363		}
364    `
365
366	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
367		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
368			variables.VendorVars = vars
369		})
370	}
371
372	GroupFixturePreparers(
373		fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
374		PrepareForTestWithDefaults,
375		PrepareForTestWithSoongConfigModuleBuildComponents,
376		prepareForSoongConfigTestModule,
377		FixtureWithRootAndroidBp(bp),
378	).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
379		`unrecognized property "soong_config_variables.feature1.made_up_property`,
380	})).RunTest(t)
381}
382
383func TestDuplicateStringValueInSoongConfigStringVariable(t *testing.T) {
384	bp := `
385		soong_config_string_variable {
386			name: "board",
387			values: ["soc_a", "soc_b", "soc_c", "soc_a"],
388		}
389
390		soong_config_module_type {
391			name: "acme_test",
392			module_type: "test",
393			config_namespace: "acme",
394			variables: ["board"],
395			properties: ["cflags", "srcs", "defaults"],
396		}
397    `
398
399	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
400		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
401			variables.VendorVars = vars
402		})
403	}
404
405	GroupFixturePreparers(
406		fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
407		PrepareForTestWithDefaults,
408		PrepareForTestWithSoongConfigModuleBuildComponents,
409		prepareForSoongConfigTestModule,
410		FixtureWithRootAndroidBp(bp),
411	).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
412		// TODO(b/171232169): improve the error message for non-existent properties
413		`Android.bp: soong_config_string_variable: values property error: duplicate value: "soc_a"`,
414	})).RunTest(t)
415}
416
417type soongConfigTestSingletonModule struct {
418	SingletonModuleBase
419	props soongConfigTestSingletonModuleProperties
420}
421
422type soongConfigTestSingletonModuleProperties struct {
423	Fragments []struct {
424		Apex   string
425		Module string
426	}
427}
428
429func soongConfigTestSingletonModuleFactory() SingletonModule {
430	m := &soongConfigTestSingletonModule{}
431	m.AddProperties(&m.props)
432	InitAndroidModule(m)
433	return m
434}
435
436func (t *soongConfigTestSingletonModule) GenerateAndroidBuildActions(ModuleContext) {}
437
438func (t *soongConfigTestSingletonModule) GenerateSingletonBuildActions(SingletonContext) {}
439
440var prepareForSoongConfigTestSingletonModule = FixtureRegisterWithContext(func(ctx RegistrationContext) {
441	ctx.RegisterSingletonModuleType("test_singleton", soongConfigTestSingletonModuleFactory)
442})
443
444func TestSoongConfigModuleSingletonModule(t *testing.T) {
445	bp := `
446		soong_config_module_type {
447			name: "acme_test_singleton",
448			module_type: "test_singleton",
449			config_namespace: "acme",
450			bool_variables: ["coyote"],
451			properties: ["fragments"],
452		}
453
454		acme_test_singleton {
455			name: "wiley",
456			fragments: [
457				{
458					apex: "com.android.acme",
459					module: "road-runner",
460				},
461			],
462			soong_config_variables: {
463				coyote: {
464					fragments: [
465						{
466							apex: "com.android.acme",
467							module: "wiley",
468						},
469					],
470				},
471			},
472		}
473	`
474
475	for _, test := range []struct {
476		coyote            bool
477		expectedFragments string
478	}{
479		{
480			coyote:            false,
481			expectedFragments: "[{Apex:com.android.acme Module:road-runner}]",
482		},
483		{
484			coyote:            true,
485			expectedFragments: "[{Apex:com.android.acme Module:road-runner} {Apex:com.android.acme Module:wiley}]",
486		},
487	} {
488		t.Run(fmt.Sprintf("coyote:%t", test.coyote), func(t *testing.T) {
489			result := GroupFixturePreparers(
490				PrepareForTestWithSoongConfigModuleBuildComponents,
491				prepareForSoongConfigTestSingletonModule,
492				FixtureWithRootAndroidBp(bp),
493				FixtureModifyProductVariables(func(variables FixtureProductVariables) {
494					variables.VendorVars = map[string]map[string]string{
495						"acme": {
496							"coyote": fmt.Sprintf("%t", test.coyote),
497						},
498					}
499				}),
500			).RunTest(t)
501
502			// Make sure that the singleton was created.
503			result.SingletonForTests("test_singleton")
504			m := result.ModuleForTests("wiley", "").module.(*soongConfigTestSingletonModule)
505			AssertStringEquals(t, "fragments", test.expectedFragments, fmt.Sprintf("%+v", m.props.Fragments))
506		})
507	}
508}
509
510func TestSoongConfigModuleTrace(t *testing.T) {
511	bp := `
512		soong_config_module_type {
513			name: "acme_test",
514			module_type: "test",
515			config_namespace: "acme",
516			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
517			bool_variables: ["feature2", "unused_feature", "always_true"],
518			value_variables: ["size", "unused_size"],
519			properties: ["cflags", "srcs", "defaults"],
520		}
521
522		soong_config_module_type {
523			name: "acme_test_defaults",
524			module_type: "test_defaults",
525			config_namespace: "acme",
526			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
527			bool_variables: ["feature2", "unused_feature", "always_true"],
528			value_variables: ["size", "unused_size"],
529			properties: ["cflags", "srcs", "defaults"],
530		}
531
532		soong_config_string_variable {
533			name: "board",
534			values: ["soc_a", "soc_b", "soc_c"],
535		}
536
537		soong_config_string_variable {
538			name: "unused_string_var",
539			values: ["a", "b"],
540		}
541
542		soong_config_bool_variable {
543			name: "feature1",
544		}
545
546		soong_config_bool_variable {
547			name: "FEATURE3",
548		}
549
550		test_defaults {
551			name: "test_defaults",
552			cflags: ["DEFAULT"],
553		}
554
555		test {
556			name: "normal",
557			defaults: ["test_defaults"],
558		}
559
560		acme_test {
561			name: "board_1",
562			defaults: ["test_defaults"],
563			soong_config_variables: {
564				board: {
565					soc_a: {
566						cflags: ["-DSOC_A"],
567					},
568				},
569			},
570		}
571
572		acme_test {
573			name: "board_2",
574			defaults: ["test_defaults"],
575			soong_config_variables: {
576				board: {
577					soc_a: {
578						cflags: ["-DSOC_A"],
579					},
580				},
581			},
582		}
583
584		acme_test {
585			name: "size",
586			defaults: ["test_defaults"],
587			soong_config_variables: {
588				size: {
589					cflags: ["-DSIZE=%s"],
590				},
591			},
592		}
593
594		acme_test {
595			name: "board_and_size",
596			defaults: ["test_defaults"],
597			soong_config_variables: {
598				board: {
599					soc_a: {
600						cflags: ["-DSOC_A"],
601					},
602				},
603				size: {
604					cflags: ["-DSIZE=%s"],
605				},
606			},
607		}
608
609		acme_test_defaults {
610			name: "board_defaults",
611			soong_config_variables: {
612				board: {
613					soc_a: {
614						cflags: ["-DSOC_A"],
615					},
616				},
617			},
618		}
619
620		acme_test_defaults {
621			name: "size_defaults",
622			soong_config_variables: {
623				size: {
624					cflags: ["-DSIZE=%s"],
625				},
626			},
627		}
628
629		test {
630			name: "board_and_size_with_defaults",
631			defaults: ["board_defaults", "size_defaults"],
632		}
633    `
634
635	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
636		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
637			variables.VendorVars = vars
638		})
639	}
640
641	preparer := fixtureForVendorVars(map[string]map[string]string{
642		"acme": {
643			"board":    "soc_a",
644			"size":     "42",
645			"feature1": "true",
646			"feature2": "false",
647			// FEATURE3 unset
648			"unused_feature":    "true", // unused
649			"unused_size":       "1",    // unused
650			"unused_string_var": "a",    // unused
651			"always_true":       "true",
652		},
653	})
654
655	t.Run("soong config trace hash", func(t *testing.T) {
656		result := GroupFixturePreparers(
657			preparer,
658			PrepareForTestWithDefaults,
659			PrepareForTestWithSoongConfigModuleBuildComponents,
660			prepareForSoongConfigTestModule,
661			FixtureRegisterWithContext(func(ctx RegistrationContext) {
662				ctx.FinalDepsMutators(registerSoongConfigTraceMutator)
663			}),
664			FixtureWithRootAndroidBp(bp),
665		).RunTest(t)
666
667		// Hashes of modules not using soong config should be empty
668		normal := result.ModuleForTests("normal", "").Module().(*soongConfigTestModule)
669		AssertDeepEquals(t, "normal hash", normal.base().commonProperties.SoongConfigTraceHash, "")
670		AssertDeepEquals(t, "normal hash out", normal.outputPath.RelativeToTop().String(), "out/soong/.intermediates/normal/test")
671
672		board1 := result.ModuleForTests("board_1", "").Module().(*soongConfigTestModule)
673		board2 := result.ModuleForTests("board_2", "").Module().(*soongConfigTestModule)
674		size := result.ModuleForTests("size", "").Module().(*soongConfigTestModule)
675
676		// Trace mutator sets soong config trace hash correctly
677		board1Hash := board1.base().commonProperties.SoongConfigTrace.hash()
678		board1Output := board1.outputPath.RelativeToTop().String()
679		AssertDeepEquals(t, "board hash calc", board1Hash, board1.base().commonProperties.SoongConfigTraceHash)
680		AssertDeepEquals(t, "board hash path", board1Output, filepath.Join("out/soong/.intermediates/board_1", board1Hash, "test"))
681
682		sizeHash := size.base().commonProperties.SoongConfigTrace.hash()
683		sizeOutput := size.outputPath.RelativeToTop().String()
684		AssertDeepEquals(t, "size hash calc", sizeHash, size.base().commonProperties.SoongConfigTraceHash)
685		AssertDeepEquals(t, "size hash path", sizeOutput, filepath.Join("out/soong/.intermediates/size", sizeHash, "test"))
686
687		// Trace should be identical for modules using the same set of variables
688		AssertDeepEquals(t, "board trace", board1.base().commonProperties.SoongConfigTrace, board2.base().commonProperties.SoongConfigTrace)
689		AssertDeepEquals(t, "board hash", board1.base().commonProperties.SoongConfigTraceHash, board2.base().commonProperties.SoongConfigTraceHash)
690
691		// Trace hash should be different for different sets of soong variables
692		AssertBoolEquals(t, "board hash not equal to size hash", board1.base().commonProperties.SoongConfigTraceHash == size.commonProperties.SoongConfigTraceHash, false)
693
694		boardSize := result.ModuleForTests("board_and_size", "").Module().(*soongConfigTestModule)
695		boardSizeDefaults := result.ModuleForTests("board_and_size_with_defaults", "").Module()
696
697		// Trace should propagate
698		AssertDeepEquals(t, "board_size hash calc", boardSize.base().commonProperties.SoongConfigTrace.hash(), boardSize.base().commonProperties.SoongConfigTraceHash)
699		AssertDeepEquals(t, "board_size trace", boardSize.base().commonProperties.SoongConfigTrace, boardSizeDefaults.base().commonProperties.SoongConfigTrace)
700		AssertDeepEquals(t, "board_size hash", boardSize.base().commonProperties.SoongConfigTraceHash, boardSizeDefaults.base().commonProperties.SoongConfigTraceHash)
701	})
702}
703