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 } 482} 483 484func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) { 485 in := PathsForSource(ctx, t.properties.Srcs) 486 implicit := PathForSource(ctx, "implicit") 487 orderOnly := PathForSource(ctx, "orderonly") 488 validation := PathForSource(ctx, "validation") 489 out := PathForModuleOut(ctx, "gen", ctx.ModuleName()) 490 outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d") 491 outDir := PathForModuleOut(ctx, "gen") 492 rspFile := PathForModuleOut(ctx, "rsp") 493 rspFile2 := PathForModuleOut(ctx, "rsp2") 494 rspFileContents := PathsForSource(ctx, []string{"rsp_in"}) 495 rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"}) 496 manifestPath := PathForModuleOut(ctx, "sbox.textproto") 497 498 testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, t.properties.Flags, 499 out, outDep, outDir, 500 manifestPath, t.properties.Restat, t.properties.Sbox, t.properties.Sbox_inputs, 501 rspFile, rspFileContents, rspFile2, rspFileContents2) 502} 503 504type testRuleBuilderSingleton struct{} 505 506func testRuleBuilderSingletonFactory() Singleton { 507 return &testRuleBuilderSingleton{} 508} 509 510func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) { 511 in := PathsForSource(ctx, []string{"in"}) 512 implicit := PathForSource(ctx, "implicit") 513 orderOnly := PathForSource(ctx, "orderonly") 514 validation := PathForSource(ctx, "validation") 515 out := PathForOutput(ctx, "singleton/gen/baz") 516 outDep := PathForOutput(ctx, "singleton/gen/baz.d") 517 outDir := PathForOutput(ctx, "singleton/gen") 518 rspFile := PathForOutput(ctx, "singleton/rsp") 519 rspFile2 := PathForOutput(ctx, "singleton/rsp2") 520 rspFileContents := PathsForSource(ctx, []string{"rsp_in"}) 521 rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"}) 522 manifestPath := PathForOutput(ctx, "singleton/sbox.textproto") 523 524 testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, nil, out, outDep, outDir, 525 manifestPath, true, false, false, 526 rspFile, rspFileContents, rspFile2, rspFileContents2) 527} 528 529func testRuleBuilder_Build(ctx BuilderContext, in Paths, implicit, orderOnly, validation Path, 530 flags []string, 531 out, outDep, outDir, manifestPath WritablePath, 532 restat, sbox, sboxInputs bool, 533 rspFile WritablePath, rspFileContents Paths, rspFile2 WritablePath, rspFileContents2 Paths) { 534 535 rule := NewRuleBuilder(pctx_ruleBuilderTest, ctx) 536 537 if sbox { 538 rule.Sbox(outDir, manifestPath) 539 if sboxInputs { 540 rule.SandboxInputs() 541 } 542 } 543 544 rule.Command(). 545 Tool(PathForSource(ctx, "cp")). 546 Flags(flags). 547 Inputs(in). 548 Implicit(implicit). 549 OrderOnly(orderOnly). 550 Validation(validation). 551 Output(out). 552 ImplicitDepFile(outDep). 553 FlagWithRspFileInputList("@", rspFile, rspFileContents). 554 FlagWithRspFileInputList("@", rspFile2, rspFileContents2) 555 556 if restat { 557 rule.Restat() 558 } 559 560 rule.Build("rule", "desc") 561} 562 563var prepareForRuleBuilderTest = FixtureRegisterWithContext(func(ctx RegistrationContext) { 564 ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory) 565 ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory) 566}) 567 568func TestRuleBuilder_Build(t *testing.T) { 569 fs := MockFS{ 570 "in": nil, 571 "cp": nil, 572 } 573 574 bp := ` 575 rule_builder_test { 576 name: "foo", 577 srcs: ["in"], 578 restat: true, 579 } 580 rule_builder_test { 581 name: "foo_sbox", 582 srcs: ["in"], 583 sbox: true, 584 } 585 rule_builder_test { 586 name: "foo_sbox_inputs", 587 srcs: ["in"], 588 sbox: true, 589 sbox_inputs: true, 590 } 591 ` 592 593 result := GroupFixturePreparers( 594 prepareForRuleBuilderTest, 595 FixtureWithRootAndroidBp(bp), 596 fs.AddToFixture(), 597 ).RunTest(t) 598 599 check := func(t *testing.T, params TestingBuildParams, rspFile2Params TestingBuildParams, 600 wantCommand, wantOutput, wantDepfile, wantRspFile, wantRspFile2 string, 601 wantRestat bool, extraImplicits, extraCmdDeps []string) { 602 603 t.Helper() 604 command := params.RuleParams.Command 605 re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$") 606 command = re.ReplaceAllLiteralString(command, "") 607 608 AssertStringEquals(t, "RuleParams.Command", wantCommand, command) 609 610 wantDeps := append([]string{"cp"}, extraCmdDeps...) 611 AssertArrayString(t, "RuleParams.CommandDeps", wantDeps, params.RuleParams.CommandDeps) 612 613 AssertBoolEquals(t, "RuleParams.Restat", wantRestat, params.RuleParams.Restat) 614 615 wantInputs := []string{"rsp_in"} 616 AssertArrayString(t, "Inputs", wantInputs, params.Inputs.Strings()) 617 618 wantImplicits := append([]string{"implicit", "in"}, extraImplicits...) 619 // The second rsp file and the files listed in it should be in implicits 620 wantImplicits = append(wantImplicits, "rsp_in2", wantRspFile2) 621 AssertPathsRelativeToTopEquals(t, "Implicits", wantImplicits, params.Implicits) 622 623 wantOrderOnlys := []string{"orderonly"} 624 AssertPathsRelativeToTopEquals(t, "OrderOnly", wantOrderOnlys, params.OrderOnly) 625 626 wantValidations := []string{"validation"} 627 AssertPathsRelativeToTopEquals(t, "Validations", wantValidations, params.Validations) 628 629 wantRspFileContent := "$in" 630 AssertStringEquals(t, "RspfileContent", wantRspFileContent, params.RuleParams.RspfileContent) 631 632 AssertStringEquals(t, "Rspfile", wantRspFile, params.RuleParams.Rspfile) 633 634 AssertPathRelativeToTopEquals(t, "Output", wantOutput, params.Output) 635 636 if len(params.ImplicitOutputs) != 0 { 637 t.Errorf("want ImplicitOutputs = [], got %q", params.ImplicitOutputs.Strings()) 638 } 639 640 AssertPathRelativeToTopEquals(t, "Depfile", wantDepfile, params.Depfile) 641 642 if params.Deps != blueprint.DepsGCC { 643 t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps) 644 } 645 646 rspFile2Content := ContentFromFileRuleForTests(t, result.TestContext, rspFile2Params) 647 AssertStringEquals(t, "rspFile2 content", "rsp_in2\n", rspFile2Content) 648 } 649 650 t.Run("module", func(t *testing.T) { 651 outFile := "out/soong/.intermediates/foo/gen/foo" 652 rspFile := "out/soong/.intermediates/foo/rsp" 653 rspFile2 := "out/soong/.intermediates/foo/rsp2" 654 module := result.ModuleForTests("foo", "") 655 check(t, module.Rule("rule"), module.Output(rspFile2), 656 "cp in "+outFile+" @"+rspFile+" @"+rspFile2, 657 outFile, outFile+".d", rspFile, rspFile2, true, nil, nil) 658 }) 659 t.Run("sbox", func(t *testing.T) { 660 outDir := "out/soong/.intermediates/foo_sbox" 661 sboxOutDir := filepath.Join(outDir, "gen") 662 outFile := filepath.Join(sboxOutDir, "foo_sbox") 663 depFile := filepath.Join(sboxOutDir, "foo_sbox.d") 664 rspFile := filepath.Join(outDir, "rsp") 665 rspFile2 := filepath.Join(outDir, "rsp2") 666 manifest := filepath.Join(outDir, "sbox.textproto") 667 sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox") 668 sandboxPath := shared.TempDirForOutDir("out/soong") 669 670 cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest 671 module := result.ModuleForTests("foo_sbox", "") 672 check(t, module.Output("gen/foo_sbox"), module.Output(rspFile2), 673 cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox}) 674 }) 675 t.Run("sbox_inputs", func(t *testing.T) { 676 outDir := "out/soong/.intermediates/foo_sbox_inputs" 677 sboxOutDir := filepath.Join(outDir, "gen") 678 outFile := filepath.Join(sboxOutDir, "foo_sbox_inputs") 679 depFile := filepath.Join(sboxOutDir, "foo_sbox_inputs.d") 680 rspFile := filepath.Join(outDir, "rsp") 681 rspFile2 := filepath.Join(outDir, "rsp2") 682 manifest := filepath.Join(outDir, "sbox.textproto") 683 sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox") 684 sandboxPath := shared.TempDirForOutDir("out/soong") 685 686 cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest 687 688 module := result.ModuleForTests("foo_sbox_inputs", "") 689 check(t, module.Output("gen/foo_sbox_inputs"), module.Output(rspFile2), 690 cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox}) 691 }) 692 t.Run("singleton", func(t *testing.T) { 693 outFile := filepath.Join("out/soong/singleton/gen/baz") 694 rspFile := filepath.Join("out/soong/singleton/rsp") 695 rspFile2 := filepath.Join("out/soong/singleton/rsp2") 696 singleton := result.SingletonForTests("rule_builder_test") 697 check(t, singleton.Rule("rule"), singleton.Output(rspFile2), 698 "cp in "+outFile+" @"+rspFile+" @"+rspFile2, 699 outFile, outFile+".d", rspFile, rspFile2, true, nil, nil) 700 }) 701} 702 703func TestRuleBuilderHashInputs(t *testing.T) { 704 // The basic idea here is to verify that the command (in the case of a 705 // non-sbox rule) or the sbox textproto manifest contain a hash of the 706 // inputs. 707 708 // By including a hash of the inputs, we cause the rule to re-run if 709 // the list of inputs changes because the command line or a dependency 710 // changes. 711 712 hashOf := func(s string) string { 713 sum := sha256.Sum256([]byte(s)) 714 return hex.EncodeToString(sum[:]) 715 } 716 717 bp := ` 718 rule_builder_test { 719 name: "hash0", 720 srcs: ["in1.txt", "in2.txt"], 721 } 722 rule_builder_test { 723 name: "hash0_sbox", 724 srcs: ["in1.txt", "in2.txt"], 725 sbox: true, 726 } 727 rule_builder_test { 728 name: "hash1", 729 srcs: ["in1.txt", "in2.txt", "in3.txt"], 730 } 731 rule_builder_test { 732 name: "hash1_sbox", 733 srcs: ["in1.txt", "in2.txt", "in3.txt"], 734 sbox: true, 735 } 736 ` 737 testcases := []struct { 738 name string 739 expectedHash string 740 }{ 741 { 742 name: "hash0", 743 expectedHash: hashOf("implicit\nin1.txt\nin2.txt"), 744 }, 745 { 746 name: "hash1", 747 expectedHash: hashOf("implicit\nin1.txt\nin2.txt\nin3.txt"), 748 }, 749 } 750 751 result := GroupFixturePreparers( 752 prepareForRuleBuilderTest, 753 FixtureWithRootAndroidBp(bp), 754 ).RunTest(t) 755 756 for _, test := range testcases { 757 t.Run(test.name, func(t *testing.T) { 758 t.Run("sbox", func(t *testing.T) { 759 gen := result.ModuleForTests(test.name+"_sbox", "") 760 manifest := RuleBuilderSboxProtoForTests(t, result.TestContext, gen.Output("sbox.textproto")) 761 hash := manifest.Commands[0].GetInputHash() 762 763 AssertStringEquals(t, "hash", test.expectedHash, hash) 764 }) 765 t.Run("", func(t *testing.T) { 766 gen := result.ModuleForTests(test.name+"", "") 767 command := gen.Output("gen/" + test.name).RuleParams.Command 768 if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) { 769 t.Errorf("Expected command line to end with %q, got %q", w, g) 770 } 771 }) 772 }) 773 } 774} 775