blob: 8dc9d6a42d9ecb8ed78b59ba9bccd1773a6929ec [file] [log] [blame]
Colin Crossfeec25b2019-01-30 17:32:39 -08001// 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"
Colin Crossfeec25b2019-01-30 17:32:39 -080019 "sort"
20 "strings"
21
22 "github.com/google/blueprint"
23 "github.com/google/blueprint/proptools"
Dan Willemsen633c5022019-04-12 11:11:38 -070024
25 "android/soong/shared"
Colin Crossfeec25b2019-01-30 17:32:39 -080026)
27
Colin Cross758290d2019-02-01 16:42:32 -080028// RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build
29// graph.
Colin Crossfeec25b2019-01-30 17:32:39 -080030type RuleBuilder struct {
Colin Cross5cb5b092019-02-02 21:25:18 -080031 commands []*RuleBuilderCommand
Colin Crossdeabb942019-02-11 14:11:09 -080032 installs RuleBuilderInstalls
Colin Cross69f59a32019-02-15 10:39:37 -080033 temporariesSet map[WritablePath]bool
Colin Cross5cb5b092019-02-02 21:25:18 -080034 restat bool
Dan Willemsen633c5022019-04-12 11:11:38 -070035 sbox bool
Colin Cross8b8bec32019-11-15 13:18:43 -080036 highmem bool
37 remoteable RemoteRuleSupports
Dan Willemsen633c5022019-04-12 11:11:38 -070038 sboxOutDir WritablePath
Colin Cross0d2f40a2019-02-05 22:31:15 -080039 missingDeps []string
Colin Crossfeec25b2019-01-30 17:32:39 -080040}
41
Colin Cross758290d2019-02-01 16:42:32 -080042// NewRuleBuilder returns a newly created RuleBuilder.
43func NewRuleBuilder() *RuleBuilder {
Colin Cross5cb5b092019-02-02 21:25:18 -080044 return &RuleBuilder{
Colin Cross69f59a32019-02-15 10:39:37 -080045 temporariesSet: make(map[WritablePath]bool),
Colin Cross5cb5b092019-02-02 21:25:18 -080046 }
Colin Cross758290d2019-02-01 16:42:32 -080047}
48
49// RuleBuilderInstall is a tuple of install from and to locations.
50type RuleBuilderInstall struct {
Colin Cross69f59a32019-02-15 10:39:37 -080051 From Path
52 To string
Colin Cross758290d2019-02-01 16:42:32 -080053}
54
Colin Crossdeabb942019-02-11 14:11:09 -080055type 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 }
Colin Cross69f59a32019-02-15 10:39:37 -080065 sb.WriteString(install.From.String())
Colin Crossdeabb942019-02-11 14:11:09 -080066 sb.WriteRune(':')
67 sb.WriteString(install.To)
68 }
69 return sb.String()
70}
71
Colin Cross0d2f40a2019-02-05 22:31:15 -080072// 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
Colin Cross758290d2019-02-01 16:42:32 -080081// Restat marks the rule as a restat rule, which will be passed to ModuleContext.Rule in BuildParams.Restat.
Dan Willemsen633c5022019-04-12 11:11:38 -070082//
83// Restat is not compatible with Sbox()
Colin Crossfeec25b2019-01-30 17:32:39 -080084func (r *RuleBuilder) Restat() *RuleBuilder {
Dan Willemsen633c5022019-04-12 11:11:38 -070085 if r.sbox {
86 panic("Restat() is not compatible with Sbox()")
87 }
Colin Crossfeec25b2019-01-30 17:32:39 -080088 r.restat = true
89 return r
90}
91
Colin Cross8b8bec32019-11-15 13:18:43 -080092// 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
Dan Willemsen633c5022019-04-12 11:11:38 -0700105// 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
Colin Cross758290d2019-02-01 16:42:32 -0800125// Install associates an output of the rule with an install location, which can be retrieved later using
126// RuleBuilder.Installs.
Colin Cross69f59a32019-02-15 10:39:37 -0800127func (r *RuleBuilder) Install(from Path, to string) {
Colin Crossfeec25b2019-01-30 17:32:39 -0800128 r.installs = append(r.installs, RuleBuilderInstall{from, to})
129}
130
Colin Cross758290d2019-02-01 16:42:32 -0800131// 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.
Colin Crossfeec25b2019-01-30 17:32:39 -0800134func (r *RuleBuilder) Command() *RuleBuilderCommand {
Dan Willemsen633c5022019-04-12 11:11:38 -0700135 command := &RuleBuilderCommand{
136 sbox: r.sbox,
137 sboxOutDir: r.sboxOutDir,
138 }
Colin Crossfeec25b2019-01-30 17:32:39 -0800139 r.commands = append(r.commands, command)
140 return command
141}
142
Colin Cross5cb5b092019-02-02 21:25:18 -0800143// 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.
Colin Cross69f59a32019-02-15 10:39:37 -0800145func (r *RuleBuilder) Temporary(path WritablePath) {
Colin Cross5cb5b092019-02-02 21:25:18 -0800146 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() {
Colin Cross69f59a32019-02-15 10:39:37 -0800152 var temporariesList WritablePaths
Colin Cross5cb5b092019-02-02 21:25:18 -0800153
154 for intermediate := range r.temporariesSet {
155 temporariesList = append(temporariesList, intermediate)
156 }
Colin Cross69f59a32019-02-15 10:39:37 -0800157
158 sort.Slice(temporariesList, func(i, j int) bool {
159 return temporariesList[i].String() < temporariesList[j].String()
160 })
Colin Cross5cb5b092019-02-02 21:25:18 -0800161
162 r.Command().Text("rm").Flag("-f").Outputs(temporariesList)
163}
164
Colin Crossda71eda2020-02-21 16:55:19 -0800165// 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.
Colin Cross69f59a32019-02-15 10:39:37 -0800169func (r *RuleBuilder) Inputs() Paths {
Colin Crossfeec25b2019-01-30 17:32:39 -0800170 outputs := r.outputSet()
Dan Willemsen633c5022019-04-12 11:11:38 -0700171 depFiles := r.depFileSet()
Colin Crossfeec25b2019-01-30 17:32:39 -0800172
Colin Cross69f59a32019-02-15 10:39:37 -0800173 inputs := make(map[string]Path)
Colin Crossfeec25b2019-01-30 17:32:39 -0800174 for _, c := range r.commands {
Ramy Medhat2f99eec2020-06-13 17:38:27 -0400175 for _, input := range append(c.inputs, c.implicits...) {
Dan Willemsen633c5022019-04-12 11:11:38 -0700176 inputStr := input.String()
177 if _, isOutput := outputs[inputStr]; !isOutput {
178 if _, isDepFile := depFiles[inputStr]; !isDepFile {
179 inputs[input.String()] = input
180 }
Colin Crossfeec25b2019-01-30 17:32:39 -0800181 }
182 }
183 }
184
Colin Cross69f59a32019-02-15 10:39:37 -0800185 var inputList Paths
186 for _, input := range inputs {
Colin Crossfeec25b2019-01-30 17:32:39 -0800187 inputList = append(inputList, input)
188 }
Colin Cross69f59a32019-02-15 10:39:37 -0800189
190 sort.Slice(inputList, func(i, j int) bool {
191 return inputList[i].String() < inputList[j].String()
192 })
Colin Crossfeec25b2019-01-30 17:32:39 -0800193
194 return inputList
195}
196
Colin Crossda71eda2020-02-21 16:55:19 -0800197// 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
Colin Cross69f59a32019-02-15 10:39:37 -0800219func (r *RuleBuilder) outputSet() map[string]WritablePath {
220 outputs := make(map[string]WritablePath)
Colin Crossfeec25b2019-01-30 17:32:39 -0800221 for _, c := range r.commands {
222 for _, output := range c.outputs {
Colin Cross69f59a32019-02-15 10:39:37 -0800223 outputs[output.String()] = output
Colin Crossfeec25b2019-01-30 17:32:39 -0800224 }
225 }
226 return outputs
227}
228
Colin Crossda71eda2020-02-21 16:55:19 -0800229// 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.
Colin Cross69f59a32019-02-15 10:39:37 -0800232func (r *RuleBuilder) Outputs() WritablePaths {
Colin Crossfeec25b2019-01-30 17:32:39 -0800233 outputs := r.outputSet()
234
Colin Cross69f59a32019-02-15 10:39:37 -0800235 var outputList WritablePaths
236 for _, output := range outputs {
Colin Cross5cb5b092019-02-02 21:25:18 -0800237 if !r.temporariesSet[output] {
238 outputList = append(outputList, output)
239 }
Colin Crossfeec25b2019-01-30 17:32:39 -0800240 }
Colin Cross69f59a32019-02-15 10:39:37 -0800241
242 sort.Slice(outputList, func(i, j int) bool {
243 return outputList[i].String() < outputList[j].String()
244 })
245
Colin Crossfeec25b2019-01-30 17:32:39 -0800246 return outputList
247}
248
Jingwen Chence679d22020-09-23 04:30:02 +0000249func (r *RuleBuilder) symlinkOutputSet() map[string]WritablePath {
250 symlinkOutputs := make(map[string]WritablePath)
251 for _, c := range r.commands {
252 for _, symlinkOutput := range c.symlinkOutputs {
253 symlinkOutputs[symlinkOutput.String()] = symlinkOutput
254 }
255 }
256 return symlinkOutputs
257}
258
259// SymlinkOutputs returns the list of paths that the executor (Ninja) would
260// verify, after build edge completion, that:
261//
262// 1) Created output symlinks match the list of paths in this list exactly (no more, no fewer)
263// 2) Created output files are *not* declared in this list.
264//
265// These symlink outputs are expected to be a subset of outputs or implicit
266// outputs, or they would fail validation at build param construction time
267// later, to support other non-rule-builder approaches for constructing
268// statements.
269func (r *RuleBuilder) SymlinkOutputs() WritablePaths {
270 symlinkOutputs := r.symlinkOutputSet()
271
272 var symlinkOutputList WritablePaths
273 for _, symlinkOutput := range symlinkOutputs {
274 symlinkOutputList = append(symlinkOutputList, symlinkOutput)
275 }
276
277 sort.Slice(symlinkOutputList, func(i, j int) bool {
278 return symlinkOutputList[i].String() < symlinkOutputList[j].String()
279 })
280
281 return symlinkOutputList
282}
283
Dan Willemsen633c5022019-04-12 11:11:38 -0700284func (r *RuleBuilder) depFileSet() map[string]WritablePath {
285 depFiles := make(map[string]WritablePath)
286 for _, c := range r.commands {
287 for _, depFile := range c.depFiles {
288 depFiles[depFile.String()] = depFile
289 }
290 }
291 return depFiles
292}
293
Colin Cross1d2cf042019-03-29 15:33:06 -0700294// DepFiles returns the list of paths that were passed to the RuleBuilderCommand methods that take depfile paths, such
295// as RuleBuilderCommand.DepFile or RuleBuilderCommand.FlagWithDepFile.
296func (r *RuleBuilder) DepFiles() WritablePaths {
297 var depFiles WritablePaths
298
299 for _, c := range r.commands {
300 for _, depFile := range c.depFiles {
301 depFiles = append(depFiles, depFile)
302 }
303 }
304
305 return depFiles
306}
307
Colin Cross758290d2019-02-01 16:42:32 -0800308// Installs returns the list of tuples passed to Install.
Colin Crossdeabb942019-02-11 14:11:09 -0800309func (r *RuleBuilder) Installs() RuleBuilderInstalls {
310 return append(RuleBuilderInstalls(nil), r.installs...)
Colin Crossfeec25b2019-01-30 17:32:39 -0800311}
312
Colin Cross69f59a32019-02-15 10:39:37 -0800313func (r *RuleBuilder) toolsSet() map[string]Path {
314 tools := make(map[string]Path)
Colin Cross5cb5b092019-02-02 21:25:18 -0800315 for _, c := range r.commands {
316 for _, tool := range c.tools {
Colin Cross69f59a32019-02-15 10:39:37 -0800317 tools[tool.String()] = tool
Colin Cross5cb5b092019-02-02 21:25:18 -0800318 }
319 }
320
321 return tools
322}
323
Colin Crossda71eda2020-02-21 16:55:19 -0800324// Tools returns the list of paths that were passed to the RuleBuilderCommand.Tool method. The
325// list is sorted and duplicates removed.
Colin Cross69f59a32019-02-15 10:39:37 -0800326func (r *RuleBuilder) Tools() Paths {
Colin Cross5cb5b092019-02-02 21:25:18 -0800327 toolsSet := r.toolsSet()
328
Colin Cross69f59a32019-02-15 10:39:37 -0800329 var toolsList Paths
330 for _, tool := range toolsSet {
Colin Cross5cb5b092019-02-02 21:25:18 -0800331 toolsList = append(toolsList, tool)
Colin Crossfeec25b2019-01-30 17:32:39 -0800332 }
Colin Cross69f59a32019-02-15 10:39:37 -0800333
334 sort.Slice(toolsList, func(i, j int) bool {
335 return toolsList[i].String() < toolsList[j].String()
336 })
337
Colin Cross5cb5b092019-02-02 21:25:18 -0800338 return toolsList
Colin Crossfeec25b2019-01-30 17:32:39 -0800339}
340
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700341// RspFileInputs returns the list of paths that were passed to the RuleBuilderCommand.FlagWithRspFileInputList method.
342func (r *RuleBuilder) RspFileInputs() Paths {
343 var rspFileInputs Paths
344 for _, c := range r.commands {
345 if c.rspFileInputs != nil {
346 if rspFileInputs != nil {
347 panic("Multiple commands in a rule may not have rsp file inputs")
348 }
349 rspFileInputs = c.rspFileInputs
350 }
351 }
352
353 return rspFileInputs
354}
355
356// Commands returns a slice containing the built command line for each call to RuleBuilder.Command.
Colin Crossfeec25b2019-01-30 17:32:39 -0800357func (r *RuleBuilder) Commands() []string {
358 var commands []string
359 for _, c := range r.commands {
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700360 commands = append(commands, c.String())
361 }
362 return commands
363}
364
365// NinjaEscapedCommands returns a slice containin the built command line after ninja escaping for each call to
366// RuleBuilder.Command.
367func (r *RuleBuilder) NinjaEscapedCommands() []string {
368 var commands []string
369 for _, c := range r.commands {
370 commands = append(commands, c.NinjaEscapedString())
Colin Crossfeec25b2019-01-30 17:32:39 -0800371 }
372 return commands
373}
374
Colin Cross758290d2019-02-01 16:42:32 -0800375// BuilderContext is a subset of ModuleContext and SingletonContext.
Colin Cross786cd6d2019-02-01 16:41:11 -0800376type BuilderContext interface {
377 PathContext
378 Rule(PackageContext, string, blueprint.RuleParams, ...string) blueprint.Rule
379 Build(PackageContext, BuildParams)
380}
381
Colin Cross758290d2019-02-01 16:42:32 -0800382var _ BuilderContext = ModuleContext(nil)
383var _ BuilderContext = SingletonContext(nil)
384
Colin Cross1d2cf042019-03-29 15:33:06 -0700385func (r *RuleBuilder) depFileMergerCmd(ctx PathContext, depFiles WritablePaths) *RuleBuilderCommand {
Dan Willemsen633c5022019-04-12 11:11:38 -0700386 return r.Command().
Colin Crossee94d6a2019-07-08 17:08:34 -0700387 BuiltTool(ctx, "dep_fixer").
Dan Willemsen633c5022019-04-12 11:11:38 -0700388 Inputs(depFiles.Paths())
Colin Cross1d2cf042019-03-29 15:33:06 -0700389}
390
Colin Cross758290d2019-02-01 16:42:32 -0800391// Build adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for
392// Outputs.
Colin Cross786cd6d2019-02-01 16:41:11 -0800393func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string, desc string) {
Colin Cross1d2cf042019-03-29 15:33:06 -0700394 name = ninjaNameEscape(name)
395
Colin Cross0d2f40a2019-02-05 22:31:15 -0800396 if len(r.missingDeps) > 0 {
397 ctx.Build(pctx, BuildParams{
398 Rule: ErrorRule,
Colin Cross69f59a32019-02-15 10:39:37 -0800399 Outputs: r.Outputs(),
Colin Crossda71eda2020-02-21 16:55:19 -0800400 OrderOnly: r.OrderOnlys(),
Colin Cross0d2f40a2019-02-05 22:31:15 -0800401 Description: desc,
402 Args: map[string]string{
403 "error": "missing dependencies: " + strings.Join(r.missingDeps, ", "),
404 },
405 })
406 return
407 }
408
Colin Cross1d2cf042019-03-29 15:33:06 -0700409 var depFile WritablePath
410 var depFormat blueprint.Deps
411 if depFiles := r.DepFiles(); len(depFiles) > 0 {
412 depFile = depFiles[0]
413 depFormat = blueprint.DepsGCC
414 if len(depFiles) > 1 {
415 // Add a command locally that merges all depfiles together into the first depfile.
Dan Willemsen633c5022019-04-12 11:11:38 -0700416 r.depFileMergerCmd(ctx, depFiles)
417
418 if r.sbox {
419 // Check for Rel() errors, as all depfiles should be in the output dir
420 for _, path := range depFiles[1:] {
421 Rel(ctx, r.sboxOutDir.String(), path.String())
422 }
423 }
Colin Cross1d2cf042019-03-29 15:33:06 -0700424 }
425 }
426
Dan Willemsen633c5022019-04-12 11:11:38 -0700427 tools := r.Tools()
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700428 commands := r.NinjaEscapedCommands()
Dan Willemsen633c5022019-04-12 11:11:38 -0700429 outputs := r.Outputs()
430
431 if len(commands) == 0 {
432 return
433 }
434 if len(outputs) == 0 {
435 panic("No outputs specified from any Commands")
436 }
437
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700438 commandString := strings.Join(commands, " && ")
Dan Willemsen633c5022019-04-12 11:11:38 -0700439
440 if r.sbox {
441 sboxOutputs := make([]string, len(outputs))
442 for i, output := range outputs {
443 sboxOutputs[i] = "__SBOX_OUT_DIR__/" + Rel(ctx, r.sboxOutDir.String(), output.String())
444 }
445
Dan Willemsen633c5022019-04-12 11:11:38 -0700446 commandString = proptools.ShellEscape(commandString)
447 if !strings.HasPrefix(commandString, `'`) {
448 commandString = `'` + commandString + `'`
449 }
450
451 sboxCmd := &RuleBuilderCommand{}
Colin Crossee94d6a2019-07-08 17:08:34 -0700452 sboxCmd.BuiltTool(ctx, "sbox").
Dan Willemsen633c5022019-04-12 11:11:38 -0700453 Flag("-c").Text(commandString).
454 Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())).
Dan Willemsenc89b6f12019-08-29 14:47:40 -0700455 Flag("--output-root").Text(r.sboxOutDir.String())
456
457 if depFile != nil {
458 sboxCmd.Flag("--depfile-out").Text(depFile.String())
459 }
460
461 sboxCmd.Flags(sboxOutputs)
Dan Willemsen633c5022019-04-12 11:11:38 -0700462
Colin Crosscfec40c2019-07-08 17:07:18 -0700463 commandString = sboxCmd.buf.String()
Dan Willemsen633c5022019-04-12 11:11:38 -0700464 tools = append(tools, sboxCmd.tools...)
465 }
466
Colin Cross1d2cf042019-03-29 15:33:06 -0700467 // Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700468 // ImplicitOutputs. RuleBuilder only uses "$out" for the rsp file location, so the distinction between Outputs and
469 // ImplicitOutputs doesn't matter.
Dan Willemsen633c5022019-04-12 11:11:38 -0700470 output := outputs[0]
471 implicitOutputs := outputs[1:]
Colin Cross1d2cf042019-03-29 15:33:06 -0700472
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700473 var rspFile, rspFileContent string
474 rspFileInputs := r.RspFileInputs()
475 if rspFileInputs != nil {
476 rspFile = "$out.rsp"
477 rspFileContent = "$in"
478 }
479
Colin Cross8b8bec32019-11-15 13:18:43 -0800480 var pool blueprint.Pool
Ramy Medhat8ea054a2020-01-27 14:19:44 -0500481 if ctx.Config().UseGoma() && r.remoteable.Goma {
Colin Cross8b8bec32019-11-15 13:18:43 -0800482 // When USE_GOMA=true is set and the rule is supported by goma, allow jobs to run outside the local pool.
Ramy Medhat8ea054a2020-01-27 14:19:44 -0500483 } else if ctx.Config().UseRBE() && r.remoteable.RBE {
Ramy Medhat944839a2020-03-31 22:14:52 -0400484 // When USE_RBE=true is set and the rule is supported by RBE, use the remotePool.
485 pool = remotePool
Colin Cross8b8bec32019-11-15 13:18:43 -0800486 } else if r.highmem {
487 pool = highmemPool
488 } else if ctx.Config().UseRemoteBuild() {
489 pool = localPool
490 }
491
Dan Willemsen633c5022019-04-12 11:11:38 -0700492 ctx.Build(pctx, BuildParams{
493 Rule: ctx.Rule(pctx, name, blueprint.RuleParams{
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700494 Command: commandString,
495 CommandDeps: tools.Strings(),
496 Restat: r.restat,
497 Rspfile: rspFile,
498 RspfileContent: rspFileContent,
Colin Cross8b8bec32019-11-15 13:18:43 -0800499 Pool: pool,
Dan Willemsen633c5022019-04-12 11:11:38 -0700500 }),
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700501 Inputs: rspFileInputs,
Dan Willemsen633c5022019-04-12 11:11:38 -0700502 Implicits: r.Inputs(),
503 Output: output,
504 ImplicitOutputs: implicitOutputs,
Jingwen Chence679d22020-09-23 04:30:02 +0000505 SymlinkOutputs: r.SymlinkOutputs(),
Dan Willemsen633c5022019-04-12 11:11:38 -0700506 Depfile: depFile,
507 Deps: depFormat,
508 Description: desc,
509 })
Colin Crossfeec25b2019-01-30 17:32:39 -0800510}
511
Colin Cross758290d2019-02-01 16:42:32 -0800512// RuleBuilderCommand is a builder for a command in a command line. It can be mutated by its methods to add to the
513// command and track dependencies. The methods mutate the RuleBuilderCommand in place, as well as return the
514// RuleBuilderCommand, so they can be used chained or unchained. All methods that add text implicitly add a single
515// space as a separator from the previous method.
Colin Crossfeec25b2019-01-30 17:32:39 -0800516type RuleBuilderCommand struct {
Jingwen Chence679d22020-09-23 04:30:02 +0000517 buf strings.Builder
518 inputs Paths
519 implicits Paths
520 orderOnlys Paths
521 outputs WritablePaths
522 symlinkOutputs WritablePaths
523 depFiles WritablePaths
524 tools Paths
525 rspFileInputs Paths
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700526
527 // spans [start,end) of the command that should not be ninja escaped
528 unescapedSpans [][2]int
Dan Willemsen633c5022019-04-12 11:11:38 -0700529
530 sbox bool
531 sboxOutDir WritablePath
532}
533
534func (c *RuleBuilderCommand) addInput(path Path) string {
535 if c.sbox {
536 if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel {
537 return "__SBOX_OUT_DIR__/" + rel
538 }
539 }
540 c.inputs = append(c.inputs, path)
541 return path.String()
542}
543
Ramy Medhat2f99eec2020-06-13 17:38:27 -0400544func (c *RuleBuilderCommand) addImplicit(path Path) string {
545 if c.sbox {
546 if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel {
547 return "__SBOX_OUT_DIR__/" + rel
548 }
549 }
550 c.implicits = append(c.implicits, path)
551 return path.String()
552}
553
Colin Crossda71eda2020-02-21 16:55:19 -0800554func (c *RuleBuilderCommand) addOrderOnly(path Path) {
555 c.orderOnlys = append(c.orderOnlys, path)
556}
557
Dan Willemsen633c5022019-04-12 11:11:38 -0700558func (c *RuleBuilderCommand) outputStr(path Path) string {
559 if c.sbox {
560 // Errors will be handled in RuleBuilder.Build where we have a context to report them
561 rel, _, _ := maybeRelErr(c.sboxOutDir.String(), path.String())
562 return "__SBOX_OUT_DIR__/" + rel
563 }
564 return path.String()
Colin Crossfeec25b2019-01-30 17:32:39 -0800565}
566
Colin Cross758290d2019-02-01 16:42:32 -0800567// Text adds the specified raw text to the command line. The text should not contain input or output paths or the
568// rule will not have them listed in its dependencies or outputs.
Colin Crossfeec25b2019-01-30 17:32:39 -0800569func (c *RuleBuilderCommand) Text(text string) *RuleBuilderCommand {
Colin Crosscfec40c2019-07-08 17:07:18 -0700570 if c.buf.Len() > 0 {
571 c.buf.WriteByte(' ')
Colin Crossfeec25b2019-01-30 17:32:39 -0800572 }
Colin Crosscfec40c2019-07-08 17:07:18 -0700573 c.buf.WriteString(text)
Colin Crossfeec25b2019-01-30 17:32:39 -0800574 return c
575}
576
Colin Cross758290d2019-02-01 16:42:32 -0800577// Textf adds the specified formatted text to the command line. The text should not contain input or output paths or
578// the rule will not have them listed in its dependencies or outputs.
Colin Crossfeec25b2019-01-30 17:32:39 -0800579func (c *RuleBuilderCommand) Textf(format string, a ...interface{}) *RuleBuilderCommand {
580 return c.Text(fmt.Sprintf(format, a...))
581}
582
Colin Cross758290d2019-02-01 16:42:32 -0800583// Flag adds the specified raw text to the command line. The text should not contain input or output paths or the
584// rule will not have them listed in its dependencies or outputs.
Colin Crossfeec25b2019-01-30 17:32:39 -0800585func (c *RuleBuilderCommand) Flag(flag string) *RuleBuilderCommand {
586 return c.Text(flag)
587}
588
Colin Crossab054432019-07-15 16:13:59 -0700589// OptionalFlag adds the specified raw text to the command line if it is not nil. The text should not contain input or
590// output paths or the rule will not have them listed in its dependencies or outputs.
591func (c *RuleBuilderCommand) OptionalFlag(flag *string) *RuleBuilderCommand {
592 if flag != nil {
593 c.Text(*flag)
594 }
595
596 return c
597}
598
Colin Cross92b7d582019-03-29 15:32:51 -0700599// Flags adds the specified raw text to the command line. The text should not contain input or output paths or the
600// rule will not have them listed in its dependencies or outputs.
601func (c *RuleBuilderCommand) Flags(flags []string) *RuleBuilderCommand {
602 for _, flag := range flags {
603 c.Text(flag)
604 }
605 return c
606}
607
Colin Cross758290d2019-02-01 16:42:32 -0800608// FlagWithArg adds the specified flag and argument text to the command line, with no separator between them. The flag
609// and argument should not contain input or output paths or the rule will not have them listed in its dependencies or
610// outputs.
Colin Crossfeec25b2019-01-30 17:32:39 -0800611func (c *RuleBuilderCommand) FlagWithArg(flag, arg string) *RuleBuilderCommand {
612 return c.Text(flag + arg)
613}
614
Colin Crossc7ed0042019-02-11 14:11:09 -0800615// FlagForEachArg adds the specified flag joined with each argument to the command line. The result is identical to
616// calling FlagWithArg for argument.
617func (c *RuleBuilderCommand) FlagForEachArg(flag string, args []string) *RuleBuilderCommand {
618 for _, arg := range args {
619 c.FlagWithArg(flag, arg)
620 }
621 return c
622}
623
Roland Levillain2da5d9a2019-02-27 16:56:41 +0000624// FlagWithList adds the specified flag and list of arguments to the command line, with the arguments joined by sep
Colin Cross758290d2019-02-01 16:42:32 -0800625// and no separator between the flag and arguments. The flag and arguments should not contain input or output paths or
626// the rule will not have them listed in its dependencies or outputs.
Colin Crossfeec25b2019-01-30 17:32:39 -0800627func (c *RuleBuilderCommand) FlagWithList(flag string, list []string, sep string) *RuleBuilderCommand {
628 return c.Text(flag + strings.Join(list, sep))
629}
630
Colin Cross758290d2019-02-01 16:42:32 -0800631// Tool adds the specified tool path to the command line. The path will be also added to the dependencies returned by
632// RuleBuilder.Tools.
Colin Cross69f59a32019-02-15 10:39:37 -0800633func (c *RuleBuilderCommand) Tool(path Path) *RuleBuilderCommand {
Colin Crossfeec25b2019-01-30 17:32:39 -0800634 c.tools = append(c.tools, path)
Colin Cross69f59a32019-02-15 10:39:37 -0800635 return c.Text(path.String())
Colin Crossfeec25b2019-01-30 17:32:39 -0800636}
637
Colin Crossee94d6a2019-07-08 17:08:34 -0700638// BuiltTool adds the specified tool path that was built using a host Soong module to the command line. The path will
639// be also added to the dependencies returned by RuleBuilder.Tools.
640//
641// It is equivalent to:
642// cmd.Tool(ctx.Config().HostToolPath(ctx, tool))
643func (c *RuleBuilderCommand) BuiltTool(ctx PathContext, tool string) *RuleBuilderCommand {
644 return c.Tool(ctx.Config().HostToolPath(ctx, tool))
645}
646
647// PrebuiltBuildTool adds the specified tool path from prebuils/build-tools. The path will be also added to the
648// dependencies returned by RuleBuilder.Tools.
649//
650// It is equivalent to:
651// cmd.Tool(ctx.Config().PrebuiltBuildTool(ctx, tool))
652func (c *RuleBuilderCommand) PrebuiltBuildTool(ctx PathContext, tool string) *RuleBuilderCommand {
653 return c.Tool(ctx.Config().PrebuiltBuildTool(ctx, tool))
654}
655
Colin Cross758290d2019-02-01 16:42:32 -0800656// Input adds the specified input path to the command line. The path will also be added to the dependencies returned by
657// RuleBuilder.Inputs.
Colin Cross69f59a32019-02-15 10:39:37 -0800658func (c *RuleBuilderCommand) Input(path Path) *RuleBuilderCommand {
Dan Willemsen633c5022019-04-12 11:11:38 -0700659 return c.Text(c.addInput(path))
Colin Crossfeec25b2019-01-30 17:32:39 -0800660}
661
Colin Cross758290d2019-02-01 16:42:32 -0800662// Inputs adds the specified input paths to the command line, separated by spaces. The paths will also be added to the
663// dependencies returned by RuleBuilder.Inputs.
Colin Cross69f59a32019-02-15 10:39:37 -0800664func (c *RuleBuilderCommand) Inputs(paths Paths) *RuleBuilderCommand {
Colin Cross758290d2019-02-01 16:42:32 -0800665 for _, path := range paths {
666 c.Input(path)
667 }
668 return c
669}
670
671// Implicit adds the specified input path to the dependencies returned by RuleBuilder.Inputs without modifying the
672// command line.
Colin Cross69f59a32019-02-15 10:39:37 -0800673func (c *RuleBuilderCommand) Implicit(path Path) *RuleBuilderCommand {
Ramy Medhat2f99eec2020-06-13 17:38:27 -0400674 c.addImplicit(path)
Colin Crossfeec25b2019-01-30 17:32:39 -0800675 return c
676}
677
Colin Cross758290d2019-02-01 16:42:32 -0800678// Implicits adds the specified input paths to the dependencies returned by RuleBuilder.Inputs without modifying the
679// command line.
Colin Cross69f59a32019-02-15 10:39:37 -0800680func (c *RuleBuilderCommand) Implicits(paths Paths) *RuleBuilderCommand {
Dan Willemsen633c5022019-04-12 11:11:38 -0700681 for _, path := range paths {
Ramy Medhat2f99eec2020-06-13 17:38:27 -0400682 c.addImplicit(path)
Dan Willemsen633c5022019-04-12 11:11:38 -0700683 }
Colin Crossfeec25b2019-01-30 17:32:39 -0800684 return c
685}
686
Ramy Medhat2f99eec2020-06-13 17:38:27 -0400687// GetImplicits returns the command's implicit inputs.
688func (c *RuleBuilderCommand) GetImplicits() Paths {
689 return c.implicits
690}
691
Colin Crossda71eda2020-02-21 16:55:19 -0800692// OrderOnly adds the specified input path to the dependencies returned by RuleBuilder.OrderOnlys
693// without modifying the command line.
694func (c *RuleBuilderCommand) OrderOnly(path Path) *RuleBuilderCommand {
695 c.addOrderOnly(path)
696 return c
697}
698
699// OrderOnlys adds the specified input paths to the dependencies returned by RuleBuilder.OrderOnlys
700// without modifying the command line.
701func (c *RuleBuilderCommand) OrderOnlys(paths Paths) *RuleBuilderCommand {
702 for _, path := range paths {
703 c.addOrderOnly(path)
704 }
705 return c
706}
707
Colin Cross758290d2019-02-01 16:42:32 -0800708// Output adds the specified output path to the command line. The path will also be added to the outputs returned by
709// RuleBuilder.Outputs.
Colin Cross69f59a32019-02-15 10:39:37 -0800710func (c *RuleBuilderCommand) Output(path WritablePath) *RuleBuilderCommand {
Colin Crossfeec25b2019-01-30 17:32:39 -0800711 c.outputs = append(c.outputs, path)
Dan Willemsen633c5022019-04-12 11:11:38 -0700712 return c.Text(c.outputStr(path))
Colin Crossfeec25b2019-01-30 17:32:39 -0800713}
714
Colin Cross758290d2019-02-01 16:42:32 -0800715// Outputs adds the specified output paths to the command line, separated by spaces. The paths will also be added to
716// the outputs returned by RuleBuilder.Outputs.
Colin Cross69f59a32019-02-15 10:39:37 -0800717func (c *RuleBuilderCommand) Outputs(paths WritablePaths) *RuleBuilderCommand {
Colin Cross758290d2019-02-01 16:42:32 -0800718 for _, path := range paths {
719 c.Output(path)
720 }
721 return c
722}
723
Dan Willemsen1945a4b2019-06-04 17:10:41 -0700724// OutputDir adds the output directory to the command line. This is only available when used with RuleBuilder.Sbox,
725// and will be the temporary output directory managed by sbox, not the final one.
726func (c *RuleBuilderCommand) OutputDir() *RuleBuilderCommand {
727 if !c.sbox {
728 panic("OutputDir only valid with Sbox")
729 }
730 return c.Text("__SBOX_OUT_DIR__")
731}
732
Colin Cross1d2cf042019-03-29 15:33:06 -0700733// DepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles and adds it to the command
734// line, and causes RuleBuilder.Build file to set the depfile flag for ninja. If multiple depfiles are added to
735// commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the depfiles together.
736func (c *RuleBuilderCommand) DepFile(path WritablePath) *RuleBuilderCommand {
737 c.depFiles = append(c.depFiles, path)
Dan Willemsen633c5022019-04-12 11:11:38 -0700738 return c.Text(c.outputStr(path))
Colin Cross1d2cf042019-03-29 15:33:06 -0700739}
740
Colin Cross758290d2019-02-01 16:42:32 -0800741// ImplicitOutput adds the specified output path to the dependencies returned by RuleBuilder.Outputs without modifying
742// the command line.
Colin Cross69f59a32019-02-15 10:39:37 -0800743func (c *RuleBuilderCommand) ImplicitOutput(path WritablePath) *RuleBuilderCommand {
Colin Crossfeec25b2019-01-30 17:32:39 -0800744 c.outputs = append(c.outputs, path)
745 return c
746}
747
Colin Cross758290d2019-02-01 16:42:32 -0800748// ImplicitOutputs adds the specified output paths to the dependencies returned by RuleBuilder.Outputs without modifying
749// the command line.
Colin Cross69f59a32019-02-15 10:39:37 -0800750func (c *RuleBuilderCommand) ImplicitOutputs(paths WritablePaths) *RuleBuilderCommand {
Colin Cross758290d2019-02-01 16:42:32 -0800751 c.outputs = append(c.outputs, paths...)
752 return c
753}
754
Jingwen Chence679d22020-09-23 04:30:02 +0000755// ImplicitSymlinkOutput declares the specified path as an implicit output that
756// will be a symlink instead of a regular file. Does not modify the command
757// line.
758func (c *RuleBuilderCommand) ImplicitSymlinkOutput(path WritablePath) *RuleBuilderCommand {
759 c.symlinkOutputs = append(c.symlinkOutputs, path)
760 return c.ImplicitOutput(path)
761}
762
763// ImplicitSymlinkOutputs declares the specified paths as implicit outputs that
764// will be a symlinks instead of regular files. Does not modify the command
765// line.
766func (c *RuleBuilderCommand) ImplicitSymlinkOutputs(paths WritablePaths) *RuleBuilderCommand {
767 for _, path := range paths {
768 c.ImplicitSymlinkOutput(path)
769 }
770 return c
771}
772
773// SymlinkOutput declares the specified path as an output that will be a symlink
774// instead of a regular file. Modifies the command line.
775func (c *RuleBuilderCommand) SymlinkOutput(path WritablePath) *RuleBuilderCommand {
776 c.symlinkOutputs = append(c.symlinkOutputs, path)
777 return c.Output(path)
778}
779
780// SymlinkOutputsl declares the specified paths as outputs that will be symlinks
781// instead of regular files. Modifies the command line.
782func (c *RuleBuilderCommand) SymlinkOutputs(paths WritablePaths) *RuleBuilderCommand {
783 for _, path := range paths {
784 c.SymlinkOutput(path)
785 }
786 return c
787}
788
Colin Cross1d2cf042019-03-29 15:33:06 -0700789// ImplicitDepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles without modifying
790// the command line, and causes RuleBuilder.Build file to set the depfile flag for ninja. If multiple depfiles
791// are added to commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the
792// depfiles together.
793func (c *RuleBuilderCommand) ImplicitDepFile(path WritablePath) *RuleBuilderCommand {
794 c.depFiles = append(c.depFiles, path)
795 return c
796}
797
Colin Cross758290d2019-02-01 16:42:32 -0800798// FlagWithInput adds the specified flag and input path to the command line, with no separator between them. The path
799// will also be added to the dependencies returned by RuleBuilder.Inputs.
Colin Cross69f59a32019-02-15 10:39:37 -0800800func (c *RuleBuilderCommand) FlagWithInput(flag string, path Path) *RuleBuilderCommand {
Dan Willemsen633c5022019-04-12 11:11:38 -0700801 return c.Text(flag + c.addInput(path))
Colin Crossfeec25b2019-01-30 17:32:39 -0800802}
803
Colin Cross758290d2019-02-01 16:42:32 -0800804// FlagWithInputList adds the specified flag and input paths to the command line, with the inputs joined by sep
805// and no separator between the flag and inputs. The input paths will also be added to the dependencies returned by
806// RuleBuilder.Inputs.
Colin Cross69f59a32019-02-15 10:39:37 -0800807func (c *RuleBuilderCommand) FlagWithInputList(flag string, paths Paths, sep string) *RuleBuilderCommand {
Dan Willemsen633c5022019-04-12 11:11:38 -0700808 strs := make([]string, len(paths))
809 for i, path := range paths {
810 strs[i] = c.addInput(path)
811 }
812 return c.FlagWithList(flag, strs, sep)
Colin Crossfeec25b2019-01-30 17:32:39 -0800813}
814
Colin Cross758290d2019-02-01 16:42:32 -0800815// FlagForEachInput adds the specified flag joined with each input path to the command line. The input paths will also
816// be added to the dependencies returned by RuleBuilder.Inputs. The result is identical to calling FlagWithInput for
817// each input path.
Colin Cross69f59a32019-02-15 10:39:37 -0800818func (c *RuleBuilderCommand) FlagForEachInput(flag string, paths Paths) *RuleBuilderCommand {
Colin Cross758290d2019-02-01 16:42:32 -0800819 for _, path := range paths {
820 c.FlagWithInput(flag, path)
821 }
822 return c
823}
824
825// FlagWithOutput adds the specified flag and output path to the command line, with no separator between them. The path
826// will also be added to the outputs returned by RuleBuilder.Outputs.
Colin Cross69f59a32019-02-15 10:39:37 -0800827func (c *RuleBuilderCommand) FlagWithOutput(flag string, path WritablePath) *RuleBuilderCommand {
Colin Crossfeec25b2019-01-30 17:32:39 -0800828 c.outputs = append(c.outputs, path)
Dan Willemsen633c5022019-04-12 11:11:38 -0700829 return c.Text(flag + c.outputStr(path))
Colin Crossfeec25b2019-01-30 17:32:39 -0800830}
831
Colin Cross1d2cf042019-03-29 15:33:06 -0700832// FlagWithDepFile adds the specified flag and depfile path to the command line, with no separator between them. The path
833// will also be added to the outputs returned by RuleBuilder.Outputs.
834func (c *RuleBuilderCommand) FlagWithDepFile(flag string, path WritablePath) *RuleBuilderCommand {
835 c.depFiles = append(c.depFiles, path)
Dan Willemsen633c5022019-04-12 11:11:38 -0700836 return c.Text(flag + c.outputStr(path))
Colin Cross1d2cf042019-03-29 15:33:06 -0700837}
838
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700839// FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with no separator
840// between them. The paths will be written to the rspfile.
841func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, paths Paths) *RuleBuilderCommand {
842 if c.rspFileInputs != nil {
843 panic("FlagWithRspFileInputList cannot be called if rsp file inputs have already been provided")
844 }
845
846 // Use an empty slice if paths is nil, the non-nil slice is used as an indicator that the rsp file must be
847 // generated.
848 if paths == nil {
849 paths = Paths{}
850 }
851
852 c.rspFileInputs = paths
853
854 rspFile := "$out.rsp"
855 c.FlagWithArg(flag, rspFile)
856 c.unescapedSpans = append(c.unescapedSpans, [2]int{c.buf.Len() - len(rspFile), c.buf.Len()})
857 return c
858}
859
Colin Cross758290d2019-02-01 16:42:32 -0800860// String returns the command line.
861func (c *RuleBuilderCommand) String() string {
Colin Crosscfec40c2019-07-08 17:07:18 -0700862 return c.buf.String()
Colin Cross758290d2019-02-01 16:42:32 -0800863}
Colin Cross1d2cf042019-03-29 15:33:06 -0700864
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700865// String returns the command line.
866func (c *RuleBuilderCommand) NinjaEscapedString() string {
867 return ninjaEscapeExceptForSpans(c.String(), c.unescapedSpans)
868}
869
870func ninjaEscapeExceptForSpans(s string, spans [][2]int) string {
871 if len(spans) == 0 {
872 return proptools.NinjaEscape(s)
873 }
874
875 sb := strings.Builder{}
876 sb.Grow(len(s) * 11 / 10)
877
878 i := 0
879 for _, span := range spans {
880 sb.WriteString(proptools.NinjaEscape(s[i:span[0]]))
881 sb.WriteString(s[span[0]:span[1]])
882 i = span[1]
883 }
884 sb.WriteString(proptools.NinjaEscape(s[i:]))
885
886 return sb.String()
887}
888
Colin Cross1d2cf042019-03-29 15:33:06 -0700889func ninjaNameEscape(s string) string {
890 b := []byte(s)
891 escaped := false
892 for i, c := range b {
893 valid := (c >= 'a' && c <= 'z') ||
894 (c >= 'A' && c <= 'Z') ||
895 (c >= '0' && c <= '9') ||
896 (c == '_') ||
897 (c == '-') ||
898 (c == '.')
899 if !valid {
900 b[i] = '_'
901 escaped = true
902 }
903 }
904 if escaped {
905 s = string(b)
906 }
907 return s
908}