1// Copyright 2018 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 genrule 16 17import ( 18 "fmt" 19 "os" 20 "regexp" 21 "testing" 22 23 "android/soong/android" 24 25 "github.com/google/blueprint/proptools" 26) 27 28func TestMain(m *testing.M) { 29 os.Exit(m.Run()) 30} 31 32var prepareForGenRuleTest = android.GroupFixturePreparers( 33 android.PrepareForTestWithArchMutator, 34 android.PrepareForTestWithDefaults, 35 android.PrepareForTestWithFilegroup, 36 PrepareForTestWithGenRuleBuildComponents, 37 android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { 38 android.RegisterPrebuiltMutators(ctx) 39 ctx.RegisterModuleType("tool", toolFactory) 40 ctx.RegisterModuleType("prebuilt_tool", prebuiltToolFactory) 41 ctx.RegisterModuleType("output", outputProducerFactory) 42 ctx.RegisterModuleType("use_source", useSourceFactory) 43 }), 44 android.FixtureMergeMockFs(android.MockFS{ 45 "tool": nil, 46 "tool_file1": nil, 47 "tool_file2": nil, 48 "in1": nil, 49 "in2": nil, 50 "in1.txt": nil, 51 "in2.txt": nil, 52 "in3.txt": nil, 53 }), 54) 55 56func testGenruleBp() string { 57 return ` 58 tool { 59 name: "tool", 60 } 61 62 filegroup { 63 name: "tool_files", 64 srcs: [ 65 "tool_file1", 66 "tool_file2", 67 ], 68 } 69 70 filegroup { 71 name: "1tool_file", 72 srcs: [ 73 "tool_file1", 74 ], 75 } 76 77 filegroup { 78 name: "ins", 79 srcs: [ 80 "in1", 81 "in2", 82 ], 83 } 84 85 filegroup { 86 name: "1in", 87 srcs: [ 88 "in1", 89 ], 90 } 91 92 filegroup { 93 name: "empty", 94 } 95 ` 96} 97 98func TestGenruleCmd(t *testing.T) { 99 testcases := []struct { 100 name string 101 prop string 102 103 allowMissingDependencies bool 104 105 err string 106 expect string 107 }{ 108 { 109 name: "empty location tool", 110 prop: ` 111 tools: ["tool"], 112 out: ["out"], 113 cmd: "$(location) > $(out)", 114 `, 115 expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", 116 }, 117 { 118 name: "empty location tool2", 119 prop: ` 120 tools: [":tool"], 121 out: ["out"], 122 cmd: "$(location) > $(out)", 123 `, 124 expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", 125 }, 126 { 127 name: "empty location tool file", 128 prop: ` 129 tool_files: ["tool_file1"], 130 out: ["out"], 131 cmd: "$(location) > $(out)", 132 `, 133 expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out", 134 }, 135 { 136 name: "empty location tool file fg", 137 prop: ` 138 tool_files: [":1tool_file"], 139 out: ["out"], 140 cmd: "$(location) > $(out)", 141 `, 142 expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out", 143 }, 144 { 145 name: "empty location tool and tool file", 146 prop: ` 147 tools: ["tool"], 148 tool_files: ["tool_file1"], 149 out: ["out"], 150 cmd: "$(location) > $(out)", 151 `, 152 expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", 153 }, 154 { 155 name: "tool", 156 prop: ` 157 tools: ["tool"], 158 out: ["out"], 159 cmd: "$(location tool) > $(out)", 160 `, 161 expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", 162 }, 163 { 164 name: "tool2", 165 prop: ` 166 tools: [":tool"], 167 out: ["out"], 168 cmd: "$(location :tool) > $(out)", 169 `, 170 expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", 171 }, 172 { 173 name: "tool file", 174 prop: ` 175 tool_files: ["tool_file1"], 176 out: ["out"], 177 cmd: "$(location tool_file1) > $(out)", 178 `, 179 expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out", 180 }, 181 { 182 name: "tool file fg", 183 prop: ` 184 tool_files: [":1tool_file"], 185 out: ["out"], 186 cmd: "$(location :1tool_file) > $(out)", 187 `, 188 expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out", 189 }, 190 { 191 name: "tool files", 192 prop: ` 193 tool_files: [":tool_files"], 194 out: ["out"], 195 cmd: "$(locations :tool_files) > $(out)", 196 `, 197 expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 __SBOX_SANDBOX_DIR__/tools/src/tool_file2 > __SBOX_SANDBOX_DIR__/out/out", 198 }, 199 { 200 name: "in1", 201 prop: ` 202 srcs: ["in1"], 203 out: ["out"], 204 cmd: "cat $(in) > $(out)", 205 `, 206 expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", 207 }, 208 { 209 name: "in1 fg", 210 prop: ` 211 srcs: [":1in"], 212 out: ["out"], 213 cmd: "cat $(in) > $(out)", 214 `, 215 expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", 216 }, 217 { 218 name: "ins", 219 prop: ` 220 srcs: ["in1", "in2"], 221 out: ["out"], 222 cmd: "cat $(in) > $(out)", 223 `, 224 expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out", 225 }, 226 { 227 name: "ins fg", 228 prop: ` 229 srcs: [":ins"], 230 out: ["out"], 231 cmd: "cat $(in) > $(out)", 232 `, 233 expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out", 234 }, 235 { 236 name: "location in1", 237 prop: ` 238 srcs: ["in1"], 239 out: ["out"], 240 cmd: "cat $(location in1) > $(out)", 241 `, 242 expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", 243 }, 244 { 245 name: "location in1 fg", 246 prop: ` 247 srcs: [":1in"], 248 out: ["out"], 249 cmd: "cat $(location :1in) > $(out)", 250 `, 251 expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", 252 }, 253 { 254 name: "location ins", 255 prop: ` 256 srcs: ["in1", "in2"], 257 out: ["out"], 258 cmd: "cat $(location in1) > $(out)", 259 `, 260 expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", 261 }, 262 { 263 name: "location ins fg", 264 prop: ` 265 srcs: [":ins"], 266 out: ["out"], 267 cmd: "cat $(locations :ins) > $(out)", 268 `, 269 expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out", 270 }, 271 { 272 name: "outs", 273 prop: ` 274 out: ["out", "out2"], 275 cmd: "echo foo > $(out)", 276 `, 277 expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out __SBOX_SANDBOX_DIR__/out/out2", 278 }, 279 { 280 name: "location out", 281 prop: ` 282 out: ["out", "out2"], 283 cmd: "echo foo > $(location out2)", 284 `, 285 expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out2", 286 }, 287 { 288 name: "depfile", 289 prop: ` 290 out: ["out"], 291 depfile: true, 292 cmd: "echo foo > $(out) && touch $(depfile)", 293 `, 294 expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out && touch __SBOX_DEPFILE__", 295 }, 296 { 297 name: "gendir", 298 prop: ` 299 out: ["out"], 300 cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)", 301 `, 302 expect: "echo foo > __SBOX_SANDBOX_DIR__/out/foo && cp __SBOX_SANDBOX_DIR__/out/foo __SBOX_SANDBOX_DIR__/out/out", 303 }, 304 { 305 name: "$", 306 prop: ` 307 out: ["out"], 308 cmd: "echo $$ > $(out)", 309 `, 310 expect: "echo $ > __SBOX_SANDBOX_DIR__/out/out", 311 }, 312 313 { 314 name: "error empty location", 315 prop: ` 316 out: ["out"], 317 cmd: "$(location) > $(out)", 318 `, 319 err: "at least one `tools` or `tool_files` is required if $(location) is used", 320 }, 321 { 322 name: "error empty location no files", 323 prop: ` 324 tool_files: [":empty"], 325 out: ["out"], 326 cmd: "$(location) > $(out)", 327 `, 328 err: `default label ":empty" has no files`, 329 }, 330 { 331 name: "error empty location multiple files", 332 prop: ` 333 tool_files: [":tool_files"], 334 out: ["out"], 335 cmd: "$(location) > $(out)", 336 `, 337 err: `default label ":tool_files" has multiple files`, 338 }, 339 { 340 name: "error location", 341 prop: ` 342 out: ["out"], 343 cmd: "echo foo > $(location missing)", 344 `, 345 err: `unknown location label "missing" is not in srcs, out, tools or tool_files.`, 346 }, 347 { 348 name: "error locations", 349 prop: ` 350 out: ["out"], 351 cmd: "echo foo > $(locations missing)", 352 `, 353 err: `unknown locations label "missing" is not in srcs, out, tools or tool_files`, 354 }, 355 { 356 name: "error location no files", 357 prop: ` 358 out: ["out"], 359 srcs: [":empty"], 360 cmd: "echo $(location :empty) > $(out)", 361 `, 362 err: `label ":empty" has no files`, 363 }, 364 { 365 name: "error locations no files", 366 prop: ` 367 out: ["out"], 368 srcs: [":empty"], 369 cmd: "echo $(locations :empty) > $(out)", 370 `, 371 err: `label ":empty" has no files`, 372 }, 373 { 374 name: "error location multiple files", 375 prop: ` 376 out: ["out"], 377 srcs: [":ins"], 378 cmd: "echo $(location :ins) > $(out)", 379 `, 380 err: `label ":ins" has multiple files`, 381 }, 382 { 383 name: "error variable", 384 prop: ` 385 out: ["out"], 386 srcs: ["in1"], 387 cmd: "echo $(foo) > $(out)", 388 `, 389 err: `unknown variable '$(foo)'`, 390 }, 391 { 392 name: "error depfile", 393 prop: ` 394 out: ["out"], 395 cmd: "echo foo > $(out) && touch $(depfile)", 396 `, 397 err: "$(depfile) used without depfile property", 398 }, 399 { 400 name: "error no depfile", 401 prop: ` 402 out: ["out"], 403 depfile: true, 404 cmd: "echo foo > $(out)", 405 `, 406 err: "specified depfile=true but did not include a reference to '${depfile}' in cmd", 407 }, 408 { 409 name: "error no out", 410 prop: ` 411 cmd: "echo foo > $(out)", 412 `, 413 err: "must have at least one output file", 414 }, 415 { 416 name: "srcs allow missing dependencies", 417 prop: ` 418 srcs: [":missing"], 419 out: ["out"], 420 cmd: "cat $(location :missing) > $(out)", 421 `, 422 423 allowMissingDependencies: true, 424 425 expect: "cat '***missing srcs :missing***' > __SBOX_SANDBOX_DIR__/out/out", 426 }, 427 { 428 name: "tool allow missing dependencies", 429 prop: ` 430 tools: [":missing"], 431 out: ["out"], 432 cmd: "$(location :missing) > $(out)", 433 `, 434 435 allowMissingDependencies: true, 436 437 expect: "'***missing tool :missing***' > __SBOX_SANDBOX_DIR__/out/out", 438 }, 439 } 440 441 for _, test := range testcases { 442 t.Run(test.name, func(t *testing.T) { 443 bp := "genrule {\n" 444 bp += "name: \"gen\",\n" 445 bp += test.prop 446 bp += "}\n" 447 448 var expectedErrors []string 449 if test.err != "" { 450 expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err)) 451 } 452 453 result := android.GroupFixturePreparers( 454 prepareForGenRuleTest, 455 android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { 456 variables.Allow_missing_dependencies = proptools.BoolPtr(test.allowMissingDependencies) 457 }), 458 android.FixtureModifyContext(func(ctx *android.TestContext) { 459 ctx.SetAllowMissingDependencies(test.allowMissingDependencies) 460 }), 461 ). 462 ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)). 463 RunTestWithBp(t, testGenruleBp()+bp) 464 465 if expectedErrors != nil { 466 return 467 } 468 469 gen := result.Module("gen", "").(*Module) 470 android.AssertStringEquals(t, "raw commands", test.expect, gen.rawCommands[0]) 471 }) 472 } 473} 474 475func TestGenruleHashInputs(t *testing.T) { 476 477 // The basic idea here is to verify that the sbox command (which is 478 // in the Command field of the generate rule) contains a hash of the 479 // inputs, but only if $(in) is not referenced in the genrule cmd 480 // property. 481 482 // By including a hash of the inputs, we cause the rule to re-run if 483 // the list of inputs changes (because the sbox command changes). 484 485 // However, if the genrule cmd property already contains $(in), then 486 // the dependency is already expressed, so we don't need to include the 487 // hash in that case. 488 489 bp := ` 490 genrule { 491 name: "hash0", 492 srcs: ["in1.txt", "in2.txt"], 493 out: ["out"], 494 cmd: "echo foo > $(out)", 495 } 496 genrule { 497 name: "hash1", 498 srcs: ["*.txt"], 499 out: ["out"], 500 cmd: "echo bar > $(out)", 501 } 502 genrule { 503 name: "hash2", 504 srcs: ["*.txt"], 505 out: ["out"], 506 cmd: "echo $(in) > $(out)", 507 } 508 ` 509 testcases := []struct { 510 name string 511 expectedHash string 512 }{ 513 { 514 name: "hash0", 515 // sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum 516 expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d", 517 }, 518 { 519 name: "hash1", 520 // sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum 521 expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45", 522 }, 523 { 524 name: "hash2", 525 // sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum 526 expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45", 527 }, 528 } 529 530 result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp) 531 532 for _, test := range testcases { 533 t.Run(test.name, func(t *testing.T) { 534 gen := result.ModuleForTests(test.name, "") 535 manifest := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto")) 536 hash := manifest.Commands[0].GetInputHash() 537 538 android.AssertStringEquals(t, "hash", test.expectedHash, hash) 539 }) 540 } 541} 542 543func TestGenSrcs(t *testing.T) { 544 testcases := []struct { 545 name string 546 prop string 547 548 allowMissingDependencies bool 549 550 err string 551 cmds []string 552 deps []string 553 files []string 554 }{ 555 { 556 name: "gensrcs", 557 prop: ` 558 tools: ["tool"], 559 srcs: ["in1.txt", "in2.txt"], 560 cmd: "$(location) $(in) > $(out)", 561 `, 562 cmds: []string{ 563 "bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'", 564 }, 565 deps: []string{ 566 "out/soong/.intermediates/gen/gen/gensrcs/in1.h", 567 "out/soong/.intermediates/gen/gen/gensrcs/in2.h", 568 }, 569 files: []string{ 570 "out/soong/.intermediates/gen/gen/gensrcs/in1.h", 571 "out/soong/.intermediates/gen/gen/gensrcs/in2.h", 572 }, 573 }, 574 { 575 name: "shards", 576 prop: ` 577 tools: ["tool"], 578 srcs: ["in1.txt", "in2.txt", "in3.txt"], 579 cmd: "$(location) $(in) > $(out)", 580 shard_size: 2, 581 `, 582 cmds: []string{ 583 "bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'", 584 "bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in3.txt > __SBOX_SANDBOX_DIR__/out/in3.h'", 585 }, 586 deps: []string{ 587 "out/soong/.intermediates/gen/gen/gensrcs/in1.h", 588 "out/soong/.intermediates/gen/gen/gensrcs/in2.h", 589 "out/soong/.intermediates/gen/gen/gensrcs/in3.h", 590 }, 591 files: []string{ 592 "out/soong/.intermediates/gen/gen/gensrcs/in1.h", 593 "out/soong/.intermediates/gen/gen/gensrcs/in2.h", 594 "out/soong/.intermediates/gen/gen/gensrcs/in3.h", 595 }, 596 }, 597 } 598 599 for _, test := range testcases { 600 t.Run(test.name, func(t *testing.T) { 601 bp := "gensrcs {\n" 602 bp += `name: "gen",` + "\n" 603 bp += `output_extension: "h",` + "\n" 604 bp += test.prop 605 bp += "}\n" 606 607 var expectedErrors []string 608 if test.err != "" { 609 expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err)) 610 } 611 612 result := prepareForGenRuleTest. 613 ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)). 614 RunTestWithBp(t, testGenruleBp()+bp) 615 616 if expectedErrors != nil { 617 return 618 } 619 620 gen := result.Module("gen", "").(*Module) 621 android.AssertDeepEquals(t, "cmd", test.cmds, gen.rawCommands) 622 623 android.AssertPathsRelativeToTopEquals(t, "deps", test.deps, gen.outputDeps) 624 625 android.AssertPathsRelativeToTopEquals(t, "files", test.files, gen.outputFiles) 626 }) 627 } 628} 629 630func TestGensrcsBuildBrokenDepfile(t *testing.T) { 631 tests := []struct { 632 name string 633 prop string 634 BuildBrokenDepfile *bool 635 err string 636 }{ 637 { 638 name: `error when BuildBrokenDepfile is set to false`, 639 prop: ` 640 depfile: true, 641 cmd: "cat $(in) > $(out) && cat $(depfile)", 642 `, 643 BuildBrokenDepfile: proptools.BoolPtr(false), 644 err: "depfile: Deprecated to ensure the module type is convertible to Bazel", 645 }, 646 { 647 name: `error when BuildBrokenDepfile is not set`, 648 prop: ` 649 depfile: true, 650 cmd: "cat $(in) > $(out) && cat $(depfile)", 651 `, 652 err: "depfile: Deprecated to ensure the module type is convertible to Bazel.", 653 }, 654 { 655 name: `no error when BuildBrokenDepfile is explicitly set to true`, 656 prop: ` 657 depfile: true, 658 cmd: "cat $(in) > $(out) && cat $(depfile)", 659 `, 660 BuildBrokenDepfile: proptools.BoolPtr(true), 661 }, 662 { 663 name: `no error if depfile is not set`, 664 prop: ` 665 cmd: "cat $(in) > $(out)", 666 `, 667 }, 668 } 669 for _, test := range tests { 670 t.Run(test.name, func(t *testing.T) { 671 bp := fmt.Sprintf(` 672 gensrcs { 673 name: "foo", 674 srcs: ["data.txt"], 675 %s 676 }`, test.prop) 677 678 var expectedErrors []string 679 if test.err != "" { 680 expectedErrors = append(expectedErrors, test.err) 681 } 682 android.GroupFixturePreparers( 683 prepareForGenRuleTest, 684 android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { 685 if test.BuildBrokenDepfile != nil { 686 variables.BuildBrokenDepfile = test.BuildBrokenDepfile 687 } 688 }), 689 ). 690 ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)). 691 RunTestWithBp(t, bp) 692 }) 693 694 } 695} 696 697func TestGenruleDefaults(t *testing.T) { 698 bp := ` 699 genrule_defaults { 700 name: "gen_defaults1", 701 cmd: "cp $(in) $(out)", 702 } 703 704 genrule_defaults { 705 name: "gen_defaults2", 706 srcs: ["in1"], 707 } 708 709 genrule { 710 name: "gen", 711 out: ["out"], 712 defaults: ["gen_defaults1", "gen_defaults2"], 713 } 714 ` 715 716 result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp) 717 718 gen := result.Module("gen", "").(*Module) 719 720 expectedCmd := "cp in1 __SBOX_SANDBOX_DIR__/out/out" 721 android.AssertStringEquals(t, "cmd", expectedCmd, gen.rawCommands[0]) 722 723 expectedSrcs := []string{"in1"} 724 android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.Srcs) 725} 726 727func TestGenruleAllowMissingDependencies(t *testing.T) { 728 bp := ` 729 output { 730 name: "disabled", 731 enabled: false, 732 } 733 734 genrule { 735 name: "gen", 736 srcs: [ 737 ":disabled", 738 ], 739 out: ["out"], 740 cmd: "cat $(in) > $(out)", 741 } 742 ` 743 result := android.GroupFixturePreparers( 744 prepareForGenRuleTest, 745 android.FixtureModifyConfigAndContext( 746 func(config android.Config, ctx *android.TestContext) { 747 config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true) 748 ctx.SetAllowMissingDependencies(true) 749 })).RunTestWithBp(t, bp) 750 751 gen := result.ModuleForTests("gen", "").Output("out") 752 if gen.Rule != android.ErrorRule { 753 t.Errorf("Expected missing dependency error rule for gen, got %q", gen.Rule.String()) 754 } 755} 756 757func TestGenruleOutputFiles(t *testing.T) { 758 bp := ` 759 genrule { 760 name: "gen", 761 out: ["foo", "sub/bar"], 762 cmd: "echo foo > $(location foo) && echo bar > $(location sub/bar)", 763 } 764 use_source { 765 name: "gen_foo", 766 srcs: [":gen{foo}"], 767 } 768 use_source { 769 name: "gen_bar", 770 srcs: [":gen{sub/bar}"], 771 } 772 use_source { 773 name: "gen_all", 774 srcs: [":gen"], 775 } 776 ` 777 778 result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp) 779 android.AssertPathsRelativeToTopEquals(t, 780 "genrule.tag with output", 781 []string{"out/soong/.intermediates/gen/gen/foo"}, 782 result.ModuleForTests("gen_foo", "").Module().(*useSource).srcs) 783 android.AssertPathsRelativeToTopEquals(t, 784 "genrule.tag with output in subdir", 785 []string{"out/soong/.intermediates/gen/gen/sub/bar"}, 786 result.ModuleForTests("gen_bar", "").Module().(*useSource).srcs) 787 android.AssertPathsRelativeToTopEquals(t, 788 "genrule.tag with all", 789 []string{"out/soong/.intermediates/gen/gen/foo", "out/soong/.intermediates/gen/gen/sub/bar"}, 790 result.ModuleForTests("gen_all", "").Module().(*useSource).srcs) 791} 792 793func TestGenSrcsWithNonRootAndroidBpOutputFiles(t *testing.T) { 794 result := android.GroupFixturePreparers( 795 prepareForGenRuleTest, 796 android.FixtureMergeMockFs(android.MockFS{ 797 "external-protos/path/Android.bp": []byte(` 798 filegroup { 799 name: "external-protos", 800 srcs: ["baz/baz.proto", "bar.proto"], 801 } 802 `), 803 "package-dir/Android.bp": []byte(` 804 gensrcs { 805 name: "module-name", 806 cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)", 807 srcs: [ 808 "src/foo.proto", 809 ":external-protos", 810 ], 811 output_extension: "proto.h", 812 } 813 `), 814 }), 815 ).RunTest(t) 816 817 exportedIncludeDir := "out/soong/.intermediates/package-dir/module-name/gen/gensrcs" 818 gen := result.Module("module-name", "").(*Module) 819 820 android.AssertPathsRelativeToTopEquals( 821 t, 822 "include path", 823 []string{exportedIncludeDir}, 824 gen.exportedIncludeDirs, 825 ) 826 android.AssertPathsRelativeToTopEquals( 827 t, 828 "files", 829 []string{ 830 exportedIncludeDir + "/package-dir/src/foo.proto.h", 831 exportedIncludeDir + "/external-protos/path/baz/baz.proto.h", 832 exportedIncludeDir + "/external-protos/path/bar.proto.h", 833 }, 834 gen.outputFiles, 835 ) 836} 837 838func TestGenSrcsWithSrcsFromExternalPackage(t *testing.T) { 839 bp := ` 840 gensrcs { 841 name: "module-name", 842 cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)", 843 srcs: [ 844 ":external-protos", 845 ], 846 output_extension: "proto.h", 847 } 848 ` 849 result := android.GroupFixturePreparers( 850 prepareForGenRuleTest, 851 android.FixtureMergeMockFs(android.MockFS{ 852 "external-protos/path/Android.bp": []byte(` 853 filegroup { 854 name: "external-protos", 855 srcs: ["foo/foo.proto", "bar.proto"], 856 } 857 `), 858 }), 859 ).RunTestWithBp(t, bp) 860 861 exportedIncludeDir := "out/soong/.intermediates/module-name/gen/gensrcs" 862 gen := result.Module("module-name", "").(*Module) 863 864 android.AssertPathsRelativeToTopEquals( 865 t, 866 "include path", 867 []string{exportedIncludeDir}, 868 gen.exportedIncludeDirs, 869 ) 870 android.AssertPathsRelativeToTopEquals( 871 t, 872 "files", 873 []string{ 874 exportedIncludeDir + "/external-protos/path/foo/foo.proto.h", 875 exportedIncludeDir + "/external-protos/path/bar.proto.h", 876 }, 877 gen.outputFiles, 878 ) 879} 880 881func TestPrebuiltTool(t *testing.T) { 882 testcases := []struct { 883 name string 884 bp string 885 expectedToolName string 886 }{ 887 { 888 name: "source only", 889 bp: ` 890 tool { name: "tool" } 891 `, 892 expectedToolName: "bin/tool", 893 }, 894 { 895 name: "prebuilt only", 896 bp: ` 897 prebuilt_tool { name: "tool" } 898 `, 899 expectedToolName: "prebuilt_bin/tool", 900 }, 901 { 902 name: "source preferred", 903 bp: ` 904 tool { name: "tool" } 905 prebuilt_tool { name: "tool" } 906 `, 907 expectedToolName: "bin/tool", 908 }, 909 { 910 name: "prebuilt preferred", 911 bp: ` 912 tool { name: "tool" } 913 prebuilt_tool { name: "tool", prefer: true } 914 `, 915 expectedToolName: "prebuilt_bin/prebuilt_tool", 916 }, 917 { 918 name: "source disabled", 919 bp: ` 920 tool { name: "tool", enabled: false } 921 prebuilt_tool { name: "tool" } 922 `, 923 expectedToolName: "prebuilt_bin/prebuilt_tool", 924 }, 925 } 926 927 for _, test := range testcases { 928 t.Run(test.name, func(t *testing.T) { 929 result := prepareForGenRuleTest.RunTestWithBp(t, test.bp+` 930 genrule { 931 name: "gen", 932 tools: ["tool"], 933 out: ["foo"], 934 cmd: "$(location tool)", 935 } 936 `) 937 gen := result.Module("gen", "").(*Module) 938 expectedCmd := "__SBOX_SANDBOX_DIR__/tools/out/" + test.expectedToolName 939 android.AssertStringEquals(t, "command", expectedCmd, gen.rawCommands[0]) 940 }) 941 } 942} 943 944func TestGenruleWithBazel(t *testing.T) { 945 bp := ` 946 genrule { 947 name: "foo", 948 out: ["one.txt", "two.txt"], 949 bazel_module: { label: "//foo/bar:bar" }, 950 } 951 ` 952 953 result := android.GroupFixturePreparers( 954 prepareForGenRuleTest, android.FixtureModifyConfig(func(config android.Config) { 955 config.BazelContext = android.MockBazelContext{ 956 OutputBaseDir: "outputbase", 957 LabelToOutputFiles: map[string][]string{ 958 "//foo/bar:bar": []string{"bazelone.txt", "bazeltwo.txt"}}} 959 })).RunTestWithBp(t, testGenruleBp()+bp) 960 961 gen := result.Module("foo", "").(*Module) 962 963 expectedOutputFiles := []string{"outputbase/execroot/__main__/bazelone.txt", 964 "outputbase/execroot/__main__/bazeltwo.txt"} 965 android.AssertDeepEquals(t, "output files", expectedOutputFiles, gen.outputFiles.Strings()) 966 android.AssertDeepEquals(t, "output deps", expectedOutputFiles, gen.outputDeps.Strings()) 967} 968 969func TestGenruleWithGlobPaths(t *testing.T) { 970 testcases := []struct { 971 name string 972 bp string 973 additionalFiles android.MockFS 974 expectedCmd string 975 }{ 976 { 977 name: "single file in directory with $ sign", 978 bp: ` 979 genrule { 980 name: "gen", 981 srcs: ["inn*.txt"], 982 out: ["out.txt"], 983 cmd: "cp $(in) $(out)", 984 } 985 `, 986 additionalFiles: android.MockFS{"inn$1.txt": nil}, 987 expectedCmd: "cp 'inn$1.txt' __SBOX_SANDBOX_DIR__/out/out.txt", 988 }, 989 { 990 name: "multiple file in directory with $ sign", 991 bp: ` 992 genrule { 993 name: "gen", 994 srcs: ["inn*.txt"], 995 out: ["."], 996 cmd: "cp $(in) $(out)", 997 } 998 `, 999 additionalFiles: android.MockFS{"inn$1.txt": nil, "inn$2.txt": nil}, 1000 expectedCmd: "cp 'inn$1.txt' 'inn$2.txt' __SBOX_SANDBOX_DIR__/out", 1001 }, 1002 { 1003 name: "file in directory with other shell unsafe character", 1004 bp: ` 1005 genrule { 1006 name: "gen", 1007 srcs: ["inn*.txt"], 1008 out: ["out.txt"], 1009 cmd: "cp $(in) $(out)", 1010 } 1011 `, 1012 additionalFiles: android.MockFS{"inn@1.txt": nil}, 1013 expectedCmd: "cp 'inn@1.txt' __SBOX_SANDBOX_DIR__/out/out.txt", 1014 }, 1015 { 1016 name: "glob location param with filepath containing $", 1017 bp: ` 1018 genrule { 1019 name: "gen", 1020 srcs: ["**/inn*"], 1021 out: ["."], 1022 cmd: "cp $(in) $(location **/inn*)", 1023 } 1024 `, 1025 additionalFiles: android.MockFS{"a/inn$1.txt": nil}, 1026 expectedCmd: "cp 'a/inn$1.txt' 'a/inn$1.txt'", 1027 }, 1028 { 1029 name: "glob locations param with filepath containing $", 1030 bp: ` 1031 genrule { 1032 name: "gen", 1033 tool_files: ["**/inn*"], 1034 out: ["out.txt"], 1035 cmd: "cp $(locations **/inn*) $(out)", 1036 } 1037 `, 1038 additionalFiles: android.MockFS{"a/inn$1.txt": nil}, 1039 expectedCmd: "cp '__SBOX_SANDBOX_DIR__/tools/src/a/inn$1.txt' __SBOX_SANDBOX_DIR__/out/out.txt", 1040 }, 1041 } 1042 1043 for _, test := range testcases { 1044 t.Run(test.name, func(t *testing.T) { 1045 result := android.GroupFixturePreparers( 1046 prepareForGenRuleTest, 1047 android.FixtureMergeMockFs(test.additionalFiles), 1048 ).RunTestWithBp(t, test.bp) 1049 gen := result.Module("gen", "").(*Module) 1050 android.AssertStringEquals(t, "command", test.expectedCmd, gen.rawCommands[0]) 1051 }) 1052 } 1053} 1054 1055type testTool struct { 1056 android.ModuleBase 1057 outputFile android.Path 1058} 1059 1060func toolFactory() android.Module { 1061 module := &testTool{} 1062 android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) 1063 return module 1064} 1065 1066func (t *testTool) GenerateAndroidBuildActions(ctx android.ModuleContext) { 1067 t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName())) 1068} 1069 1070func (t *testTool) HostToolPath() android.OptionalPath { 1071 return android.OptionalPathForPath(t.outputFile) 1072} 1073 1074type prebuiltTestTool struct { 1075 android.ModuleBase 1076 prebuilt android.Prebuilt 1077 testTool 1078} 1079 1080func (p *prebuiltTestTool) Name() string { 1081 return p.prebuilt.Name(p.ModuleBase.Name()) 1082} 1083 1084func (p *prebuiltTestTool) Prebuilt() *android.Prebuilt { 1085 return &p.prebuilt 1086} 1087 1088func (t *prebuiltTestTool) GenerateAndroidBuildActions(ctx android.ModuleContext) { 1089 t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "prebuilt_bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName())) 1090} 1091 1092func prebuiltToolFactory() android.Module { 1093 module := &prebuiltTestTool{} 1094 android.InitPrebuiltModuleWithoutSrcs(module) 1095 android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) 1096 return module 1097} 1098 1099var _ android.HostToolProvider = (*testTool)(nil) 1100var _ android.HostToolProvider = (*prebuiltTestTool)(nil) 1101 1102type testOutputProducer struct { 1103 android.ModuleBase 1104 outputFile android.Path 1105} 1106 1107func outputProducerFactory() android.Module { 1108 module := &testOutputProducer{} 1109 android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) 1110 return module 1111} 1112 1113func (t *testOutputProducer) GenerateAndroidBuildActions(ctx android.ModuleContext) { 1114 t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName())) 1115} 1116 1117func (t *testOutputProducer) OutputFiles(tag string) (android.Paths, error) { 1118 return android.Paths{t.outputFile}, nil 1119} 1120 1121var _ android.OutputFileProducer = (*testOutputProducer)(nil) 1122 1123type useSource struct { 1124 android.ModuleBase 1125 props struct { 1126 Srcs []string `android:"path"` 1127 } 1128 srcs android.Paths 1129} 1130 1131func (s *useSource) GenerateAndroidBuildActions(ctx android.ModuleContext) { 1132 s.srcs = android.PathsForModuleSrc(ctx, s.props.Srcs) 1133} 1134 1135func useSourceFactory() android.Module { 1136 module := &useSource{} 1137 module.AddProperties(&module.props) 1138 android.InitAndroidModule(module) 1139 return module 1140} 1141