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