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