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 "io" 20 "reflect" 21 "runtime" 22 "strings" 23 "testing" 24 25 "github.com/google/blueprint/proptools" 26) 27 28type customModule struct { 29 ModuleBase 30 31 properties struct { 32 Default_dist_files *string 33 Dist_output_file *bool 34 } 35 36 data AndroidMkData 37 outputFile OptionalPath 38} 39 40const ( 41 defaultDistFiles_None = "none" 42 defaultDistFiles_Default = "default" 43 defaultDistFiles_Tagged = "tagged" 44) 45 46func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) { 47 48 var defaultDistPaths Paths 49 50 // If the dist_output_file: true then create an output file that is stored in 51 // the OutputFile property of the AndroidMkEntry. 52 if proptools.BoolDefault(m.properties.Dist_output_file, true) { 53 path := PathForTesting("dist-output-file.out") 54 m.outputFile = OptionalPathForPath(path) 55 56 // Previous code would prioritize the DistFiles property over the OutputFile 57 // property in AndroidMkEntry when determining the default dist paths. 58 // Setting this first allows it to be overridden based on the 59 // default_dist_files setting replicating that previous behavior. 60 defaultDistPaths = Paths{path} 61 } 62 63 // Based on the setting of the default_dist_files property possibly create a 64 // TaggedDistFiles structure that will be stored in the DistFiles property of 65 // the AndroidMkEntry. 66 defaultDistFiles := proptools.StringDefault(m.properties.Default_dist_files, defaultDistFiles_Tagged) 67 switch defaultDistFiles { 68 case defaultDistFiles_None: 69 m.setOutputFiles(ctx, defaultDistPaths) 70 71 case defaultDistFiles_Default: 72 path := PathForTesting("default-dist.out") 73 defaultDistPaths = Paths{path} 74 m.setOutputFiles(ctx, defaultDistPaths) 75 76 case defaultDistFiles_Tagged: 77 // Module types that set AndroidMkEntry.DistFiles to the result of calling 78 // GenerateTaggedDistFiles(ctx) relied on no tag being treated as "" which 79 // meant that the default dist paths would be the same as empty-string-tag 80 // output files. In order to preserve that behavior when treating no tag 81 // as being equal to DefaultDistTag this ensures that DefaultDistTag output 82 // will be the same as empty-string-tag output. 83 defaultDistPaths = PathsForTesting("one.out") 84 m.setOutputFiles(ctx, defaultDistPaths) 85 } 86} 87 88func (m *customModule) setOutputFiles(ctx ModuleContext, defaultDistPaths Paths) { 89 ctx.SetOutputFiles(PathsForTesting("one.out"), "") 90 ctx.SetOutputFiles(PathsForTesting("two.out", "three/four.out"), ".multiple") 91 ctx.SetOutputFiles(PathsForTesting("another.out"), ".another-tag") 92 if defaultDistPaths != nil { 93 ctx.SetOutputFiles(defaultDistPaths, DefaultDistTag) 94 } 95} 96 97func (m *customModule) AndroidMk() AndroidMkData { 98 return AndroidMkData{ 99 Custom: func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData) { 100 m.data = data 101 }, 102 } 103} 104 105func (m *customModule) AndroidMkEntries() []AndroidMkEntries { 106 return []AndroidMkEntries{ 107 { 108 Class: "CUSTOM_MODULE", 109 OutputFile: m.outputFile, 110 }, 111 } 112} 113 114func customModuleFactory() Module { 115 module := &customModule{} 116 117 module.AddProperties(&module.properties) 118 119 InitAndroidModule(module) 120 return module 121} 122 123// buildContextAndCustomModuleFoo creates a config object, processes the supplied 124// bp module and then returns the config and the custom module called "foo". 125func buildContextAndCustomModuleFoo(t *testing.T, bp string) (*TestContext, *customModule) { 126 t.Helper() 127 result := GroupFixturePreparers( 128 // Enable androidmk Singleton 129 PrepareForTestWithAndroidMk, 130 FixtureRegisterWithContext(func(ctx RegistrationContext) { 131 ctx.RegisterModuleType("custom", customModuleFactory) 132 }), 133 FixtureModifyProductVariables(func(variables FixtureProductVariables) { 134 variables.DeviceProduct = proptools.StringPtr("bar") 135 }), 136 FixtureWithRootAndroidBp(bp), 137 ).RunTest(t) 138 139 module := result.ModuleForTests(t, "foo", "").Module().(*customModule) 140 return result.TestContext, module 141} 142 143func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) { 144 if runtime.GOOS == "darwin" { 145 // Device modules are not exported on Mac, so this test doesn't work. 146 t.SkipNow() 147 } 148 149 bp := ` 150 custom { 151 name: "foo", 152 required: ["bar"], 153 host_required: ["baz"], 154 target_required: ["qux"], 155 } 156 ` 157 158 _, m := buildContextAndCustomModuleFoo(t, bp) 159 160 assertEqual := func(expected interface{}, actual interface{}) { 161 if !reflect.DeepEqual(expected, actual) { 162 t.Errorf("%q expected, but got %q", expected, actual) 163 } 164 } 165 assertEqual([]string{"bar"}, m.data.Required) 166 assertEqual([]string{"baz"}, m.data.Host_required) 167 assertEqual([]string{"qux"}, m.data.Target_required) 168} 169 170func TestGenerateDistContributionsForMake(t *testing.T) { 171 dc := &distContributions{ 172 copiesForGoals: []*copiesForGoals{ 173 { 174 goals: "my_goal", 175 copies: []distCopy{ 176 distCopyForTest("one.out", "one.out"), 177 distCopyForTest("two.out", "other.out"), 178 }, 179 }, 180 }, 181 } 182 183 dc.licenseMetadataFile = PathForTesting("meta_lic") 184 makeOutput := generateDistContributionsForMake(dc) 185 186 assertStringEquals(t, `.PHONY: my_goal 187$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic)) 188$(call dist-for-goals,my_goal,one.out:one.out) 189$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic)) 190$(call dist-for-goals,my_goal,two.out:other.out)`, strings.Join(makeOutput, "\n")) 191} 192 193func TestGetDistForGoals(t *testing.T) { 194 bp := ` 195 custom { 196 name: "foo", 197 dist: { 198 targets: ["my_goal", "my_other_goal"], 199 tag: ".multiple", 200 }, 201 dists: [ 202 { 203 targets: ["my_second_goal"], 204 tag: ".multiple", 205 }, 206 { 207 targets: ["my_third_goal"], 208 dir: "test/dir", 209 }, 210 { 211 targets: ["my_fourth_goal"], 212 suffix: ".suffix", 213 }, 214 { 215 targets: ["my_fifth_goal"], 216 dest: "new-name", 217 }, 218 { 219 targets: ["my_sixth_goal"], 220 dest: "new-name", 221 dir: "some/dir", 222 suffix: ".suffix", 223 }, 224 ], 225 } 226 ` 227 228 expectedAndroidMkLines := []string{ 229 ".PHONY: my_second_goal", 230 "$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))", 231 "$(call dist-for-goals,my_second_goal,two.out:two.out)", 232 "$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))", 233 "$(call dist-for-goals,my_second_goal,three/four.out:four.out)", 234 ".PHONY: my_third_goal", 235 "$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))", 236 "$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)", 237 ".PHONY: my_fourth_goal", 238 "$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))", 239 "$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)", 240 ".PHONY: my_fifth_goal", 241 "$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))", 242 "$(call dist-for-goals,my_fifth_goal,one.out:new-name)", 243 ".PHONY: my_sixth_goal", 244 "$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))", 245 "$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)", 246 ".PHONY: my_goal my_other_goal", 247 "$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))", 248 "$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)", 249 "$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))", 250 "$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)", 251 } 252 253 ctx, module := buildContextAndCustomModuleFoo(t, bp) 254 entries := AndroidMkEntriesForTest(t, ctx, module) 255 if len(entries) != 1 { 256 t.Errorf("Expected a single AndroidMk entry, got %d", len(entries)) 257 } 258 androidMkLines := entries[0].GetDistForGoals(module) 259 260 if len(androidMkLines) != len(expectedAndroidMkLines) { 261 t.Errorf( 262 "Expected %d AndroidMk lines, got %d:\n%v", 263 len(expectedAndroidMkLines), 264 len(androidMkLines), 265 androidMkLines, 266 ) 267 } 268 for idx, line := range androidMkLines { 269 expectedLine := strings.ReplaceAll(expectedAndroidMkLines[idx], "meta_lic", 270 OtherModuleProviderOrDefault(ctx, module, InstallFilesProvider).LicenseMetadataFile.String()) 271 if line != expectedLine { 272 t.Errorf( 273 "Expected AndroidMk line to be '%s', got '%s'", 274 expectedLine, 275 line, 276 ) 277 } 278 } 279} 280 281func distCopyForTest(from, to string) distCopy { 282 return distCopy{PathForTesting(from), to} 283} 284 285func TestGetDistContributions(t *testing.T) { 286 compareContributions := func(d1 *distContributions, d2 *distContributions) error { 287 if d1 == nil || d2 == nil { 288 if d1 != d2 { 289 return fmt.Errorf("pointer mismatch, expected both to be nil but they were %p and %p", d1, d2) 290 } else { 291 return nil 292 } 293 } 294 if expected, actual := len(d1.copiesForGoals), len(d2.copiesForGoals); expected != actual { 295 return fmt.Errorf("length mismatch, expected %d found %d", expected, actual) 296 } 297 298 for i, copies1 := range d1.copiesForGoals { 299 copies2 := d2.copiesForGoals[i] 300 if expected, actual := copies1.goals, copies2.goals; expected != actual { 301 return fmt.Errorf("goals mismatch at position %d: expected %q found %q", i, expected, actual) 302 } 303 304 if expected, actual := len(copies1.copies), len(copies2.copies); expected != actual { 305 return fmt.Errorf("length mismatch in copy instructions at position %d, expected %d found %d", i, expected, actual) 306 } 307 308 for j, c1 := range copies1.copies { 309 c2 := copies2.copies[j] 310 if expected, actual := NormalizePathForTesting(c1.from), NormalizePathForTesting(c2.from); expected != actual { 311 return fmt.Errorf("paths mismatch at position %d.%d: expected %q found %q", i, j, expected, actual) 312 } 313 314 if expected, actual := c1.dest, c2.dest; expected != actual { 315 return fmt.Errorf("dest mismatch at position %d.%d: expected %q found %q", i, j, expected, actual) 316 } 317 } 318 } 319 320 return nil 321 } 322 323 formatContributions := func(d *distContributions) string { 324 buf := &strings.Builder{} 325 if d == nil { 326 fmt.Fprint(buf, "nil") 327 } else { 328 for _, copiesForGoals := range d.copiesForGoals { 329 fmt.Fprintf(buf, " Goals: %q {\n", copiesForGoals.goals) 330 for _, c := range copiesForGoals.copies { 331 fmt.Fprintf(buf, " %s -> %s\n", NormalizePathForTesting(c.from), c.dest) 332 } 333 fmt.Fprint(buf, " }\n") 334 } 335 } 336 return buf.String() 337 } 338 339 testHelper := func(t *testing.T, name, bp string, expectedContributions *distContributions) { 340 t.Helper() 341 t.Run(name, func(t *testing.T) { 342 t.Helper() 343 344 ctx, module := buildContextAndCustomModuleFoo(t, bp) 345 entries := AndroidMkEntriesForTest(t, ctx, module) 346 if len(entries) != 1 { 347 t.Errorf("Expected a single AndroidMk entry, got %d", len(entries)) 348 } 349 distContributions := getDistContributions(ctx, module) 350 351 if err := compareContributions(expectedContributions, distContributions); err != nil { 352 t.Errorf("%s\nExpected Contributions\n%sActualContributions\n%s", 353 err, 354 formatContributions(expectedContributions), 355 formatContributions(distContributions)) 356 } 357 }) 358 } 359 360 testHelper(t, "dist-without-tag", ` 361 custom { 362 name: "foo", 363 dist: { 364 targets: ["my_goal"] 365 } 366 } 367`, 368 &distContributions{ 369 copiesForGoals: []*copiesForGoals{ 370 { 371 goals: "my_goal", 372 copies: []distCopy{ 373 distCopyForTest("one.out", "one.out"), 374 }, 375 }, 376 }, 377 }) 378 379 testHelper(t, "dist-with-tag", ` 380 custom { 381 name: "foo", 382 dist: { 383 targets: ["my_goal"], 384 tag: ".another-tag", 385 } 386 } 387`, 388 &distContributions{ 389 copiesForGoals: []*copiesForGoals{ 390 { 391 goals: "my_goal", 392 copies: []distCopy{ 393 distCopyForTest("another.out", "another.out"), 394 }, 395 }, 396 }, 397 }) 398 399 testHelper(t, "append-artifact-with-product", ` 400 custom { 401 name: "foo", 402 dist: { 403 targets: ["my_goal"], 404 append_artifact_with_product: true, 405 } 406 } 407`, &distContributions{ 408 copiesForGoals: []*copiesForGoals{ 409 { 410 goals: "my_goal", 411 copies: []distCopy{ 412 distCopyForTest("one.out", "one_bar.out"), 413 }, 414 }, 415 }, 416 }) 417 418 testHelper(t, "dists-with-tag", ` 419 custom { 420 name: "foo", 421 dists: [ 422 { 423 targets: ["my_goal"], 424 tag: ".another-tag", 425 }, 426 ], 427 } 428`, 429 &distContributions{ 430 copiesForGoals: []*copiesForGoals{ 431 { 432 goals: "my_goal", 433 copies: []distCopy{ 434 distCopyForTest("another.out", "another.out"), 435 }, 436 }, 437 }, 438 }) 439 440 testHelper(t, "multiple-dists-with-and-without-tag", ` 441 custom { 442 name: "foo", 443 dists: [ 444 { 445 targets: ["my_goal"], 446 }, 447 { 448 targets: ["my_second_goal", "my_third_goal"], 449 }, 450 ], 451 } 452`, 453 &distContributions{ 454 copiesForGoals: []*copiesForGoals{ 455 { 456 goals: "my_goal", 457 copies: []distCopy{ 458 distCopyForTest("one.out", "one.out"), 459 }, 460 }, 461 { 462 goals: "my_second_goal my_third_goal", 463 copies: []distCopy{ 464 distCopyForTest("one.out", "one.out"), 465 }, 466 }, 467 }, 468 }) 469 470 testHelper(t, "dist-plus-dists-without-tags", ` 471 custom { 472 name: "foo", 473 dist: { 474 targets: ["my_goal"], 475 }, 476 dists: [ 477 { 478 targets: ["my_second_goal", "my_third_goal"], 479 }, 480 ], 481 } 482`, 483 &distContributions{ 484 copiesForGoals: []*copiesForGoals{ 485 { 486 goals: "my_second_goal my_third_goal", 487 copies: []distCopy{ 488 distCopyForTest("one.out", "one.out"), 489 }, 490 }, 491 { 492 goals: "my_goal", 493 copies: []distCopy{ 494 distCopyForTest("one.out", "one.out"), 495 }, 496 }, 497 }, 498 }) 499 500 testHelper(t, "dist-plus-dists-with-tags", ` 501 custom { 502 name: "foo", 503 dist: { 504 targets: ["my_goal", "my_other_goal"], 505 tag: ".multiple", 506 }, 507 dists: [ 508 { 509 targets: ["my_second_goal"], 510 tag: ".multiple", 511 }, 512 { 513 targets: ["my_third_goal"], 514 dir: "test/dir", 515 }, 516 { 517 targets: ["my_fourth_goal"], 518 suffix: ".suffix", 519 }, 520 { 521 targets: ["my_fifth_goal"], 522 dest: "new-name", 523 }, 524 { 525 targets: ["my_sixth_goal"], 526 dest: "new-name", 527 dir: "some/dir", 528 suffix: ".suffix", 529 }, 530 ], 531 } 532`, 533 &distContributions{ 534 copiesForGoals: []*copiesForGoals{ 535 { 536 goals: "my_second_goal", 537 copies: []distCopy{ 538 distCopyForTest("two.out", "two.out"), 539 distCopyForTest("three/four.out", "four.out"), 540 }, 541 }, 542 { 543 goals: "my_third_goal", 544 copies: []distCopy{ 545 distCopyForTest("one.out", "test/dir/one.out"), 546 }, 547 }, 548 { 549 goals: "my_fourth_goal", 550 copies: []distCopy{ 551 distCopyForTest("one.out", "one.suffix.out"), 552 }, 553 }, 554 { 555 goals: "my_fifth_goal", 556 copies: []distCopy{ 557 distCopyForTest("one.out", "new-name"), 558 }, 559 }, 560 { 561 goals: "my_sixth_goal", 562 copies: []distCopy{ 563 distCopyForTest("one.out", "some/dir/new-name.suffix"), 564 }, 565 }, 566 { 567 goals: "my_goal my_other_goal", 568 copies: []distCopy{ 569 distCopyForTest("two.out", "two.out"), 570 distCopyForTest("three/four.out", "four.out"), 571 }, 572 }, 573 }, 574 }) 575 576 // The above test the default values of default_dist_files and use_output_file. 577 578 // The following tests explicitly test the different combinations of those settings. 579 testHelper(t, "tagged-dist-files-no-output", ` 580 custom { 581 name: "foo", 582 default_dist_files: "tagged", 583 dist_output_file: false, 584 dists: [ 585 { 586 targets: ["my_goal"], 587 }, 588 { 589 targets: ["my_goal"], 590 tag: ".multiple", 591 }, 592 ], 593 } 594`, &distContributions{ 595 copiesForGoals: []*copiesForGoals{ 596 { 597 goals: "my_goal", 598 copies: []distCopy{ 599 distCopyForTest("one.out", "one.out"), 600 }, 601 }, 602 { 603 goals: "my_goal", 604 copies: []distCopy{ 605 distCopyForTest("two.out", "two.out"), 606 distCopyForTest("three/four.out", "four.out"), 607 }, 608 }, 609 }, 610 }) 611 612 testHelper(t, "default-dist-files-no-output", ` 613 custom { 614 name: "foo", 615 default_dist_files: "default", 616 dist_output_file: false, 617 dists: [ 618 { 619 targets: ["my_goal"], 620 }, 621 { 622 targets: ["my_goal"], 623 tag: ".multiple", 624 }, 625 ], 626 } 627`, &distContributions{ 628 copiesForGoals: []*copiesForGoals{ 629 { 630 goals: "my_goal", 631 copies: []distCopy{ 632 distCopyForTest("default-dist.out", "default-dist.out"), 633 }, 634 }, 635 { 636 goals: "my_goal", 637 copies: []distCopy{ 638 distCopyForTest("two.out", "two.out"), 639 distCopyForTest("three/four.out", "four.out"), 640 }, 641 }, 642 }, 643 }) 644 645 testHelper(t, "no-dist-files-no-output", ` 646 custom { 647 name: "foo", 648 default_dist_files: "none", 649 dist_output_file: false, 650 dists: [ 651 // The following will dist one.out because there's no default dist file provided 652 // (default_dist_files: "none") and one.out is the outputfile for the "" tag. 653 { 654 targets: ["my_goal"], 655 }, 656 { 657 targets: ["my_goal"], 658 tag: ".multiple", 659 }, 660 ], 661 } 662`, &distContributions{ 663 copiesForGoals: []*copiesForGoals{ 664 { 665 goals: "my_goal", 666 copies: []distCopy{ 667 distCopyForTest("one.out", "one.out"), 668 }, 669 }, 670 { 671 goals: "my_goal", 672 copies: []distCopy{ 673 distCopyForTest("two.out", "two.out"), 674 distCopyForTest("three/four.out", "four.out"), 675 }, 676 }, 677 }, 678 }) 679 680 testHelper(t, "tagged-dist-files-default-output", ` 681 custom { 682 name: "foo", 683 default_dist_files: "tagged", 684 dist_output_file: true, 685 dists: [ 686 { 687 targets: ["my_goal"], 688 }, 689 { 690 targets: ["my_goal"], 691 tag: ".multiple", 692 }, 693 ], 694 } 695`, &distContributions{ 696 copiesForGoals: []*copiesForGoals{ 697 { 698 goals: "my_goal", 699 copies: []distCopy{ 700 distCopyForTest("one.out", "one.out"), 701 }, 702 }, 703 { 704 goals: "my_goal", 705 copies: []distCopy{ 706 distCopyForTest("two.out", "two.out"), 707 distCopyForTest("three/four.out", "four.out"), 708 }, 709 }, 710 }, 711 }) 712 713 testHelper(t, "default-dist-files-default-output", ` 714 custom { 715 name: "foo", 716 default_dist_files: "default", 717 dist_output_file: true, 718 dists: [ 719 { 720 targets: ["my_goal"], 721 }, 722 { 723 targets: ["my_goal"], 724 tag: ".multiple", 725 }, 726 ], 727 } 728`, &distContributions{ 729 copiesForGoals: []*copiesForGoals{ 730 { 731 goals: "my_goal", 732 copies: []distCopy{ 733 distCopyForTest("default-dist.out", "default-dist.out"), 734 }, 735 }, 736 { 737 goals: "my_goal", 738 copies: []distCopy{ 739 distCopyForTest("two.out", "two.out"), 740 distCopyForTest("three/four.out", "four.out"), 741 }, 742 }, 743 }, 744 }) 745 746 testHelper(t, "no-dist-files-default-output", ` 747 custom { 748 name: "foo", 749 default_dist_files: "none", 750 dist_output_file: true, 751 dists: [ 752 { 753 targets: ["my_goal"], 754 }, 755 { 756 targets: ["my_goal"], 757 tag: ".multiple", 758 }, 759 ], 760 } 761`, &distContributions{ 762 copiesForGoals: []*copiesForGoals{ 763 { 764 goals: "my_goal", 765 copies: []distCopy{ 766 distCopyForTest("dist-output-file.out", "dist-output-file.out"), 767 }, 768 }, 769 { 770 goals: "my_goal", 771 copies: []distCopy{ 772 distCopyForTest("two.out", "two.out"), 773 distCopyForTest("three/four.out", "four.out"), 774 }, 775 }, 776 }, 777 }) 778} 779