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 15 package genrule 16 17 import ( 18 "io/ioutil" 19 "os" 20 "reflect" 21 "strings" 22 "testing" 23 24 "android/soong/android" 25 26 "github.com/google/blueprint/proptools" 27 ) 28 29 var buildDir string 30 31 func setUp() { 32 var err error 33 buildDir, err = ioutil.TempDir("", "genrule_test") 34 if err != nil { 35 panic(err) 36 } 37 } 38 39 func tearDown() { 40 os.RemoveAll(buildDir) 41 } 42 43 func TestMain(m *testing.M) { 44 run := func() int { 45 setUp() 46 defer tearDown() 47 48 return m.Run() 49 } 50 51 os.Exit(run()) 52 } 53 54 func testContext(config android.Config, bp string, 55 fs map[string][]byte) *android.TestContext { 56 57 ctx := android.NewTestArchContext() 58 ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(android.FileGroupFactory)) 59 ctx.RegisterModuleType("genrule", android.ModuleFactoryAdaptor(GenRuleFactory)) 60 ctx.RegisterModuleType("genrule_defaults", android.ModuleFactoryAdaptor(defaultsFactory)) 61 ctx.RegisterModuleType("tool", android.ModuleFactoryAdaptor(toolFactory)) 62 ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) 63 ctx.Register() 64 65 bp += ` 66 tool { 67 name: "tool", 68 } 69 70 filegroup { 71 name: "tool_files", 72 srcs: [ 73 "tool_file1", 74 "tool_file2", 75 ], 76 } 77 78 filegroup { 79 name: "1tool_file", 80 srcs: [ 81 "tool_file1", 82 ], 83 } 84 85 filegroup { 86 name: "ins", 87 srcs: [ 88 "in1", 89 "in2", 90 ], 91 } 92 93 filegroup { 94 name: "1in", 95 srcs: [ 96 "in1", 97 ], 98 } 99 100 filegroup { 101 name: "empty", 102 } 103 ` 104 105 mockFS := map[string][]byte{ 106 "Android.bp": []byte(bp), 107 "tool": nil, 108 "tool_file1": nil, 109 "tool_file2": nil, 110 "in1": nil, 111 "in2": nil, 112 } 113 114 for k, v := range fs { 115 mockFS[k] = v 116 } 117 118 ctx.MockFileSystem(mockFS) 119 120 return ctx 121 } 122 123 func TestGenruleCmd(t *testing.T) { 124 testcases := []struct { 125 name string 126 prop string 127 128 allowMissingDependencies bool 129 130 err string 131 expect string 132 }{ 133 { 134 name: "empty location tool", 135 prop: ` 136 tools: ["tool"], 137 out: ["out"], 138 cmd: "$(location) > $(out)", 139 `, 140 expect: "out/tool > __SBOX_OUT_FILES__", 141 }, 142 { 143 name: "empty location tool2", 144 prop: ` 145 tools: [":tool"], 146 out: ["out"], 147 cmd: "$(location) > $(out)", 148 `, 149 expect: "out/tool > __SBOX_OUT_FILES__", 150 }, 151 { 152 name: "empty location tool file", 153 prop: ` 154 tool_files: ["tool_file1"], 155 out: ["out"], 156 cmd: "$(location) > $(out)", 157 `, 158 expect: "tool_file1 > __SBOX_OUT_FILES__", 159 }, 160 { 161 name: "empty location tool file fg", 162 prop: ` 163 tool_files: [":1tool_file"], 164 out: ["out"], 165 cmd: "$(location) > $(out)", 166 `, 167 expect: "tool_file1 > __SBOX_OUT_FILES__", 168 }, 169 { 170 name: "empty location tool and tool file", 171 prop: ` 172 tools: ["tool"], 173 tool_files: ["tool_file1"], 174 out: ["out"], 175 cmd: "$(location) > $(out)", 176 `, 177 expect: "out/tool > __SBOX_OUT_FILES__", 178 }, 179 { 180 name: "tool", 181 prop: ` 182 tools: ["tool"], 183 out: ["out"], 184 cmd: "$(location tool) > $(out)", 185 `, 186 expect: "out/tool > __SBOX_OUT_FILES__", 187 }, 188 { 189 name: "tool2", 190 prop: ` 191 tools: [":tool"], 192 out: ["out"], 193 cmd: "$(location :tool) > $(out)", 194 `, 195 expect: "out/tool > __SBOX_OUT_FILES__", 196 }, 197 { 198 name: "tool file", 199 prop: ` 200 tool_files: ["tool_file1"], 201 out: ["out"], 202 cmd: "$(location tool_file1) > $(out)", 203 `, 204 expect: "tool_file1 > __SBOX_OUT_FILES__", 205 }, 206 { 207 name: "tool file fg", 208 prop: ` 209 tool_files: [":1tool_file"], 210 out: ["out"], 211 cmd: "$(location :1tool_file) > $(out)", 212 `, 213 expect: "tool_file1 > __SBOX_OUT_FILES__", 214 }, 215 { 216 name: "tool files", 217 prop: ` 218 tool_files: [":tool_files"], 219 out: ["out"], 220 cmd: "$(locations :tool_files) > $(out)", 221 `, 222 expect: "tool_file1 tool_file2 > __SBOX_OUT_FILES__", 223 }, 224 { 225 name: "in1", 226 prop: ` 227 srcs: ["in1"], 228 out: ["out"], 229 cmd: "cat $(in) > $(out)", 230 `, 231 expect: "cat ${in} > __SBOX_OUT_FILES__", 232 }, 233 { 234 name: "in1 fg", 235 prop: ` 236 srcs: [":1in"], 237 out: ["out"], 238 cmd: "cat $(in) > $(out)", 239 `, 240 expect: "cat ${in} > __SBOX_OUT_FILES__", 241 }, 242 { 243 name: "ins", 244 prop: ` 245 srcs: ["in1", "in2"], 246 out: ["out"], 247 cmd: "cat $(in) > $(out)", 248 `, 249 expect: "cat ${in} > __SBOX_OUT_FILES__", 250 }, 251 { 252 name: "ins fg", 253 prop: ` 254 srcs: [":ins"], 255 out: ["out"], 256 cmd: "cat $(in) > $(out)", 257 `, 258 expect: "cat ${in} > __SBOX_OUT_FILES__", 259 }, 260 { 261 name: "location in1", 262 prop: ` 263 srcs: ["in1"], 264 out: ["out"], 265 cmd: "cat $(location in1) > $(out)", 266 `, 267 expect: "cat in1 > __SBOX_OUT_FILES__", 268 }, 269 { 270 name: "location in1 fg", 271 prop: ` 272 srcs: [":1in"], 273 out: ["out"], 274 cmd: "cat $(location :1in) > $(out)", 275 `, 276 expect: "cat in1 > __SBOX_OUT_FILES__", 277 }, 278 { 279 name: "location ins", 280 prop: ` 281 srcs: ["in1", "in2"], 282 out: ["out"], 283 cmd: "cat $(location in1) > $(out)", 284 `, 285 expect: "cat in1 > __SBOX_OUT_FILES__", 286 }, 287 { 288 name: "location ins fg", 289 prop: ` 290 srcs: [":ins"], 291 out: ["out"], 292 cmd: "cat $(locations :ins) > $(out)", 293 `, 294 expect: "cat in1 in2 > __SBOX_OUT_FILES__", 295 }, 296 { 297 name: "outs", 298 prop: ` 299 out: ["out", "out2"], 300 cmd: "echo foo > $(out)", 301 `, 302 expect: "echo foo > __SBOX_OUT_FILES__", 303 }, 304 { 305 name: "location out", 306 prop: ` 307 out: ["out", "out2"], 308 cmd: "echo foo > $(location out2)", 309 `, 310 expect: "echo foo > __SBOX_OUT_DIR__/out2", 311 }, 312 { 313 name: "depfile", 314 prop: ` 315 out: ["out"], 316 depfile: true, 317 cmd: "echo foo > $(out) && touch $(depfile)", 318 `, 319 expect: "echo foo > __SBOX_OUT_FILES__ && touch __SBOX_DEPFILE__", 320 }, 321 { 322 name: "gendir", 323 prop: ` 324 out: ["out"], 325 cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)", 326 `, 327 expect: "echo foo > __SBOX_OUT_DIR__/foo && cp __SBOX_OUT_DIR__/foo __SBOX_OUT_FILES__", 328 }, 329 330 { 331 name: "error empty location", 332 prop: ` 333 out: ["out"], 334 cmd: "$(location) > $(out)", 335 `, 336 err: "at least one `tools` or `tool_files` is required if $(location) is used", 337 }, 338 { 339 name: "error empty location no files", 340 prop: ` 341 tool_files: [":empty"], 342 out: ["out"], 343 cmd: "$(location) > $(out)", 344 `, 345 err: `default label ":empty" has no files`, 346 }, 347 { 348 name: "error empty location multiple files", 349 prop: ` 350 tool_files: [":tool_files"], 351 out: ["out"], 352 cmd: "$(location) > $(out)", 353 `, 354 err: `default label ":tool_files" has multiple files`, 355 }, 356 { 357 name: "error location", 358 prop: ` 359 out: ["out"], 360 cmd: "echo foo > $(location missing)", 361 `, 362 err: `unknown location label "missing"`, 363 }, 364 { 365 name: "error locations", 366 prop: ` 367 out: ["out"], 368 cmd: "echo foo > $(locations missing)", 369 `, 370 err: `unknown locations label "missing"`, 371 }, 372 { 373 name: "error location no files", 374 prop: ` 375 out: ["out"], 376 srcs: [":empty"], 377 cmd: "echo $(location :empty) > $(out)", 378 `, 379 err: `label ":empty" has no files`, 380 }, 381 { 382 name: "error locations no files", 383 prop: ` 384 out: ["out"], 385 srcs: [":empty"], 386 cmd: "echo $(locations :empty) > $(out)", 387 `, 388 err: `label ":empty" has no files`, 389 }, 390 { 391 name: "error location multiple files", 392 prop: ` 393 out: ["out"], 394 srcs: [":ins"], 395 cmd: "echo $(location :ins) > $(out)", 396 `, 397 err: `label ":ins" has multiple files`, 398 }, 399 { 400 name: "error variable", 401 prop: ` 402 out: ["out"], 403 srcs: ["in1"], 404 cmd: "echo $(foo) > $(out)", 405 `, 406 err: `unknown variable '$(foo)'`, 407 }, 408 { 409 name: "error depfile", 410 prop: ` 411 out: ["out"], 412 cmd: "echo foo > $(out) && touch $(depfile)", 413 `, 414 err: "$(depfile) used without depfile property", 415 }, 416 { 417 name: "error no depfile", 418 prop: ` 419 out: ["out"], 420 depfile: true, 421 cmd: "echo foo > $(out)", 422 `, 423 err: "specified depfile=true but did not include a reference to '${depfile}' in cmd", 424 }, 425 { 426 name: "error no out", 427 prop: ` 428 cmd: "echo foo > $(out)", 429 `, 430 err: "must have at least one output file", 431 }, 432 { 433 name: "srcs allow missing dependencies", 434 prop: ` 435 srcs: [":missing"], 436 out: ["out"], 437 cmd: "cat $(location :missing) > $(out)", 438 `, 439 440 allowMissingDependencies: true, 441 442 expect: "cat ***missing srcs :missing*** > __SBOX_OUT_FILES__", 443 }, 444 { 445 name: "tool allow missing dependencies", 446 prop: ` 447 tools: [":missing"], 448 out: ["out"], 449 cmd: "$(location :missing) > $(out)", 450 `, 451 452 allowMissingDependencies: true, 453 454 expect: "***missing tool :missing*** > __SBOX_OUT_FILES__", 455 }, 456 } 457 458 for _, test := range testcases { 459 t.Run(test.name, func(t *testing.T) { 460 config := android.TestArchConfig(buildDir, nil) 461 bp := "genrule {\n" 462 bp += "name: \"gen\",\n" 463 bp += test.prop 464 bp += "}\n" 465 466 config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(test.allowMissingDependencies) 467 468 ctx := testContext(config, bp, nil) 469 ctx.SetAllowMissingDependencies(test.allowMissingDependencies) 470 471 _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) 472 if errs == nil { 473 _, errs = ctx.PrepareBuildActions(config) 474 } 475 if errs == nil && test.err != "" { 476 t.Fatalf("want error %q, got no error", test.err) 477 } else if errs != nil && test.err == "" { 478 android.FailIfErrored(t, errs) 479 } else if test.err != "" { 480 if len(errs) != 1 { 481 t.Errorf("want 1 error, got %d errors:", len(errs)) 482 for _, err := range errs { 483 t.Errorf(" %s", err.Error()) 484 } 485 t.FailNow() 486 } 487 if !strings.Contains(errs[0].Error(), test.err) { 488 t.Fatalf("want %q, got %q", test.err, errs[0].Error()) 489 } 490 return 491 } 492 493 gen := ctx.ModuleForTests("gen", "").Module().(*Module) 494 if g, w := gen.rawCommand, "'"+test.expect+"'"; w != g { 495 t.Errorf("want %q, got %q", w, g) 496 } 497 }) 498 } 499 500 } 501 502 func TestGenruleDefaults(t *testing.T) { 503 config := android.TestArchConfig(buildDir, nil) 504 bp := ` 505 genrule_defaults { 506 name: "gen_defaults1", 507 cmd: "cp $(in) $(out)", 508 } 509 510 genrule_defaults { 511 name: "gen_defaults2", 512 srcs: ["in1"], 513 } 514 515 genrule { 516 name: "gen", 517 out: ["out"], 518 defaults: ["gen_defaults1", "gen_defaults2"], 519 } 520 ` 521 ctx := testContext(config, bp, nil) 522 _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) 523 if errs == nil { 524 _, errs = ctx.PrepareBuildActions(config) 525 } 526 if errs != nil { 527 t.Fatal(errs) 528 } 529 gen := ctx.ModuleForTests("gen", "").Module().(*Module) 530 531 expectedCmd := "'cp ${in} __SBOX_OUT_FILES__'" 532 if gen.rawCommand != expectedCmd { 533 t.Errorf("Expected cmd: %q, actual: %q", expectedCmd, gen.rawCommand) 534 } 535 536 expectedSrcs := []string{"in1"} 537 if !reflect.DeepEqual(expectedSrcs, gen.properties.Srcs) { 538 t.Errorf("Expected srcs: %q, actual: %q", expectedSrcs, gen.properties.Srcs) 539 } 540 } 541 542 type testTool struct { 543 android.ModuleBase 544 outputFile android.Path 545 } 546 547 func toolFactory() android.Module { 548 module := &testTool{} 549 android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) 550 return module 551 } 552 553 func (t *testTool) GenerateAndroidBuildActions(ctx android.ModuleContext) { 554 t.outputFile = android.PathForTesting("out", ctx.ModuleName()) 555 } 556 557 func (t *testTool) HostToolPath() android.OptionalPath { 558 return android.OptionalPathForPath(t.outputFile) 559 } 560 561 var _ android.HostToolProvider = (*testTool)(nil) 562