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