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