1// Copyright 2019 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package android 16 17import ( 18 "crypto/sha256" 19 "encoding/hex" 20 "fmt" 21 "path/filepath" 22 "regexp" 23 "strings" 24 "testing" 25 26 "github.com/google/blueprint" 27 28 "android/soong/shared" 29) 30 31func builderContext() BuilderContext { 32 return BuilderContextForTesting(TestConfig("out", nil, "", map[string][]byte{ 33 "ld": nil, 34 "a.o": nil, 35 "b.o": nil, 36 "cp": nil, 37 "a": nil, 38 "b": nil, 39 "ls": nil, 40 "ln": nil, 41 "turbine": nil, 42 "java": nil, 43 "javac": nil, 44 })) 45} 46 47func ExampleRuleBuilder() { 48 ctx := builderContext() 49 50 rule := NewRuleBuilder(pctx, ctx) 51 52 rule.Command(). 53 Tool(PathForSource(ctx, "ld")). 54 Inputs(PathsForTesting("a.o", "b.o")). 55 FlagWithOutput("-o ", PathForOutput(ctx, "linked")) 56 rule.Command().Text("echo success") 57 58 // To add the command to the build graph: 59 // rule.Build("link", "link") 60 61 fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && ")) 62 fmt.Printf("tools: %q\n", rule.Tools()) 63 fmt.Printf("inputs: %q\n", rule.Inputs()) 64 fmt.Printf("outputs: %q\n", rule.Outputs()) 65 66 // Output: 67 // commands: "ld a.o b.o -o out/linked && echo success" 68 // tools: ["ld"] 69 // inputs: ["a.o" "b.o"] 70 // outputs: ["out/linked"] 71} 72 73func ExampleRuleBuilder_SymlinkOutputs() { 74 ctx := builderContext() 75 76 rule := NewRuleBuilder(pctx, ctx) 77 78 rule.Command(). 79 Tool(PathForSource(ctx, "ln")). 80 FlagWithInput("-s ", PathForTesting("a.o")). 81 SymlinkOutput(PathForOutput(ctx, "a")) 82 rule.Command().Text("cp out/a out/b"). 83 ImplicitSymlinkOutput(PathForOutput(ctx, "b")) 84 85 fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && ")) 86 fmt.Printf("tools: %q\n", rule.Tools()) 87 fmt.Printf("inputs: %q\n", rule.Inputs()) 88 fmt.Printf("outputs: %q\n", rule.Outputs()) 89 fmt.Printf("symlink_outputs: %q\n", rule.SymlinkOutputs()) 90 91 // Output: 92 // commands: "ln -s a.o out/a && cp out/a out/b" 93 // tools: ["ln"] 94 // inputs: ["a.o"] 95 // outputs: ["out/a" "out/b"] 96 // symlink_outputs: ["out/a" "out/b"] 97} 98 99func ExampleRuleBuilder_Temporary() { 100 ctx := builderContext() 101 102 rule := NewRuleBuilder(pctx, ctx) 103 104 rule.Command(). 105 Tool(PathForSource(ctx, "cp")). 106 Input(PathForSource(ctx, "a")). 107 Output(PathForOutput(ctx, "b")) 108 rule.Command(). 109 Tool(PathForSource(ctx, "cp")). 110 Input(PathForOutput(ctx, "b")). 111 Output(PathForOutput(ctx, "c")) 112 rule.Temporary(PathForOutput(ctx, "b")) 113 114 fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && ")) 115 fmt.Printf("tools: %q\n", rule.Tools()) 116 fmt.Printf("inputs: %q\n", rule.Inputs()) 117 fmt.Printf("outputs: %q\n", rule.Outputs()) 118 119 // Output: 120 // commands: "cp a out/b && cp out/b out/c" 121 // tools: ["cp"] 122 // inputs: ["a"] 123 // outputs: ["out/c"] 124} 125 126func ExampleRuleBuilder_DeleteTemporaryFiles() { 127 ctx := builderContext() 128 129 rule := NewRuleBuilder(pctx, ctx) 130 131 rule.Command(). 132 Tool(PathForSource(ctx, "cp")). 133 Input(PathForSource(ctx, "a")). 134 Output(PathForOutput(ctx, "b")) 135 rule.Command(). 136 Tool(PathForSource(ctx, "cp")). 137 Input(PathForOutput(ctx, "b")). 138 Output(PathForOutput(ctx, "c")) 139 rule.Temporary(PathForOutput(ctx, "b")) 140 rule.DeleteTemporaryFiles() 141 142 fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && ")) 143 fmt.Printf("tools: %q\n", rule.Tools()) 144 fmt.Printf("inputs: %q\n", rule.Inputs()) 145 fmt.Printf("outputs: %q\n", rule.Outputs()) 146 147 // Output: 148 // commands: "cp a out/b && cp out/b out/c && rm -f out/b" 149 // tools: ["cp"] 150 // inputs: ["a"] 151 // outputs: ["out/c"] 152} 153 154func ExampleRuleBuilder_Installs() { 155 ctx := builderContext() 156 157 rule := NewRuleBuilder(pctx, ctx) 158 159 out := PathForOutput(ctx, "linked") 160 161 rule.Command(). 162 Tool(PathForSource(ctx, "ld")). 163 Inputs(PathsForTesting("a.o", "b.o")). 164 FlagWithOutput("-o ", out) 165 rule.Install(out, "/bin/linked") 166 rule.Install(out, "/sbin/linked") 167 168 fmt.Printf("rule.Installs().String() = %q\n", rule.Installs().String()) 169 170 // Output: 171 // rule.Installs().String() = "out/linked:/bin/linked out/linked:/sbin/linked" 172} 173 174func ExampleRuleBuilderCommand() { 175 ctx := builderContext() 176 177 rule := NewRuleBuilder(pctx, ctx) 178 179 // chained 180 rule.Command(). 181 Tool(PathForSource(ctx, "ld")). 182 Inputs(PathsForTesting("a.o", "b.o")). 183 FlagWithOutput("-o ", PathForOutput(ctx, "linked")) 184 185 // unchained 186 cmd := rule.Command() 187 cmd.Tool(PathForSource(ctx, "ld")) 188 cmd.Inputs(PathsForTesting("a.o", "b.o")) 189 cmd.FlagWithOutput("-o ", PathForOutput(ctx, "linked")) 190 191 // mixed: 192 cmd = rule.Command().Tool(PathForSource(ctx, "ld")) 193 cmd.Inputs(PathsForTesting("a.o", "b.o")) 194 cmd.FlagWithOutput("-o ", PathForOutput(ctx, "linked")) 195} 196 197func ExampleRuleBuilderCommand_Flag() { 198 ctx := builderContext() 199 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 200 Tool(PathForSource(ctx, "ls")).Flag("-l")) 201 // Output: 202 // ls -l 203} 204 205func ExampleRuleBuilderCommand_Flags() { 206 ctx := builderContext() 207 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 208 Tool(PathForSource(ctx, "ls")).Flags([]string{"-l", "-a"})) 209 // Output: 210 // ls -l -a 211} 212 213func ExampleRuleBuilderCommand_FlagWithArg() { 214 ctx := builderContext() 215 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 216 Tool(PathForSource(ctx, "ls")). 217 FlagWithArg("--sort=", "time")) 218 // Output: 219 // ls --sort=time 220} 221 222func ExampleRuleBuilderCommand_FlagForEachArg() { 223 ctx := builderContext() 224 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 225 Tool(PathForSource(ctx, "ls")). 226 FlagForEachArg("--sort=", []string{"time", "size"})) 227 // Output: 228 // ls --sort=time --sort=size 229} 230 231func ExampleRuleBuilderCommand_FlagForEachInput() { 232 ctx := builderContext() 233 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 234 Tool(PathForSource(ctx, "turbine")). 235 FlagForEachInput("--classpath ", PathsForTesting("a.jar", "b.jar"))) 236 // Output: 237 // turbine --classpath a.jar --classpath b.jar 238} 239 240func ExampleRuleBuilderCommand_FlagWithInputList() { 241 ctx := builderContext() 242 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 243 Tool(PathForSource(ctx, "java")). 244 FlagWithInputList("-classpath=", PathsForTesting("a.jar", "b.jar"), ":")) 245 // Output: 246 // java -classpath=a.jar:b.jar 247} 248 249func ExampleRuleBuilderCommand_FlagWithInput() { 250 ctx := builderContext() 251 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 252 Tool(PathForSource(ctx, "java")). 253 FlagWithInput("-classpath=", PathForSource(ctx, "a"))) 254 // Output: 255 // java -classpath=a 256} 257 258func ExampleRuleBuilderCommand_FlagWithList() { 259 ctx := builderContext() 260 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 261 Tool(PathForSource(ctx, "ls")). 262 FlagWithList("--sort=", []string{"time", "size"}, ",")) 263 // Output: 264 // ls --sort=time,size 265} 266 267func ExampleRuleBuilderCommand_FlagWithRspFileInputList() { 268 ctx := builderContext() 269 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 270 Tool(PathForSource(ctx, "javac")). 271 FlagWithRspFileInputList("@", PathForOutput(ctx, "foo.rsp"), PathsForTesting("a.java", "b.java")). 272 String()) 273 // Output: 274 // javac @out/foo.rsp 275} 276 277func ExampleRuleBuilderCommand_String() { 278 ctx := builderContext() 279 fmt.Println(NewRuleBuilder(pctx, ctx).Command(). 280 Text("FOO=foo"). 281 Text("echo $FOO"). 282 String()) 283 // Output: 284 // FOO=foo echo $FOO 285} 286 287func TestRuleBuilder(t *testing.T) { 288 fs := map[string][]byte{ 289 "dep_fixer": nil, 290 "input": nil, 291 "Implicit": nil, 292 "Input": nil, 293 "OrderOnly": nil, 294 "OrderOnlys": nil, 295 "Tool": nil, 296 "input2": nil, 297 "tool2": nil, 298 "input3": nil, 299 } 300 301 pathCtx := PathContextForTesting(TestConfig("out_local", nil, "", fs)) 302 ctx := builderContextForTests{ 303 PathContext: pathCtx, 304 } 305 306 addCommands := func(rule *RuleBuilder) { 307 cmd := rule.Command(). 308 DepFile(PathForOutput(ctx, "module/DepFile")). 309 Flag("Flag"). 310 FlagWithArg("FlagWithArg=", "arg"). 311 FlagWithDepFile("FlagWithDepFile=", PathForOutput(ctx, "module/depfile")). 312 FlagWithInput("FlagWithInput=", PathForSource(ctx, "input")). 313 FlagWithOutput("FlagWithOutput=", PathForOutput(ctx, "module/output")). 314 FlagWithRspFileInputList("FlagWithRspFileInputList=", PathForOutput(ctx, "rsp"), 315 Paths{ 316 PathForSource(ctx, "RspInput"), 317 PathForOutput(ctx, "other/RspOutput2"), 318 }). 319 Implicit(PathForSource(ctx, "Implicit")). 320 ImplicitDepFile(PathForOutput(ctx, "module/ImplicitDepFile")). 321 ImplicitOutput(PathForOutput(ctx, "module/ImplicitOutput")). 322 Input(PathForSource(ctx, "Input")). 323 Output(PathForOutput(ctx, "module/Output")). 324 OrderOnly(PathForSource(ctx, "OrderOnly")). 325 Validation(PathForSource(ctx, "Validation")). 326 SymlinkOutput(PathForOutput(ctx, "module/SymlinkOutput")). 327 ImplicitSymlinkOutput(PathForOutput(ctx, "module/ImplicitSymlinkOutput")). 328 Text("Text"). 329 Tool(PathForSource(ctx, "Tool")) 330 331 rule.Command(). 332 Text("command2"). 333 DepFile(PathForOutput(ctx, "module/depfile2")). 334 Input(PathForSource(ctx, "input2")). 335 Output(PathForOutput(ctx, "module/output2")). 336 OrderOnlys(PathsForSource(ctx, []string{"OrderOnlys"})). 337 Validations(PathsForSource(ctx, []string{"Validations"})). 338 Tool(PathForSource(ctx, "tool2")) 339 340 // Test updates to the first command after the second command has been started 341 cmd.Text("after command2") 342 // Test updating a command when the previous update did not replace the cmd variable 343 cmd.Text("old cmd") 344 345 // Test a command that uses the output of a previous command as an input 346 rule.Command(). 347 Text("command3"). 348 Input(PathForSource(ctx, "input3")). 349 Input(PathForOutput(ctx, "module/output2")). 350 Output(PathForOutput(ctx, "module/output3")). 351 Text(cmd.PathForInput(PathForSource(ctx, "input3"))). 352 Text(cmd.PathForOutput(PathForOutput(ctx, "module/output2"))) 353 } 354 355 wantInputs := PathsForSource(ctx, []string{"Implicit", "Input", "input", "input2", "input3"}) 356 wantRspFileInputs := Paths{PathForSource(ctx, "RspInput"), 357 PathForOutput(ctx, "other/RspOutput2")} 358 wantOutputs := PathsForOutput(ctx, []string{ 359 "module/ImplicitOutput", "module/ImplicitSymlinkOutput", "module/Output", "module/SymlinkOutput", 360 "module/output", "module/output2", "module/output3"}) 361 wantDepFiles := PathsForOutput(ctx, []string{ 362 "module/DepFile", "module/depfile", "module/ImplicitDepFile", "module/depfile2"}) 363 wantTools := PathsForSource(ctx, []string{"Tool", "tool2"}) 364 wantOrderOnlys := PathsForSource(ctx, []string{"OrderOnly", "OrderOnlys"}) 365 wantValidations := PathsForSource(ctx, []string{"Validation", "Validations"}) 366 wantSymlinkOutputs := PathsForOutput(ctx, []string{ 367 "module/ImplicitSymlinkOutput", "module/SymlinkOutput"}) 368 369 t.Run("normal", func(t *testing.T) { 370 rule := NewRuleBuilder(pctx, ctx) 371 addCommands(rule) 372 373 wantCommands := []string{ 374 "out_local/module/DepFile Flag FlagWithArg=arg FlagWithDepFile=out_local/module/depfile " + 375 "FlagWithInput=input FlagWithOutput=out_local/module/output FlagWithRspFileInputList=out_local/rsp " + 376 "Input out_local/module/Output out_local/module/SymlinkOutput Text Tool after command2 old cmd", 377 "command2 out_local/module/depfile2 input2 out_local/module/output2 tool2", 378 "command3 input3 out_local/module/output2 out_local/module/output3 input3 out_local/module/output2", 379 } 380 381 wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " + 382 "out_local/module/DepFile out_local/module/depfile out_local/module/ImplicitDepFile out_local/module/depfile2" 383 384 AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands()) 385 386 AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs()) 387 AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs()) 388 AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs()) 389 AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs()) 390 AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles()) 391 AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools()) 392 AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys()) 393 AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations()) 394 395 AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String()) 396 }) 397 398 t.Run("sbox", func(t *testing.T) { 399 rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"), 400 PathForOutput(ctx, "sbox.textproto")) 401 addCommands(rule) 402 403 wantCommands := []string{ 404 "__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " + 405 "FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " + 406 "FlagWithRspFileInputList=out_local/rsp Input __SBOX_SANDBOX_DIR__/out/Output " + 407 "__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text Tool after command2 old cmd", 408 "command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 tool2", 409 "command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2", 410 } 411 412 wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2" 413 414 AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands()) 415 416 AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs()) 417 AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs()) 418 AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs()) 419 AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs()) 420 AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles()) 421 AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools()) 422 AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys()) 423 AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations()) 424 425 AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String()) 426 }) 427 428 t.Run("sbox tools", func(t *testing.T) { 429 rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"), 430 PathForOutput(ctx, "sbox.textproto")).SandboxTools() 431 addCommands(rule) 432 433 wantCommands := []string{ 434 "__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " + 435 "FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " + 436 "FlagWithRspFileInputList=out_local/rsp Input __SBOX_SANDBOX_DIR__/out/Output " + 437 "__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd", 438 "command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2", 439 "command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2", 440 } 441 442 wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2" 443 444 AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands()) 445 446 AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs()) 447 AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs()) 448 AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs()) 449 AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs()) 450 AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles()) 451 AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools()) 452 AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys()) 453 AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations()) 454 455 AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String()) 456 }) 457 458 t.Run("sbox inputs", func(t *testing.T) { 459 rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"), 460 PathForOutput(ctx, "sbox.textproto")).SandboxInputs() 461 addCommands(rule) 462 463 wantCommands := []string{ 464 "__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " + 465 "FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " + 466 "FlagWithRspFileInputList=__SBOX_SANDBOX_DIR__/out/rsp Input __SBOX_SANDBOX_DIR__/out/Output " + 467 "__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd", 468 "command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2", 469 "command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2", 470 } 471 472 wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2" 473 474 AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands()) 475 476 AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs()) 477 AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs()) 478 AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs()) 479 AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs()) 480 AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles()) 481 AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools()) 482 AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys()) 483 AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations()) 484 485 AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String()) 486 }) 487} 488 489func testRuleBuilderFactory() Module { 490 module := &testRuleBuilderModule{} 491 module.AddProperties(&module.properties) 492 InitAndroidModule(module) 493 return module 494} 495 496type testRuleBuilderModule struct { 497 ModuleBase 498 properties struct { 499 Srcs []string 500 501 Restat bool 502 Sbox bool 503 Sbox_inputs bool 504 } 505} 506 507func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) { 508 in := PathsForSource(ctx, t.properties.Srcs) 509 implicit := PathForSource(ctx, "implicit") 510 orderOnly := PathForSource(ctx, "orderonly") 511 validation := PathForSource(ctx, "validation") 512 out := PathForModuleOut(ctx, "gen", ctx.ModuleName()) 513 outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d") 514 outDir := PathForModuleOut(ctx, "gen") 515 rspFile := PathForModuleOut(ctx, "rsp") 516 rspFile2 := PathForModuleOut(ctx, "rsp2") 517 rspFileContents := PathsForSource(ctx, []string{"rsp_in"}) 518 rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"}) 519 manifestPath := PathForModuleOut(ctx, "sbox.textproto") 520 521 testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, out, outDep, outDir, 522 manifestPath, t.properties.Restat, t.properties.Sbox, t.properties.Sbox_inputs, 523 rspFile, rspFileContents, rspFile2, rspFileContents2) 524} 525 526type testRuleBuilderSingleton struct{} 527 528func testRuleBuilderSingletonFactory() Singleton { 529 return &testRuleBuilderSingleton{} 530} 531 532func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) { 533 in := PathsForSource(ctx, []string{"in"}) 534 implicit := PathForSource(ctx, "implicit") 535 orderOnly := PathForSource(ctx, "orderonly") 536 validation := PathForSource(ctx, "validation") 537 out := PathForOutput(ctx, "singleton/gen/baz") 538 outDep := PathForOutput(ctx, "singleton/gen/baz.d") 539 outDir := PathForOutput(ctx, "singleton/gen") 540 rspFile := PathForOutput(ctx, "singleton/rsp") 541 rspFile2 := PathForOutput(ctx, "singleton/rsp2") 542 rspFileContents := PathsForSource(ctx, []string{"rsp_in"}) 543 rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"}) 544 manifestPath := PathForOutput(ctx, "singleton/sbox.textproto") 545 546 testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, out, outDep, outDir, 547 manifestPath, true, false, false, 548 rspFile, rspFileContents, rspFile2, rspFileContents2) 549} 550 551func testRuleBuilder_Build(ctx BuilderContext, in Paths, implicit, orderOnly, validation Path, 552 out, outDep, outDir, manifestPath WritablePath, 553 restat, sbox, sboxInputs bool, 554 rspFile WritablePath, rspFileContents Paths, rspFile2 WritablePath, rspFileContents2 Paths) { 555 556 rule := NewRuleBuilder(pctx, ctx) 557 558 if sbox { 559 rule.Sbox(outDir, manifestPath) 560 if sboxInputs { 561 rule.SandboxInputs() 562 } 563 } 564 565 rule.Command(). 566 Tool(PathForSource(ctx, "cp")). 567 Inputs(in). 568 Implicit(implicit). 569 OrderOnly(orderOnly). 570 Validation(validation). 571 Output(out). 572 ImplicitDepFile(outDep). 573 FlagWithRspFileInputList("@", rspFile, rspFileContents). 574 FlagWithRspFileInputList("@", rspFile2, rspFileContents2) 575 576 if restat { 577 rule.Restat() 578 } 579 580 rule.Build("rule", "desc") 581} 582 583var prepareForRuleBuilderTest = FixtureRegisterWithContext(func(ctx RegistrationContext) { 584 ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory) 585 ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory) 586}) 587 588func TestRuleBuilder_Build(t *testing.T) { 589 fs := MockFS{ 590 "in": nil, 591 "cp": nil, 592 } 593 594 bp := ` 595 rule_builder_test { 596 name: "foo", 597 srcs: ["in"], 598 restat: true, 599 } 600 rule_builder_test { 601 name: "foo_sbox", 602 srcs: ["in"], 603 sbox: true, 604 } 605 rule_builder_test { 606 name: "foo_sbox_inputs", 607 srcs: ["in"], 608 sbox: true, 609 sbox_inputs: true, 610 } 611 ` 612 613 result := GroupFixturePreparers( 614 prepareForRuleBuilderTest, 615 FixtureWithRootAndroidBp(bp), 616 fs.AddToFixture(), 617 ).RunTest(t) 618 619 check := func(t *testing.T, params TestingBuildParams, rspFile2Params TestingBuildParams, 620 wantCommand, wantOutput, wantDepfile, wantRspFile, wantRspFile2 string, 621 wantRestat bool, extraImplicits, extraCmdDeps []string) { 622 623 t.Helper() 624 command := params.RuleParams.Command 625 re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$") 626 command = re.ReplaceAllLiteralString(command, "") 627 628 AssertStringEquals(t, "RuleParams.Command", wantCommand, command) 629 630 wantDeps := append([]string{"cp"}, extraCmdDeps...) 631 AssertArrayString(t, "RuleParams.CommandDeps", wantDeps, params.RuleParams.CommandDeps) 632 633 AssertBoolEquals(t, "RuleParams.Restat", wantRestat, params.RuleParams.Restat) 634 635 wantInputs := []string{"rsp_in"} 636 AssertArrayString(t, "Inputs", wantInputs, params.Inputs.Strings()) 637 638 wantImplicits := append([]string{"implicit", "in"}, extraImplicits...) 639 // The second rsp file and the files listed in it should be in implicits 640 wantImplicits = append(wantImplicits, "rsp_in2", wantRspFile2) 641 AssertPathsRelativeToTopEquals(t, "Implicits", wantImplicits, params.Implicits) 642 643 wantOrderOnlys := []string{"orderonly"} 644 AssertPathsRelativeToTopEquals(t, "OrderOnly", wantOrderOnlys, params.OrderOnly) 645 646 wantValidations := []string{"validation"} 647 AssertPathsRelativeToTopEquals(t, "Validations", wantValidations, params.Validations) 648 649 wantRspFileContent := "$in" 650 AssertStringEquals(t, "RspfileContent", wantRspFileContent, params.RuleParams.RspfileContent) 651 652 AssertStringEquals(t, "Rspfile", wantRspFile, params.RuleParams.Rspfile) 653 654 AssertPathRelativeToTopEquals(t, "Output", wantOutput, params.Output) 655 656 if len(params.ImplicitOutputs) != 0 { 657 t.Errorf("want ImplicitOutputs = [], got %q", params.ImplicitOutputs.Strings()) 658 } 659 660 AssertPathRelativeToTopEquals(t, "Depfile", wantDepfile, params.Depfile) 661 662 if params.Deps != blueprint.DepsGCC { 663 t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps) 664 } 665 666 rspFile2Content := ContentFromFileRuleForTests(t, rspFile2Params) 667 AssertStringEquals(t, "rspFile2 content", "rsp_in2\n", rspFile2Content) 668 } 669 670 t.Run("module", func(t *testing.T) { 671 outFile := "out/soong/.intermediates/foo/gen/foo" 672 rspFile := "out/soong/.intermediates/foo/rsp" 673 rspFile2 := "out/soong/.intermediates/foo/rsp2" 674 module := result.ModuleForTests("foo", "") 675 check(t, module.Rule("rule"), module.Output(rspFile2), 676 "cp in "+outFile+" @"+rspFile+" @"+rspFile2, 677 outFile, outFile+".d", rspFile, rspFile2, true, nil, nil) 678 }) 679 t.Run("sbox", func(t *testing.T) { 680 outDir := "out/soong/.intermediates/foo_sbox" 681 outFile := filepath.Join(outDir, "gen/foo_sbox") 682 depFile := filepath.Join(outDir, "gen/foo_sbox.d") 683 rspFile := filepath.Join(outDir, "rsp") 684 rspFile2 := filepath.Join(outDir, "rsp2") 685 manifest := filepath.Join(outDir, "sbox.textproto") 686 sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox") 687 sandboxPath := shared.TempDirForOutDir("out/soong") 688 689 cmd := `rm -rf ` + outDir + `/gen && ` + 690 sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest 691 module := result.ModuleForTests("foo_sbox", "") 692 check(t, module.Output("gen/foo_sbox"), module.Output(rspFile2), 693 cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox}) 694 }) 695 t.Run("sbox_inputs", func(t *testing.T) { 696 outDir := "out/soong/.intermediates/foo_sbox_inputs" 697 outFile := filepath.Join(outDir, "gen/foo_sbox_inputs") 698 depFile := filepath.Join(outDir, "gen/foo_sbox_inputs.d") 699 rspFile := filepath.Join(outDir, "rsp") 700 rspFile2 := filepath.Join(outDir, "rsp2") 701 manifest := filepath.Join(outDir, "sbox.textproto") 702 sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox") 703 sandboxPath := shared.TempDirForOutDir("out/soong") 704 705 cmd := `rm -rf ` + outDir + `/gen && ` + 706 sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest 707 708 module := result.ModuleForTests("foo_sbox_inputs", "") 709 check(t, module.Output("gen/foo_sbox_inputs"), module.Output(rspFile2), 710 cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox}) 711 }) 712 t.Run("singleton", func(t *testing.T) { 713 outFile := filepath.Join("out/soong/singleton/gen/baz") 714 rspFile := filepath.Join("out/soong/singleton/rsp") 715 rspFile2 := filepath.Join("out/soong/singleton/rsp2") 716 singleton := result.SingletonForTests("rule_builder_test") 717 check(t, singleton.Rule("rule"), singleton.Output(rspFile2), 718 "cp in "+outFile+" @"+rspFile+" @"+rspFile2, 719 outFile, outFile+".d", rspFile, rspFile2, true, nil, nil) 720 }) 721} 722 723func TestRuleBuilderHashInputs(t *testing.T) { 724 // The basic idea here is to verify that the command (in the case of a 725 // non-sbox rule) or the sbox textproto manifest contain a hash of the 726 // inputs. 727 728 // By including a hash of the inputs, we cause the rule to re-run if 729 // the list of inputs changes because the command line or a dependency 730 // changes. 731 732 hashOf := func(s string) string { 733 sum := sha256.Sum256([]byte(s)) 734 return hex.EncodeToString(sum[:]) 735 } 736 737 bp := ` 738 rule_builder_test { 739 name: "hash0", 740 srcs: ["in1.txt", "in2.txt"], 741 } 742 rule_builder_test { 743 name: "hash0_sbox", 744 srcs: ["in1.txt", "in2.txt"], 745 sbox: true, 746 } 747 rule_builder_test { 748 name: "hash1", 749 srcs: ["in1.txt", "in2.txt", "in3.txt"], 750 } 751 rule_builder_test { 752 name: "hash1_sbox", 753 srcs: ["in1.txt", "in2.txt", "in3.txt"], 754 sbox: true, 755 } 756 ` 757 testcases := []struct { 758 name string 759 expectedHash string 760 }{ 761 { 762 name: "hash0", 763 expectedHash: hashOf("implicit\nin1.txt\nin2.txt"), 764 }, 765 { 766 name: "hash1", 767 expectedHash: hashOf("implicit\nin1.txt\nin2.txt\nin3.txt"), 768 }, 769 } 770 771 result := GroupFixturePreparers( 772 prepareForRuleBuilderTest, 773 FixtureWithRootAndroidBp(bp), 774 ).RunTest(t) 775 776 for _, test := range testcases { 777 t.Run(test.name, func(t *testing.T) { 778 t.Run("sbox", func(t *testing.T) { 779 gen := result.ModuleForTests(test.name+"_sbox", "") 780 manifest := RuleBuilderSboxProtoForTests(t, gen.Output("sbox.textproto")) 781 hash := manifest.Commands[0].GetInputHash() 782 783 AssertStringEquals(t, "hash", test.expectedHash, hash) 784 }) 785 t.Run("", func(t *testing.T) { 786 gen := result.ModuleForTests(test.name+"", "") 787 command := gen.Output("gen/" + test.name).RuleParams.Command 788 if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) { 789 t.Errorf("Expected command line to end with %q, got %q", w, g) 790 } 791 }) 792 }) 793 } 794} 795