1// Copyright 2024 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 "reflect" 20 "testing" 21 22 "github.com/google/blueprint" 23 "github.com/google/blueprint/proptools" 24) 25 26func TestSelects(t *testing.T) { 27 testCases := []struct { 28 name string 29 bp string 30 fs MockFS 31 provider selectsTestProvider 32 providers map[string]selectsTestProvider 33 vendorVars map[string]map[string]string 34 vendorVarTypes map[string]map[string]string 35 expectedError string 36 }{ 37 { 38 name: "basic string list", 39 bp: ` 40 my_module_type { 41 name: "foo", 42 my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { 43 "a": ["a.cpp"], 44 "b": ["b.cpp"], 45 default: ["c.cpp"], 46 }), 47 } 48 `, 49 provider: selectsTestProvider{ 50 my_string_list: &[]string{"c.cpp"}, 51 }, 52 }, 53 { 54 name: "basic string", 55 bp: ` 56 my_module_type { 57 name: "foo", 58 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 59 "a": "a.cpp", 60 "b": "b.cpp", 61 default: "c.cpp", 62 }), 63 } 64 `, 65 provider: selectsTestProvider{ 66 my_string: proptools.StringPtr("c.cpp"), 67 }, 68 }, 69 { 70 name: "basic bool", 71 bp: ` 72 my_module_type { 73 name: "foo", 74 my_bool: select(soong_config_variable("my_namespace", "my_variable"), { 75 "a": true, 76 "b": false, 77 default: true, 78 }), 79 } 80 `, 81 provider: selectsTestProvider{ 82 my_bool: proptools.BoolPtr(true), 83 }, 84 }, 85 { 86 name: "basic paths", 87 bp: ` 88 my_module_type { 89 name: "foo", 90 my_paths: select(soong_config_variable("my_namespace", "my_variable"), { 91 "a": ["foo.txt"], 92 "b": ["bar.txt"], 93 default: ["baz.txt"], 94 }), 95 } 96 `, 97 provider: selectsTestProvider{ 98 my_paths: &[]string{"baz.txt"}, 99 }, 100 }, 101 { 102 name: "Expression in select", 103 bp: ` 104 my_module_type { 105 name: "foo", 106 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 107 "a": "foo" + "bar", 108 default: "baz", 109 }), 110 } 111 `, 112 provider: selectsTestProvider{ 113 my_string: proptools.StringPtr("foobar"), 114 }, 115 vendorVars: map[string]map[string]string{ 116 "my_namespace": { 117 "my_variable": "a", 118 }, 119 }, 120 }, 121 { 122 name: "paths with module references", 123 bp: ` 124 my_module_type { 125 name: "foo", 126 my_paths: select(soong_config_variable("my_namespace", "my_variable"), { 127 "a": [":a"], 128 "b": [":b"], 129 default: [":c"], 130 }), 131 } 132 `, 133 expectedError: `"foo" depends on undefined module "c"`, 134 }, 135 { 136 name: "Select type doesn't match property type", 137 bp: ` 138 my_module_type { 139 name: "foo", 140 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 141 "a": false, 142 "b": true, 143 default: true, 144 }), 145 } 146 `, 147 expectedError: `can't assign bool value to string property`, 148 }, 149 { 150 name: "String list non-default", 151 bp: ` 152 my_module_type { 153 name: "foo", 154 my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { 155 "a": ["a.cpp"], 156 "b": ["b.cpp"], 157 default: ["c.cpp"], 158 }), 159 } 160 `, 161 provider: selectsTestProvider{ 162 my_string_list: &[]string{"a.cpp"}, 163 }, 164 vendorVars: map[string]map[string]string{ 165 "my_namespace": { 166 "my_variable": "a", 167 }, 168 }, 169 }, 170 { 171 name: "String list append", 172 bp: ` 173 my_module_type { 174 name: "foo", 175 my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { 176 "a": ["a.cpp"], 177 "b": ["b.cpp"], 178 default: ["c.cpp"], 179 }) + select(soong_config_variable("my_namespace", "my_variable_2"), { 180 "a2": ["a2.cpp"], 181 "b2": ["b2.cpp"], 182 default: ["c2.cpp"], 183 }), 184 } 185 `, 186 provider: selectsTestProvider{ 187 my_string_list: &[]string{"a.cpp", "c2.cpp"}, 188 }, 189 vendorVars: map[string]map[string]string{ 190 "my_namespace": { 191 "my_variable": "a", 192 }, 193 }, 194 }, 195 { 196 name: "String list prepend literal", 197 bp: ` 198 my_module_type { 199 name: "foo", 200 my_string_list: ["literal.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), { 201 "a2": ["a2.cpp"], 202 "b2": ["b2.cpp"], 203 default: ["c2.cpp"], 204 }), 205 } 206 `, 207 provider: selectsTestProvider{ 208 my_string_list: &[]string{"literal.cpp", "c2.cpp"}, 209 }, 210 }, 211 { 212 name: "String list append literal", 213 bp: ` 214 my_module_type { 215 name: "foo", 216 my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { 217 "a2": ["a2.cpp"], 218 "b2": ["b2.cpp"], 219 default: ["c2.cpp"], 220 }) + ["literal.cpp"], 221 } 222 `, 223 provider: selectsTestProvider{ 224 my_string_list: &[]string{"c2.cpp", "literal.cpp"}, 225 }, 226 }, 227 { 228 name: "true + false = true", 229 bp: ` 230 my_module_type { 231 name: "foo", 232 my_bool: select(soong_config_variable("my_namespace", "my_variable"), { 233 "a": true, 234 "b": false, 235 default: true, 236 }) + false, 237 } 238 `, 239 provider: selectsTestProvider{ 240 my_bool: proptools.BoolPtr(true), 241 }, 242 }, 243 { 244 name: "false + false = false", 245 bp: ` 246 my_module_type { 247 name: "foo", 248 my_bool: select(soong_config_variable("my_namespace", "my_variable"), { 249 "a": true, 250 "b": false, 251 default: true, 252 }) + false, 253 } 254 `, 255 vendorVars: map[string]map[string]string{ 256 "my_namespace": { 257 "my_variable": "b", 258 }, 259 }, 260 provider: selectsTestProvider{ 261 my_bool: proptools.BoolPtr(false), 262 }, 263 }, 264 { 265 name: "Append string", 266 bp: ` 267 my_module_type { 268 name: "foo", 269 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 270 "a": "a", 271 "b": "b", 272 default: "c", 273 }) + ".cpp", 274 } 275 `, 276 provider: selectsTestProvider{ 277 my_string: proptools.StringPtr("c.cpp"), 278 }, 279 }, 280 { 281 name: "Select on arch", 282 bp: ` 283 my_module_type { 284 name: "foo", 285 my_string: select(arch(), { 286 "x86": "my_x86", 287 "x86_64": "my_x86_64", 288 "arm": "my_arm", 289 "arm64": "my_arm64", 290 default: "my_default", 291 }), 292 } 293 `, 294 provider: selectsTestProvider{ 295 my_string: proptools.StringPtr("my_arm64"), 296 }, 297 }, 298 { 299 name: "Select on os", 300 bp: ` 301 my_module_type { 302 name: "foo", 303 my_string: select(os(), { 304 "android": "my_android", 305 "linux": "my_linux", 306 default: "my_default", 307 }), 308 } 309 `, 310 provider: selectsTestProvider{ 311 my_string: proptools.StringPtr("my_android"), 312 }, 313 }, 314 { 315 name: "Unset value", 316 bp: ` 317 my_module_type { 318 name: "foo", 319 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 320 "a": unset, 321 "b": "b", 322 default: "c", 323 }) 324 } 325 `, 326 vendorVars: map[string]map[string]string{ 327 "my_namespace": { 328 "my_variable": "a", 329 }, 330 }, 331 provider: selectsTestProvider{}, 332 }, 333 { 334 name: "Unset value on different branch", 335 bp: ` 336 my_module_type { 337 name: "foo", 338 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 339 "a": unset, 340 "b": "b", 341 default: "c", 342 }) 343 } 344 `, 345 provider: selectsTestProvider{ 346 my_string: proptools.StringPtr("c"), 347 }, 348 }, 349 { 350 name: "unset + unset = unset", 351 bp: ` 352 my_module_type { 353 name: "foo", 354 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 355 "foo": "bar", 356 default: unset, 357 }) + select(soong_config_variable("my_namespace", "my_variable2"), { 358 "baz": "qux", 359 default: unset, 360 }) 361 } 362 `, 363 provider: selectsTestProvider{}, 364 }, 365 { 366 name: "unset + string = string", 367 bp: ` 368 my_module_type { 369 name: "foo", 370 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 371 "foo": "bar", 372 default: unset, 373 }) + select(soong_config_variable("my_namespace", "my_variable2"), { 374 default: "a", 375 }) 376 } 377 `, 378 provider: selectsTestProvider{ 379 my_string: proptools.StringPtr("a"), 380 }, 381 }, 382 { 383 name: "unset + bool = bool", 384 bp: ` 385 my_module_type { 386 name: "foo", 387 my_bool: select(soong_config_variable("my_namespace", "my_variable"), { 388 "a": true, 389 default: unset, 390 }) + select(soong_config_variable("my_namespace", "my_variable2"), { 391 default: true, 392 }) 393 } 394 `, 395 provider: selectsTestProvider{ 396 my_bool: proptools.BoolPtr(true), 397 }, 398 }, 399 { 400 name: "defaults with lists are appended", 401 bp: ` 402 my_module_type { 403 name: "foo", 404 defaults: ["bar"], 405 my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { 406 "a": ["a1"], 407 default: ["b1"], 408 }), 409 } 410 my_defaults { 411 name: "bar", 412 my_string_list: select(soong_config_variable("my_namespace", "my_variable2"), { 413 "a": ["a2"], 414 default: ["b2"], 415 }), 416 } 417 `, 418 provider: selectsTestProvider{ 419 my_string_list: &[]string{"b2", "b1"}, 420 }, 421 }, 422 { 423 name: "defaults applied to multiple modules", 424 bp: ` 425 my_module_type { 426 name: "foo2", 427 defaults: ["bar"], 428 my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { 429 "a": ["a1"], 430 default: ["b1"], 431 }), 432 } 433 my_module_type { 434 name: "foo", 435 defaults: ["bar"], 436 my_string_list: select(soong_config_variable("my_namespace", "my_variable"), { 437 "a": ["a1"], 438 default: ["b1"], 439 }), 440 } 441 my_defaults { 442 name: "bar", 443 my_string_list: select(soong_config_variable("my_namespace", "my_variable2"), { 444 "a": ["a2"], 445 default: ["b2"], 446 }), 447 } 448 `, 449 providers: map[string]selectsTestProvider{ 450 "foo": { 451 my_string_list: &[]string{"b2", "b1"}, 452 }, 453 "foo2": { 454 my_string_list: &[]string{"b2", "b1"}, 455 }, 456 }, 457 }, 458 { 459 name: "Replacing string list", 460 bp: ` 461 my_module_type { 462 name: "foo", 463 defaults: ["bar"], 464 replacing_string_list: select(soong_config_variable("my_namespace", "my_variable"), { 465 "a": ["a1"], 466 default: ["b1"], 467 }), 468 } 469 my_defaults { 470 name: "bar", 471 replacing_string_list: select(soong_config_variable("my_namespace", "my_variable2"), { 472 "a": ["a2"], 473 default: ["b2"], 474 }), 475 } 476 `, 477 provider: selectsTestProvider{ 478 replacing_string_list: &[]string{"b1"}, 479 }, 480 }, 481 { 482 name: "Multi-condition string 1", 483 bp: ` 484 my_module_type { 485 name: "foo", 486 my_string: select(( 487 soong_config_variable("my_namespace", "my_variable"), 488 soong_config_variable("my_namespace", "my_variable2"), 489 ), { 490 ("a", "b"): "a+b", 491 ("a", default): "a+default", 492 (default, default): "default", 493 }), 494 } 495 `, 496 vendorVars: map[string]map[string]string{ 497 "my_namespace": { 498 "my_variable": "a", 499 "my_variable2": "b", 500 }, 501 }, 502 provider: selectsTestProvider{ 503 my_string: proptools.StringPtr("a+b"), 504 }, 505 }, 506 { 507 name: "Multi-condition string 2", 508 bp: ` 509 my_module_type { 510 name: "foo", 511 my_string: select(( 512 soong_config_variable("my_namespace", "my_variable"), 513 soong_config_variable("my_namespace", "my_variable2"), 514 ), { 515 ("a", "b"): "a+b", 516 ("a", default): "a+default", 517 (default, default): "default", 518 }), 519 } 520 `, 521 vendorVars: map[string]map[string]string{ 522 "my_namespace": { 523 "my_variable": "a", 524 "my_variable2": "c", 525 }, 526 }, 527 provider: selectsTestProvider{ 528 my_string: proptools.StringPtr("a+default"), 529 }, 530 }, 531 { 532 name: "Multi-condition string 3", 533 bp: ` 534 my_module_type { 535 name: "foo", 536 my_string: select(( 537 soong_config_variable("my_namespace", "my_variable"), 538 soong_config_variable("my_namespace", "my_variable2"), 539 ), { 540 ("a", "b"): "a+b", 541 ("a", default): "a+default", 542 (default, default): "default", 543 }), 544 } 545 `, 546 vendorVars: map[string]map[string]string{ 547 "my_namespace": { 548 "my_variable": "c", 549 "my_variable2": "b", 550 }, 551 }, 552 provider: selectsTestProvider{ 553 my_string: proptools.StringPtr("default"), 554 }, 555 }, 556 { 557 name: "Unhandled string value", 558 bp: ` 559 my_module_type { 560 name: "foo", 561 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 562 "foo": "a", 563 "bar": "b", 564 }), 565 } 566 `, 567 vendorVars: map[string]map[string]string{ 568 "my_namespace": { 569 "my_variable": "baz", 570 }, 571 }, 572 expectedError: `my_string: soong_config_variable\("my_namespace", "my_variable"\) had value "baz", which was not handled by the select statement`, 573 }, 574 { 575 name: "Select on boolean", 576 bp: ` 577 my_module_type { 578 name: "foo", 579 my_string: select(boolean_var_for_testing(), { 580 true: "t", 581 false: "f", 582 }), 583 } 584 `, 585 vendorVars: map[string]map[string]string{ 586 "boolean_var": { 587 "for_testing": "true", 588 }, 589 }, 590 provider: selectsTestProvider{ 591 my_string: proptools.StringPtr("t"), 592 }, 593 }, 594 { 595 name: "Select on boolean soong config variable", 596 bp: ` 597 my_module_type { 598 name: "foo", 599 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 600 true: "t", 601 false: "f", 602 }), 603 } 604 `, 605 vendorVars: map[string]map[string]string{ 606 "my_namespace": { 607 "my_variable": "true", 608 }, 609 }, 610 vendorVarTypes: map[string]map[string]string{ 611 "my_namespace": { 612 "my_variable": "bool", 613 }, 614 }, 615 provider: selectsTestProvider{ 616 my_string: proptools.StringPtr("t"), 617 }, 618 }, 619 { 620 name: "Select on boolean false", 621 bp: ` 622 my_module_type { 623 name: "foo", 624 my_string: select(boolean_var_for_testing(), { 625 true: "t", 626 false: "f", 627 }), 628 } 629 `, 630 vendorVars: map[string]map[string]string{ 631 "boolean_var": { 632 "for_testing": "false", 633 }, 634 }, 635 provider: selectsTestProvider{ 636 my_string: proptools.StringPtr("f"), 637 }, 638 }, 639 { 640 name: "Select on boolean undefined", 641 bp: ` 642 my_module_type { 643 name: "foo", 644 my_string: select(boolean_var_for_testing(), { 645 true: "t", 646 false: "f", 647 }), 648 } 649 `, 650 expectedError: `my_string: boolean_var_for_testing\(\) had value undefined, which was not handled by the select statement`, 651 }, 652 { 653 name: "Select on boolean undefined with default", 654 bp: ` 655 my_module_type { 656 name: "foo", 657 my_string: select(boolean_var_for_testing(), { 658 true: "t", 659 false: "f", 660 default: "default", 661 }), 662 } 663 `, 664 provider: selectsTestProvider{ 665 my_string: proptools.StringPtr("default"), 666 }, 667 }, 668 { 669 name: "Mismatched condition types", 670 bp: ` 671 my_module_type { 672 name: "foo", 673 my_string: select(boolean_var_for_testing(), { 674 "true": "t", 675 "false": "f", 676 default: "default", 677 }), 678 } 679 `, 680 vendorVars: map[string]map[string]string{ 681 "boolean_var": { 682 "for_testing": "false", 683 }, 684 }, 685 expectedError: "Expected all branches of a select on condition boolean_var_for_testing\\(\\) to have type bool, found string", 686 }, 687 { 688 name: "Assigning select to nonconfigurable bool", 689 bp: ` 690 my_module_type { 691 name: "foo", 692 my_nonconfigurable_bool: select(arch(), { 693 "x86_64": true, 694 default: false, 695 }), 696 } 697 `, 698 expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_bool"`, 699 }, 700 { 701 name: "Assigning select to nonconfigurable string", 702 bp: ` 703 my_module_type { 704 name: "foo", 705 my_nonconfigurable_string: select(arch(), { 706 "x86_64": "x86!", 707 default: "unknown!", 708 }), 709 } 710 `, 711 expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string"`, 712 }, 713 { 714 name: "Assigning appended selects to nonconfigurable string", 715 bp: ` 716 my_module_type { 717 name: "foo", 718 my_nonconfigurable_string: select(arch(), { 719 "x86_64": "x86!", 720 default: "unknown!", 721 }) + select(os(), { 722 "darwin": "_darwin!", 723 default: "unknown!", 724 }), 725 } 726 `, 727 expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string"`, 728 }, 729 { 730 name: "Assigning select to nonconfigurable string list", 731 bp: ` 732 my_module_type { 733 name: "foo", 734 my_nonconfigurable_string_list: select(arch(), { 735 "x86_64": ["foo", "bar"], 736 default: ["baz", "qux"], 737 }), 738 } 739 `, 740 expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string_list"`, 741 }, 742 { 743 name: "Select in variable", 744 bp: ` 745 my_second_variable = ["after.cpp"] 746 my_variable = select(soong_config_variable("my_namespace", "my_variable"), { 747 "a": ["a.cpp"], 748 "b": ["b.cpp"], 749 default: ["c.cpp"], 750 }) + my_second_variable 751 my_module_type { 752 name: "foo", 753 my_string_list: ["before.cpp"] + my_variable, 754 } 755 `, 756 provider: selectsTestProvider{ 757 my_string_list: &[]string{"before.cpp", "a.cpp", "after.cpp"}, 758 }, 759 vendorVars: map[string]map[string]string{ 760 "my_namespace": { 761 "my_variable": "a", 762 }, 763 }, 764 }, 765 { 766 name: "Soong config value variable on configurable property", 767 bp: ` 768 soong_config_module_type { 769 name: "soong_config_my_module_type", 770 module_type: "my_module_type", 771 config_namespace: "my_namespace", 772 value_variables: ["my_variable"], 773 properties: ["my_string", "my_string_list"], 774 } 775 776 soong_config_my_module_type { 777 name: "foo", 778 my_string_list: ["before.cpp"], 779 soong_config_variables: { 780 my_variable: { 781 my_string_list: ["after_%s.cpp"], 782 my_string: "%s.cpp", 783 }, 784 }, 785 } 786 `, 787 provider: selectsTestProvider{ 788 my_string: proptools.StringPtr("foo.cpp"), 789 my_string_list: &[]string{"before.cpp", "after_foo.cpp"}, 790 }, 791 vendorVars: map[string]map[string]string{ 792 "my_namespace": { 793 "my_variable": "foo", 794 }, 795 }, 796 }, 797 { 798 name: "Property appending with variable", 799 bp: ` 800 my_variable = ["b.cpp"] 801 my_module_type { 802 name: "foo", 803 my_string_list: ["a.cpp"] + my_variable + select(soong_config_variable("my_namespace", "my_variable"), { 804 "a": ["a.cpp"], 805 "b": ["b.cpp"], 806 default: ["c.cpp"], 807 }), 808 } 809 `, 810 provider: selectsTestProvider{ 811 my_string_list: &[]string{"a.cpp", "b.cpp", "c.cpp"}, 812 }, 813 }, 814 { 815 name: "Test AppendSimpleValue", 816 bp: ` 817 my_module_type { 818 name: "foo", 819 my_string_list: ["a.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), { 820 "a": ["a.cpp"], 821 "b": ["b.cpp"], 822 default: ["c.cpp"], 823 }), 824 } 825 `, 826 vendorVars: map[string]map[string]string{ 827 "selects_test": { 828 "append_to_string_list": "foo.cpp", 829 }, 830 }, 831 provider: selectsTestProvider{ 832 my_string_list: &[]string{"a.cpp", "c.cpp", "foo.cpp"}, 833 }, 834 }, 835 { 836 name: "Arch variant bool", 837 bp: ` 838 my_variable = ["b.cpp"] 839 my_module_type { 840 name: "foo", 841 arch_variant_configurable_bool: false, 842 target: { 843 bionic_arm64: { 844 enabled: true, 845 }, 846 }, 847 } 848 `, 849 provider: selectsTestProvider{ 850 arch_variant_configurable_bool: proptools.BoolPtr(false), 851 }, 852 }, 853 { 854 name: "Simple string binding", 855 bp: ` 856 my_module_type { 857 name: "foo", 858 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 859 any @ my_binding: "hello " + my_binding, 860 default: "goodbye", 861 }) 862 } 863 `, 864 vendorVars: map[string]map[string]string{ 865 "my_namespace": { 866 "my_variable": "world!", 867 }, 868 }, 869 provider: selectsTestProvider{ 870 my_string: proptools.StringPtr("hello world!"), 871 }, 872 }, 873 { 874 name: "Any branch with binding not taken", 875 bp: ` 876 my_module_type { 877 name: "foo", 878 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 879 any @ my_binding: "hello " + my_binding, 880 default: "goodbye", 881 }) 882 } 883 `, 884 provider: selectsTestProvider{ 885 my_string: proptools.StringPtr("goodbye"), 886 }, 887 }, 888 { 889 name: "Any branch without binding", 890 bp: ` 891 my_module_type { 892 name: "foo", 893 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 894 any: "hello", 895 default: "goodbye", 896 }) 897 } 898 `, 899 vendorVars: map[string]map[string]string{ 900 "my_namespace": { 901 "my_variable": "world!", 902 }, 903 }, 904 provider: selectsTestProvider{ 905 my_string: proptools.StringPtr("hello"), 906 }, 907 }, 908 { 909 name: "Binding conflicts with file-level variable", 910 bp: ` 911 my_binding = "asdf" 912 my_module_type { 913 name: "foo", 914 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 915 any @ my_binding: "hello", 916 default: "goodbye", 917 }) 918 } 919 `, 920 vendorVars: map[string]map[string]string{ 921 "my_namespace": { 922 "my_variable": "world!", 923 }, 924 }, 925 expectedError: "variable already set in inherited scope, previous assignment", 926 }, 927 { 928 name: "Binding in combination with file-level variable", 929 bp: ` 930 my_var = " there " 931 my_module_type { 932 name: "foo", 933 my_string: select(soong_config_variable("my_namespace", "my_variable"), { 934 any @ my_binding: "hello" + my_var + my_binding, 935 default: "goodbye", 936 }) 937 } 938 `, 939 vendorVars: map[string]map[string]string{ 940 "my_namespace": { 941 "my_variable": "world!", 942 }, 943 }, 944 provider: selectsTestProvider{ 945 my_string: proptools.StringPtr("hello there world!"), 946 }, 947 }, 948 { 949 name: "Bindings in subdirectory inherits variable", 950 fs: map[string][]byte{ 951 "Android.bp": []byte(` 952my_var = "abcd" 953`), 954 "directoryB/Android.bp": []byte(` 955my_module_type { 956 name: "foo", 957 my_string: select(soong_config_variable("my_namespace", "variable_a"), { 958 any @ my_binding: my_var + my_binding, 959 default: "", 960 }), 961} 962`), 963 }, 964 vendorVars: map[string]map[string]string{ 965 "my_namespace": { 966 "variable_a": "e", 967 }, 968 }, 969 provider: selectsTestProvider{ 970 my_string: proptools.StringPtr("abcde"), 971 }, 972 }, 973 { 974 name: "Cannot modify variable after referenced by select", 975 bp: ` 976my_var = "foo" 977my_module_type { 978 name: "foo", 979 my_string: select(soong_config_variable("my_namespace", "variable_a"), { 980 "a": my_var, 981 default: "", 982 }), 983} 984my_var += "bar" 985`, 986 vendorVars: map[string]map[string]string{ 987 "my_namespace": { 988 "variable_a": "b", // notably not the value that causes my_var to be referenced 989 }, 990 }, 991 expectedError: `modified variable "my_var" with \+= after referencing`, 992 }, 993 { 994 name: "Cannot shadow variable with binding", 995 bp: ` 996my_var = "foo" 997my_module_type { 998 name: "foo", 999 my_string: select(soong_config_variable("my_namespace", "variable_a"), { 1000 any @ my_var: my_var, 1001 default: "", 1002 }), 1003} 1004`, 1005 vendorVars: map[string]map[string]string{ 1006 "my_namespace": { 1007 "variable_a": "a", 1008 }, 1009 }, 1010 expectedError: `variable already set in inherited scope, previous assignment:`, 1011 }, 1012 { 1013 name: "Basic string list postprocessor", 1014 bp: ` 1015my_defaults { 1016 name: "defaults_a", 1017 my_string_list: ["a", "b", "c"], 1018 string_list_postprocessor_add_to_elements: "1", 1019} 1020my_defaults { 1021 name: "defaults_b", 1022 my_string_list: ["d", "e", "f"], 1023 string_list_postprocessor_add_to_elements: "2", 1024} 1025my_module_type { 1026 name: "foo", 1027 defaults: ["defaults_a", "defaults_b"], 1028} 1029`, 1030 provider: selectsTestProvider{ 1031 my_string_list: &[]string{"d2", "e2", "f2", "a1", "b1", "c1"}, 1032 }, 1033 }, 1034 { 1035 name: "string list variables", 1036 bp: ` 1037my_module_type { 1038 name: "foo", 1039 my_string_list: ["a"] + select(soong_config_variable("my_namespace", "my_var"), { 1040 any @ my_var: my_var, 1041 default: [], 1042 }), 1043} 1044`, 1045 vendorVars: map[string]map[string]string{ 1046 "my_namespace": { 1047 "my_var": "b c", 1048 }, 1049 }, 1050 vendorVarTypes: map[string]map[string]string{ 1051 "my_namespace": { 1052 "my_var": "string_list", 1053 }, 1054 }, 1055 provider: selectsTestProvider{ 1056 my_string_list: &[]string{"a", "b", "c"}, 1057 }, 1058 }, 1059 { 1060 name: "string list variables don't match string matchers", 1061 bp: ` 1062my_module_type { 1063 name: "foo", 1064 my_string_list: ["a"] + select(soong_config_variable("my_namespace", "my_var"), { 1065 "foo": ["b"], 1066 default: [], 1067 }), 1068} 1069`, 1070 vendorVars: map[string]map[string]string{ 1071 "my_namespace": { 1072 "my_var": "b c", 1073 }, 1074 }, 1075 vendorVarTypes: map[string]map[string]string{ 1076 "my_namespace": { 1077 "my_var": "string_list", 1078 }, 1079 }, 1080 expectedError: `Expected all branches of a select on condition soong_config_variable\("my_namespace", "my_var"\) to have type string_list, found string`, 1081 }, 1082 } 1083 1084 for _, tc := range testCases { 1085 t.Run(tc.name, func(t *testing.T) { 1086 fs := tc.fs 1087 if fs == nil { 1088 fs = make(MockFS) 1089 } 1090 if tc.bp != "" { 1091 fs["Android.bp"] = []byte(tc.bp) 1092 } 1093 fixtures := GroupFixturePreparers( 1094 PrepareForTestWithDefaults, 1095 PrepareForTestWithArchMutator, 1096 PrepareForTestWithSoongConfigModuleBuildComponents, 1097 FixtureRegisterWithContext(func(ctx RegistrationContext) { 1098 ctx.RegisterModuleType("my_module_type", newSelectsMockModule) 1099 ctx.RegisterModuleType("my_defaults", newSelectsMockModuleDefaults) 1100 }), 1101 FixtureModifyProductVariables(func(variables FixtureProductVariables) { 1102 variables.VendorVars = tc.vendorVars 1103 variables.VendorVarTypes = tc.vendorVarTypes 1104 }), 1105 FixtureMergeMockFs(fs), 1106 ) 1107 if tc.expectedError != "" { 1108 fixtures = fixtures.ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(tc.expectedError)) 1109 } 1110 result := fixtures.RunTest(t) 1111 1112 if tc.expectedError == "" { 1113 if len(tc.providers) == 0 { 1114 tc.providers = map[string]selectsTestProvider{ 1115 "foo": tc.provider, 1116 } 1117 } 1118 1119 for moduleName := range tc.providers { 1120 expected := tc.providers[moduleName] 1121 m := result.ModuleForTests(moduleName, "android_arm64_armv8-a") 1122 p, _ := OtherModuleProvider(result.testContext.OtherModuleProviderAdaptor(), m.Module(), selectsTestProviderKey) 1123 if !reflect.DeepEqual(p, expected) { 1124 t.Errorf("Expected:\n %q\ngot:\n %q", expected.String(), p.String()) 1125 } 1126 } 1127 } 1128 }) 1129 } 1130} 1131 1132type selectsTestProvider struct { 1133 my_bool *bool 1134 my_string *string 1135 my_string_list *[]string 1136 my_paths *[]string 1137 replacing_string_list *[]string 1138 arch_variant_configurable_bool *bool 1139 my_nonconfigurable_bool *bool 1140 my_nonconfigurable_string *string 1141 my_nonconfigurable_string_list []string 1142} 1143 1144func (p *selectsTestProvider) String() string { 1145 myBoolStr := "nil" 1146 if p.my_bool != nil { 1147 myBoolStr = fmt.Sprintf("%t", *p.my_bool) 1148 } 1149 myStringStr := "nil" 1150 if p.my_string != nil { 1151 myStringStr = *p.my_string 1152 } 1153 myNonconfigurableStringStr := "nil" 1154 if p.my_nonconfigurable_string != nil { 1155 myNonconfigurableStringStr = *p.my_nonconfigurable_string 1156 } 1157 return fmt.Sprintf(`selectsTestProvider { 1158 my_bool: %v, 1159 my_string: %s, 1160 my_string_list: %s, 1161 my_paths: %s, 1162 replacing_string_list %s, 1163 arch_variant_configurable_bool %v 1164 my_nonconfigurable_bool: %v, 1165 my_nonconfigurable_string: %s, 1166 my_nonconfigurable_string_list: %s, 1167}`, 1168 myBoolStr, 1169 myStringStr, 1170 p.my_string_list, 1171 p.my_paths, 1172 p.replacing_string_list, 1173 p.arch_variant_configurable_bool, 1174 p.my_nonconfigurable_bool, 1175 myNonconfigurableStringStr, 1176 p.my_nonconfigurable_string_list, 1177 ) 1178} 1179 1180var selectsTestProviderKey = blueprint.NewProvider[selectsTestProvider]() 1181 1182type selectsMockModuleProperties struct { 1183 My_bool proptools.Configurable[bool] 1184 My_string proptools.Configurable[string] 1185 My_string_list proptools.Configurable[[]string] 1186 My_paths proptools.Configurable[[]string] `android:"path"` 1187 Replacing_string_list proptools.Configurable[[]string] `android:"replace_instead_of_append,arch_variant"` 1188 Arch_variant_configurable_bool proptools.Configurable[bool] `android:"replace_instead_of_append,arch_variant"` 1189 My_nonconfigurable_bool *bool 1190 My_nonconfigurable_string *string 1191 My_nonconfigurable_string_list []string 1192} 1193 1194type selectsMockModule struct { 1195 ModuleBase 1196 DefaultableModuleBase 1197 properties selectsMockModuleProperties 1198} 1199 1200func optionalToPtr[T any](o proptools.ConfigurableOptional[T]) *T { 1201 if o.IsEmpty() { 1202 return nil 1203 } 1204 x := o.Get() 1205 return &x 1206} 1207 1208func (p *selectsMockModule) GenerateAndroidBuildActions(ctx ModuleContext) { 1209 toAppend := ctx.Config().VendorConfig("selects_test").String("append_to_string_list") 1210 if toAppend != "" { 1211 p.properties.My_string_list.AppendSimpleValue([]string{toAppend}) 1212 } 1213 SetProvider(ctx, selectsTestProviderKey, selectsTestProvider{ 1214 my_bool: optionalToPtr(p.properties.My_bool.Get(ctx)), 1215 my_string: optionalToPtr(p.properties.My_string.Get(ctx)), 1216 my_string_list: optionalToPtr(p.properties.My_string_list.Get(ctx)), 1217 my_paths: optionalToPtr(p.properties.My_paths.Get(ctx)), 1218 replacing_string_list: optionalToPtr(p.properties.Replacing_string_list.Get(ctx)), 1219 arch_variant_configurable_bool: optionalToPtr(p.properties.Arch_variant_configurable_bool.Get(ctx)), 1220 my_nonconfigurable_bool: p.properties.My_nonconfigurable_bool, 1221 my_nonconfigurable_string: p.properties.My_nonconfigurable_string, 1222 my_nonconfigurable_string_list: p.properties.My_nonconfigurable_string_list, 1223 }) 1224} 1225 1226func newSelectsMockModule() Module { 1227 m := &selectsMockModule{} 1228 m.AddProperties(&m.properties) 1229 InitAndroidArchModule(m, HostAndDeviceSupported, MultilibFirst) 1230 InitDefaultableModule(m) 1231 return m 1232} 1233 1234type selectsMockDefaultsProperties struct { 1235 String_list_postprocessor_add_to_elements string 1236} 1237 1238type selectsMockModuleDefaults struct { 1239 ModuleBase 1240 DefaultsModuleBase 1241 myProperties selectsMockModuleProperties 1242 defaultsProperties selectsMockDefaultsProperties 1243} 1244 1245func (d *selectsMockModuleDefaults) GenerateAndroidBuildActions(ctx ModuleContext) { 1246} 1247 1248func newSelectsMockModuleDefaults() Module { 1249 module := &selectsMockModuleDefaults{} 1250 1251 module.AddProperties( 1252 &module.myProperties, 1253 &module.defaultsProperties, 1254 ) 1255 1256 InitDefaultsModule(module) 1257 1258 AddLoadHook(module, func(lhc LoadHookContext) { 1259 if module.defaultsProperties.String_list_postprocessor_add_to_elements != "" { 1260 module.myProperties.My_string_list.AddPostProcessor(func(x []string) []string { 1261 for i := range x { 1262 x[i] = x[i] + module.defaultsProperties.String_list_postprocessor_add_to_elements 1263 } 1264 return x 1265 }) 1266 } 1267 }) 1268 1269 return module 1270} 1271