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