1// Copyright 2018 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package android 16 17import ( 18 "fmt" 19 "sort" 20 "strings" 21 22 "github.com/google/blueprint" 23 "github.com/google/blueprint/proptools" 24 25 "android/soong/shared" 26) 27 28// RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build 29// graph. 30type RuleBuilder struct { 31 commands []*RuleBuilderCommand 32 installs RuleBuilderInstalls 33 temporariesSet map[WritablePath]bool 34 restat bool 35 sbox bool 36 highmem bool 37 remoteable RemoteRuleSupports 38 sboxOutDir WritablePath 39 missingDeps []string 40} 41 42// NewRuleBuilder returns a newly created RuleBuilder. 43func NewRuleBuilder() *RuleBuilder { 44 return &RuleBuilder{ 45 temporariesSet: make(map[WritablePath]bool), 46 } 47} 48 49// RuleBuilderInstall is a tuple of install from and to locations. 50type RuleBuilderInstall struct { 51 From Path 52 To string 53} 54 55type RuleBuilderInstalls []RuleBuilderInstall 56 57// String returns the RuleBuilderInstalls in the form used by $(call copy-many-files) in Make, a space separated 58// list of from:to tuples. 59func (installs RuleBuilderInstalls) String() string { 60 sb := strings.Builder{} 61 for i, install := range installs { 62 if i != 0 { 63 sb.WriteRune(' ') 64 } 65 sb.WriteString(install.From.String()) 66 sb.WriteRune(':') 67 sb.WriteString(install.To) 68 } 69 return sb.String() 70} 71 72// MissingDeps adds modules to the list of missing dependencies. If MissingDeps 73// is called with a non-empty input, any call to Build will result in a rule 74// that will print an error listing the missing dependencies and fail. 75// MissingDeps should only be called if Config.AllowMissingDependencies() is 76// true. 77func (r *RuleBuilder) MissingDeps(missingDeps []string) { 78 r.missingDeps = append(r.missingDeps, missingDeps...) 79} 80 81// Restat marks the rule as a restat rule, which will be passed to ModuleContext.Rule in BuildParams.Restat. 82// 83// Restat is not compatible with Sbox() 84func (r *RuleBuilder) Restat() *RuleBuilder { 85 if r.sbox { 86 panic("Restat() is not compatible with Sbox()") 87 } 88 r.restat = true 89 return r 90} 91 92// HighMem marks the rule as a high memory rule, which will limit how many run in parallel with other high memory 93// rules. 94func (r *RuleBuilder) HighMem() *RuleBuilder { 95 r.highmem = true 96 return r 97} 98 99// Remoteable marks the rule as supporting remote execution. 100func (r *RuleBuilder) Remoteable(supports RemoteRuleSupports) *RuleBuilder { 101 r.remoteable = supports 102 return r 103} 104 105// Sbox marks the rule as needing to be wrapped by sbox. The WritablePath should point to the output 106// directory that sbox will wipe. It should not be written to by any other rule. sbox will ensure 107// that all outputs have been written, and will discard any output files that were not specified. 108// 109// Sbox is not compatible with Restat() 110func (r *RuleBuilder) Sbox(outputDir WritablePath) *RuleBuilder { 111 if r.sbox { 112 panic("Sbox() may not be called more than once") 113 } 114 if len(r.commands) > 0 { 115 panic("Sbox() may not be called after Command()") 116 } 117 if r.restat { 118 panic("Sbox() is not compatible with Restat()") 119 } 120 r.sbox = true 121 r.sboxOutDir = outputDir 122 return r 123} 124 125// Install associates an output of the rule with an install location, which can be retrieved later using 126// RuleBuilder.Installs. 127func (r *RuleBuilder) Install(from Path, to string) { 128 r.installs = append(r.installs, RuleBuilderInstall{from, to}) 129} 130 131// Command returns a new RuleBuilderCommand for the rule. The commands will be ordered in the rule by when they were 132// created by this method. That can be mutated through their methods in any order, as long as the mutations do not 133// race with any call to Build. 134func (r *RuleBuilder) Command() *RuleBuilderCommand { 135 command := &RuleBuilderCommand{ 136 sbox: r.sbox, 137 sboxOutDir: r.sboxOutDir, 138 } 139 r.commands = append(r.commands, command) 140 return command 141} 142 143// Temporary marks an output of a command as an intermediate file that will be used as an input to another command 144// in the same rule, and should not be listed in Outputs. 145func (r *RuleBuilder) Temporary(path WritablePath) { 146 r.temporariesSet[path] = true 147} 148 149// DeleteTemporaryFiles adds a command to the rule that deletes any outputs that have been marked using Temporary 150// when the rule runs. DeleteTemporaryFiles should be called after all calls to Temporary. 151func (r *RuleBuilder) DeleteTemporaryFiles() { 152 var temporariesList WritablePaths 153 154 for intermediate := range r.temporariesSet { 155 temporariesList = append(temporariesList, intermediate) 156 } 157 158 sort.Slice(temporariesList, func(i, j int) bool { 159 return temporariesList[i].String() < temporariesList[j].String() 160 }) 161 162 r.Command().Text("rm").Flag("-f").Outputs(temporariesList) 163} 164 165// Inputs returns the list of paths that were passed to the RuleBuilderCommand methods that take 166// input paths, such as RuleBuilderCommand.Input, RuleBuilderComand.Implicit, or 167// RuleBuilderCommand.FlagWithInput. Inputs to a command that are also outputs of another command 168// in the same RuleBuilder are filtered out. The list is sorted and duplicates removed. 169func (r *RuleBuilder) Inputs() Paths { 170 outputs := r.outputSet() 171 depFiles := r.depFileSet() 172 173 inputs := make(map[string]Path) 174 for _, c := range r.commands { 175 for _, input := range append(c.inputs, c.implicits...) { 176 inputStr := input.String() 177 if _, isOutput := outputs[inputStr]; !isOutput { 178 if _, isDepFile := depFiles[inputStr]; !isDepFile { 179 inputs[input.String()] = input 180 } 181 } 182 } 183 } 184 185 var inputList Paths 186 for _, input := range inputs { 187 inputList = append(inputList, input) 188 } 189 190 sort.Slice(inputList, func(i, j int) bool { 191 return inputList[i].String() < inputList[j].String() 192 }) 193 194 return inputList 195} 196 197// OrderOnlys returns the list of paths that were passed to the RuleBuilderCommand.OrderOnly or 198// RuleBuilderCommand.OrderOnlys. The list is sorted and duplicates removed. 199func (r *RuleBuilder) OrderOnlys() Paths { 200 orderOnlys := make(map[string]Path) 201 for _, c := range r.commands { 202 for _, orderOnly := range c.orderOnlys { 203 orderOnlys[orderOnly.String()] = orderOnly 204 } 205 } 206 207 var orderOnlyList Paths 208 for _, orderOnly := range orderOnlys { 209 orderOnlyList = append(orderOnlyList, orderOnly) 210 } 211 212 sort.Slice(orderOnlyList, func(i, j int) bool { 213 return orderOnlyList[i].String() < orderOnlyList[j].String() 214 }) 215 216 return orderOnlyList 217} 218 219func (r *RuleBuilder) outputSet() map[string]WritablePath { 220 outputs := make(map[string]WritablePath) 221 for _, c := range r.commands { 222 for _, output := range c.outputs { 223 outputs[output.String()] = output 224 } 225 } 226 return outputs 227} 228 229// Outputs returns the list of paths that were passed to the RuleBuilderCommand methods that take 230// output paths, such as RuleBuilderCommand.Output, RuleBuilderCommand.ImplicitOutput, or 231// RuleBuilderCommand.FlagWithInput. The list is sorted and duplicates removed. 232func (r *RuleBuilder) Outputs() WritablePaths { 233 outputs := r.outputSet() 234 235 var outputList WritablePaths 236 for _, output := range outputs { 237 if !r.temporariesSet[output] { 238 outputList = append(outputList, output) 239 } 240 } 241 242 sort.Slice(outputList, func(i, j int) bool { 243 return outputList[i].String() < outputList[j].String() 244 }) 245 246 return outputList 247} 248 249func (r *RuleBuilder) depFileSet() map[string]WritablePath { 250 depFiles := make(map[string]WritablePath) 251 for _, c := range r.commands { 252 for _, depFile := range c.depFiles { 253 depFiles[depFile.String()] = depFile 254 } 255 } 256 return depFiles 257} 258 259// DepFiles returns the list of paths that were passed to the RuleBuilderCommand methods that take depfile paths, such 260// as RuleBuilderCommand.DepFile or RuleBuilderCommand.FlagWithDepFile. 261func (r *RuleBuilder) DepFiles() WritablePaths { 262 var depFiles WritablePaths 263 264 for _, c := range r.commands { 265 for _, depFile := range c.depFiles { 266 depFiles = append(depFiles, depFile) 267 } 268 } 269 270 return depFiles 271} 272 273// Installs returns the list of tuples passed to Install. 274func (r *RuleBuilder) Installs() RuleBuilderInstalls { 275 return append(RuleBuilderInstalls(nil), r.installs...) 276} 277 278func (r *RuleBuilder) toolsSet() map[string]Path { 279 tools := make(map[string]Path) 280 for _, c := range r.commands { 281 for _, tool := range c.tools { 282 tools[tool.String()] = tool 283 } 284 } 285 286 return tools 287} 288 289// Tools returns the list of paths that were passed to the RuleBuilderCommand.Tool method. The 290// list is sorted and duplicates removed. 291func (r *RuleBuilder) Tools() Paths { 292 toolsSet := r.toolsSet() 293 294 var toolsList Paths 295 for _, tool := range toolsSet { 296 toolsList = append(toolsList, tool) 297 } 298 299 sort.Slice(toolsList, func(i, j int) bool { 300 return toolsList[i].String() < toolsList[j].String() 301 }) 302 303 return toolsList 304} 305 306// RspFileInputs returns the list of paths that were passed to the RuleBuilderCommand.FlagWithRspFileInputList method. 307func (r *RuleBuilder) RspFileInputs() Paths { 308 var rspFileInputs Paths 309 for _, c := range r.commands { 310 if c.rspFileInputs != nil { 311 if rspFileInputs != nil { 312 panic("Multiple commands in a rule may not have rsp file inputs") 313 } 314 rspFileInputs = c.rspFileInputs 315 } 316 } 317 318 return rspFileInputs 319} 320 321// Commands returns a slice containing the built command line for each call to RuleBuilder.Command. 322func (r *RuleBuilder) Commands() []string { 323 var commands []string 324 for _, c := range r.commands { 325 commands = append(commands, c.String()) 326 } 327 return commands 328} 329 330// NinjaEscapedCommands returns a slice containin the built command line after ninja escaping for each call to 331// RuleBuilder.Command. 332func (r *RuleBuilder) NinjaEscapedCommands() []string { 333 var commands []string 334 for _, c := range r.commands { 335 commands = append(commands, c.NinjaEscapedString()) 336 } 337 return commands 338} 339 340// BuilderContext is a subset of ModuleContext and SingletonContext. 341type BuilderContext interface { 342 PathContext 343 Rule(PackageContext, string, blueprint.RuleParams, ...string) blueprint.Rule 344 Build(PackageContext, BuildParams) 345} 346 347var _ BuilderContext = ModuleContext(nil) 348var _ BuilderContext = SingletonContext(nil) 349 350func (r *RuleBuilder) depFileMergerCmd(ctx PathContext, depFiles WritablePaths) *RuleBuilderCommand { 351 return r.Command(). 352 BuiltTool(ctx, "dep_fixer"). 353 Inputs(depFiles.Paths()) 354} 355 356// Build adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for 357// Outputs. 358func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string, desc string) { 359 name = ninjaNameEscape(name) 360 361 if len(r.missingDeps) > 0 { 362 ctx.Build(pctx, BuildParams{ 363 Rule: ErrorRule, 364 Outputs: r.Outputs(), 365 OrderOnly: r.OrderOnlys(), 366 Description: desc, 367 Args: map[string]string{ 368 "error": "missing dependencies: " + strings.Join(r.missingDeps, ", "), 369 }, 370 }) 371 return 372 } 373 374 var depFile WritablePath 375 var depFormat blueprint.Deps 376 if depFiles := r.DepFiles(); len(depFiles) > 0 { 377 depFile = depFiles[0] 378 depFormat = blueprint.DepsGCC 379 if len(depFiles) > 1 { 380 // Add a command locally that merges all depfiles together into the first depfile. 381 r.depFileMergerCmd(ctx, depFiles) 382 383 if r.sbox { 384 // Check for Rel() errors, as all depfiles should be in the output dir 385 for _, path := range depFiles[1:] { 386 Rel(ctx, r.sboxOutDir.String(), path.String()) 387 } 388 } 389 } 390 } 391 392 tools := r.Tools() 393 commands := r.NinjaEscapedCommands() 394 outputs := r.Outputs() 395 396 if len(commands) == 0 { 397 return 398 } 399 if len(outputs) == 0 { 400 panic("No outputs specified from any Commands") 401 } 402 403 commandString := strings.Join(commands, " && ") 404 405 if r.sbox { 406 sboxOutputs := make([]string, len(outputs)) 407 for i, output := range outputs { 408 sboxOutputs[i] = "__SBOX_OUT_DIR__/" + Rel(ctx, r.sboxOutDir.String(), output.String()) 409 } 410 411 commandString = proptools.ShellEscape(commandString) 412 if !strings.HasPrefix(commandString, `'`) { 413 commandString = `'` + commandString + `'` 414 } 415 416 sboxCmd := &RuleBuilderCommand{} 417 sboxCmd.BuiltTool(ctx, "sbox"). 418 Flag("-c").Text(commandString). 419 Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())). 420 Flag("--output-root").Text(r.sboxOutDir.String()) 421 422 if depFile != nil { 423 sboxCmd.Flag("--depfile-out").Text(depFile.String()) 424 } 425 426 sboxCmd.Flags(sboxOutputs) 427 428 commandString = sboxCmd.buf.String() 429 tools = append(tools, sboxCmd.tools...) 430 } 431 432 // Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to 433 // ImplicitOutputs. RuleBuilder only uses "$out" for the rsp file location, so the distinction between Outputs and 434 // ImplicitOutputs doesn't matter. 435 output := outputs[0] 436 implicitOutputs := outputs[1:] 437 438 var rspFile, rspFileContent string 439 rspFileInputs := r.RspFileInputs() 440 if rspFileInputs != nil { 441 rspFile = "$out.rsp" 442 rspFileContent = "$in" 443 } 444 445 var pool blueprint.Pool 446 if ctx.Config().UseGoma() && r.remoteable.Goma { 447 // When USE_GOMA=true is set and the rule is supported by goma, allow jobs to run outside the local pool. 448 } else if ctx.Config().UseRBE() && r.remoteable.RBE { 449 // When USE_RBE=true is set and the rule is supported by RBE, use the remotePool. 450 pool = remotePool 451 } else if r.highmem { 452 pool = highmemPool 453 } else if ctx.Config().UseRemoteBuild() { 454 pool = localPool 455 } 456 457 ctx.Build(pctx, BuildParams{ 458 Rule: ctx.Rule(pctx, name, blueprint.RuleParams{ 459 Command: commandString, 460 CommandDeps: tools.Strings(), 461 Restat: r.restat, 462 Rspfile: rspFile, 463 RspfileContent: rspFileContent, 464 Pool: pool, 465 }), 466 Inputs: rspFileInputs, 467 Implicits: r.Inputs(), 468 Output: output, 469 ImplicitOutputs: implicitOutputs, 470 Depfile: depFile, 471 Deps: depFormat, 472 Description: desc, 473 }) 474} 475 476// RuleBuilderCommand is a builder for a command in a command line. It can be mutated by its methods to add to the 477// command and track dependencies. The methods mutate the RuleBuilderCommand in place, as well as return the 478// RuleBuilderCommand, so they can be used chained or unchained. All methods that add text implicitly add a single 479// space as a separator from the previous method. 480type RuleBuilderCommand struct { 481 buf strings.Builder 482 inputs Paths 483 implicits Paths 484 orderOnlys Paths 485 outputs WritablePaths 486 depFiles WritablePaths 487 tools Paths 488 rspFileInputs Paths 489 490 // spans [start,end) of the command that should not be ninja escaped 491 unescapedSpans [][2]int 492 493 sbox bool 494 sboxOutDir WritablePath 495} 496 497func (c *RuleBuilderCommand) addInput(path Path) string { 498 if c.sbox { 499 if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel { 500 return "__SBOX_OUT_DIR__/" + rel 501 } 502 } 503 c.inputs = append(c.inputs, path) 504 return path.String() 505} 506 507func (c *RuleBuilderCommand) addImplicit(path Path) string { 508 if c.sbox { 509 if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel { 510 return "__SBOX_OUT_DIR__/" + rel 511 } 512 } 513 c.implicits = append(c.implicits, path) 514 return path.String() 515} 516 517func (c *RuleBuilderCommand) addOrderOnly(path Path) { 518 c.orderOnlys = append(c.orderOnlys, path) 519} 520 521func (c *RuleBuilderCommand) outputStr(path Path) string { 522 if c.sbox { 523 // Errors will be handled in RuleBuilder.Build where we have a context to report them 524 rel, _, _ := maybeRelErr(c.sboxOutDir.String(), path.String()) 525 return "__SBOX_OUT_DIR__/" + rel 526 } 527 return path.String() 528} 529 530// Text adds the specified raw text to the command line. The text should not contain input or output paths or the 531// rule will not have them listed in its dependencies or outputs. 532func (c *RuleBuilderCommand) Text(text string) *RuleBuilderCommand { 533 if c.buf.Len() > 0 { 534 c.buf.WriteByte(' ') 535 } 536 c.buf.WriteString(text) 537 return c 538} 539 540// Textf adds the specified formatted text to the command line. The text should not contain input or output paths or 541// the rule will not have them listed in its dependencies or outputs. 542func (c *RuleBuilderCommand) Textf(format string, a ...interface{}) *RuleBuilderCommand { 543 return c.Text(fmt.Sprintf(format, a...)) 544} 545 546// Flag adds the specified raw text to the command line. The text should not contain input or output paths or the 547// rule will not have them listed in its dependencies or outputs. 548func (c *RuleBuilderCommand) Flag(flag string) *RuleBuilderCommand { 549 return c.Text(flag) 550} 551 552// OptionalFlag adds the specified raw text to the command line if it is not nil. The text should not contain input or 553// output paths or the rule will not have them listed in its dependencies or outputs. 554func (c *RuleBuilderCommand) OptionalFlag(flag *string) *RuleBuilderCommand { 555 if flag != nil { 556 c.Text(*flag) 557 } 558 559 return c 560} 561 562// Flags adds the specified raw text to the command line. The text should not contain input or output paths or the 563// rule will not have them listed in its dependencies or outputs. 564func (c *RuleBuilderCommand) Flags(flags []string) *RuleBuilderCommand { 565 for _, flag := range flags { 566 c.Text(flag) 567 } 568 return c 569} 570 571// FlagWithArg adds the specified flag and argument text to the command line, with no separator between them. The flag 572// and argument should not contain input or output paths or the rule will not have them listed in its dependencies or 573// outputs. 574func (c *RuleBuilderCommand) FlagWithArg(flag, arg string) *RuleBuilderCommand { 575 return c.Text(flag + arg) 576} 577 578// FlagForEachArg adds the specified flag joined with each argument to the command line. The result is identical to 579// calling FlagWithArg for argument. 580func (c *RuleBuilderCommand) FlagForEachArg(flag string, args []string) *RuleBuilderCommand { 581 for _, arg := range args { 582 c.FlagWithArg(flag, arg) 583 } 584 return c 585} 586 587// FlagWithList adds the specified flag and list of arguments to the command line, with the arguments joined by sep 588// and no separator between the flag and arguments. The flag and arguments should not contain input or output paths or 589// the rule will not have them listed in its dependencies or outputs. 590func (c *RuleBuilderCommand) FlagWithList(flag string, list []string, sep string) *RuleBuilderCommand { 591 return c.Text(flag + strings.Join(list, sep)) 592} 593 594// Tool adds the specified tool path to the command line. The path will be also added to the dependencies returned by 595// RuleBuilder.Tools. 596func (c *RuleBuilderCommand) Tool(path Path) *RuleBuilderCommand { 597 c.tools = append(c.tools, path) 598 return c.Text(path.String()) 599} 600 601// BuiltTool adds the specified tool path that was built using a host Soong module to the command line. The path will 602// be also added to the dependencies returned by RuleBuilder.Tools. 603// 604// It is equivalent to: 605// cmd.Tool(ctx.Config().HostToolPath(ctx, tool)) 606func (c *RuleBuilderCommand) BuiltTool(ctx PathContext, tool string) *RuleBuilderCommand { 607 return c.Tool(ctx.Config().HostToolPath(ctx, tool)) 608} 609 610// PrebuiltBuildTool adds the specified tool path from prebuils/build-tools. The path will be also added to the 611// dependencies returned by RuleBuilder.Tools. 612// 613// It is equivalent to: 614// cmd.Tool(ctx.Config().PrebuiltBuildTool(ctx, tool)) 615func (c *RuleBuilderCommand) PrebuiltBuildTool(ctx PathContext, tool string) *RuleBuilderCommand { 616 return c.Tool(ctx.Config().PrebuiltBuildTool(ctx, tool)) 617} 618 619// Input adds the specified input path to the command line. The path will also be added to the dependencies returned by 620// RuleBuilder.Inputs. 621func (c *RuleBuilderCommand) Input(path Path) *RuleBuilderCommand { 622 return c.Text(c.addInput(path)) 623} 624 625// Inputs adds the specified input paths to the command line, separated by spaces. The paths will also be added to the 626// dependencies returned by RuleBuilder.Inputs. 627func (c *RuleBuilderCommand) Inputs(paths Paths) *RuleBuilderCommand { 628 for _, path := range paths { 629 c.Input(path) 630 } 631 return c 632} 633 634// Implicit adds the specified input path to the dependencies returned by RuleBuilder.Inputs without modifying the 635// command line. 636func (c *RuleBuilderCommand) Implicit(path Path) *RuleBuilderCommand { 637 c.addImplicit(path) 638 return c 639} 640 641// Implicits adds the specified input paths to the dependencies returned by RuleBuilder.Inputs without modifying the 642// command line. 643func (c *RuleBuilderCommand) Implicits(paths Paths) *RuleBuilderCommand { 644 for _, path := range paths { 645 c.addImplicit(path) 646 } 647 return c 648} 649 650// GetImplicits returns the command's implicit inputs. 651func (c *RuleBuilderCommand) GetImplicits() Paths { 652 return c.implicits 653} 654 655// OrderOnly adds the specified input path to the dependencies returned by RuleBuilder.OrderOnlys 656// without modifying the command line. 657func (c *RuleBuilderCommand) OrderOnly(path Path) *RuleBuilderCommand { 658 c.addOrderOnly(path) 659 return c 660} 661 662// OrderOnlys adds the specified input paths to the dependencies returned by RuleBuilder.OrderOnlys 663// without modifying the command line. 664func (c *RuleBuilderCommand) OrderOnlys(paths Paths) *RuleBuilderCommand { 665 for _, path := range paths { 666 c.addOrderOnly(path) 667 } 668 return c 669} 670 671// Output adds the specified output path to the command line. The path will also be added to the outputs returned by 672// RuleBuilder.Outputs. 673func (c *RuleBuilderCommand) Output(path WritablePath) *RuleBuilderCommand { 674 c.outputs = append(c.outputs, path) 675 return c.Text(c.outputStr(path)) 676} 677 678// Outputs adds the specified output paths to the command line, separated by spaces. The paths will also be added to 679// the outputs returned by RuleBuilder.Outputs. 680func (c *RuleBuilderCommand) Outputs(paths WritablePaths) *RuleBuilderCommand { 681 for _, path := range paths { 682 c.Output(path) 683 } 684 return c 685} 686 687// OutputDir adds the output directory to the command line. This is only available when used with RuleBuilder.Sbox, 688// and will be the temporary output directory managed by sbox, not the final one. 689func (c *RuleBuilderCommand) OutputDir() *RuleBuilderCommand { 690 if !c.sbox { 691 panic("OutputDir only valid with Sbox") 692 } 693 return c.Text("__SBOX_OUT_DIR__") 694} 695 696// DepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles and adds it to the command 697// line, and causes RuleBuilder.Build file to set the depfile flag for ninja. If multiple depfiles are added to 698// commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the depfiles together. 699func (c *RuleBuilderCommand) DepFile(path WritablePath) *RuleBuilderCommand { 700 c.depFiles = append(c.depFiles, path) 701 return c.Text(c.outputStr(path)) 702} 703 704// ImplicitOutput adds the specified output path to the dependencies returned by RuleBuilder.Outputs without modifying 705// the command line. 706func (c *RuleBuilderCommand) ImplicitOutput(path WritablePath) *RuleBuilderCommand { 707 c.outputs = append(c.outputs, path) 708 return c 709} 710 711// ImplicitOutputs adds the specified output paths to the dependencies returned by RuleBuilder.Outputs without modifying 712// the command line. 713func (c *RuleBuilderCommand) ImplicitOutputs(paths WritablePaths) *RuleBuilderCommand { 714 c.outputs = append(c.outputs, paths...) 715 return c 716} 717 718// ImplicitDepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles without modifying 719// the command line, and causes RuleBuilder.Build file to set the depfile flag for ninja. If multiple depfiles 720// are added to commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the 721// depfiles together. 722func (c *RuleBuilderCommand) ImplicitDepFile(path WritablePath) *RuleBuilderCommand { 723 c.depFiles = append(c.depFiles, path) 724 return c 725} 726 727// FlagWithInput adds the specified flag and input path to the command line, with no separator between them. The path 728// will also be added to the dependencies returned by RuleBuilder.Inputs. 729func (c *RuleBuilderCommand) FlagWithInput(flag string, path Path) *RuleBuilderCommand { 730 return c.Text(flag + c.addInput(path)) 731} 732 733// FlagWithInputList adds the specified flag and input paths to the command line, with the inputs joined by sep 734// and no separator between the flag and inputs. The input paths will also be added to the dependencies returned by 735// RuleBuilder.Inputs. 736func (c *RuleBuilderCommand) FlagWithInputList(flag string, paths Paths, sep string) *RuleBuilderCommand { 737 strs := make([]string, len(paths)) 738 for i, path := range paths { 739 strs[i] = c.addInput(path) 740 } 741 return c.FlagWithList(flag, strs, sep) 742} 743 744// FlagForEachInput adds the specified flag joined with each input path to the command line. The input paths will also 745// be added to the dependencies returned by RuleBuilder.Inputs. The result is identical to calling FlagWithInput for 746// each input path. 747func (c *RuleBuilderCommand) FlagForEachInput(flag string, paths Paths) *RuleBuilderCommand { 748 for _, path := range paths { 749 c.FlagWithInput(flag, path) 750 } 751 return c 752} 753 754// FlagWithOutput adds the specified flag and output path to the command line, with no separator between them. The path 755// will also be added to the outputs returned by RuleBuilder.Outputs. 756func (c *RuleBuilderCommand) FlagWithOutput(flag string, path WritablePath) *RuleBuilderCommand { 757 c.outputs = append(c.outputs, path) 758 return c.Text(flag + c.outputStr(path)) 759} 760 761// FlagWithDepFile adds the specified flag and depfile path to the command line, with no separator between them. The path 762// will also be added to the outputs returned by RuleBuilder.Outputs. 763func (c *RuleBuilderCommand) FlagWithDepFile(flag string, path WritablePath) *RuleBuilderCommand { 764 c.depFiles = append(c.depFiles, path) 765 return c.Text(flag + c.outputStr(path)) 766} 767 768// FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with no separator 769// between them. The paths will be written to the rspfile. 770func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, paths Paths) *RuleBuilderCommand { 771 if c.rspFileInputs != nil { 772 panic("FlagWithRspFileInputList cannot be called if rsp file inputs have already been provided") 773 } 774 775 // Use an empty slice if paths is nil, the non-nil slice is used as an indicator that the rsp file must be 776 // generated. 777 if paths == nil { 778 paths = Paths{} 779 } 780 781 c.rspFileInputs = paths 782 783 rspFile := "$out.rsp" 784 c.FlagWithArg(flag, rspFile) 785 c.unescapedSpans = append(c.unescapedSpans, [2]int{c.buf.Len() - len(rspFile), c.buf.Len()}) 786 return c 787} 788 789// String returns the command line. 790func (c *RuleBuilderCommand) String() string { 791 return c.buf.String() 792} 793 794// String returns the command line. 795func (c *RuleBuilderCommand) NinjaEscapedString() string { 796 return ninjaEscapeExceptForSpans(c.String(), c.unescapedSpans) 797} 798 799func ninjaEscapeExceptForSpans(s string, spans [][2]int) string { 800 if len(spans) == 0 { 801 return proptools.NinjaEscape(s) 802 } 803 804 sb := strings.Builder{} 805 sb.Grow(len(s) * 11 / 10) 806 807 i := 0 808 for _, span := range spans { 809 sb.WriteString(proptools.NinjaEscape(s[i:span[0]])) 810 sb.WriteString(s[span[0]:span[1]]) 811 i = span[1] 812 } 813 sb.WriteString(proptools.NinjaEscape(s[i:])) 814 815 return sb.String() 816} 817 818func ninjaNameEscape(s string) string { 819 b := []byte(s) 820 escaped := false 821 for i, c := range b { 822 valid := (c >= 'a' && c <= 'z') || 823 (c >= 'A' && c <= 'Z') || 824 (c >= '0' && c <= '9') || 825 (c == '_') || 826 (c == '-') || 827 (c == '.') 828 if !valid { 829 b[i] = '_' 830 escaped = true 831 } 832 } 833 if escaped { 834 s = string(b) 835 } 836 return s 837} 838