• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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