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