diff options
author | Colin Cross <ccross@android.com> | 2019-07-11 10:59:15 -0700 |
---|---|---|
committer | Colin Cross <ccross@android.com> | 2019-07-16 11:12:04 -0700 |
commit | 0cb0d7b1c5548b763a06cb553b1adb6cb49b3930 (patch) | |
tree | 7c827e4f35e172ff5ecc4fa2035cb16e290bf71f | |
parent | 1563815780ab3ccfda01e63268f5beeab525f368 (diff) | |
download | build_soong-0cb0d7b1c5548b763a06cb553b1adb6cb49b3930.tar.gz build_soong-0cb0d7b1c5548b763a06cb553b1adb6cb49b3930.tar.bz2 build_soong-0cb0d7b1c5548b763a06cb553b1adb6cb49b3930.zip |
Add rspfile support to RuleBuilder
Allow RuleBuilderCommands to use an rspfile with FlagWithRspFileInputList.
This requires being more careful with ninja escaping, as the rspfile
will be $out.rsp, and the value for $out may not be known yet so it
must be inserted as a ninja variable that must not be escaped.
Test: rule_builder_test.go
Change-Id: Ifa91e24a0bb8f0ceeb5c9bfa5689be2a4ff3b9cd
-rw-r--r-- | android/rule_builder.go | 112 | ||||
-rw-r--r-- | android/rule_builder_test.go | 103 |
2 files changed, 201 insertions, 14 deletions
diff --git a/android/rule_builder.go b/android/rule_builder.go index 5797bc26..1238ddc6 100644 --- a/android/rule_builder.go +++ b/android/rule_builder.go @@ -263,11 +263,36 @@ func (r *RuleBuilder) Tools() Paths { return toolsList } -// Commands returns a slice containing a the built command line for each call to RuleBuilder.Command. +// RspFileInputs returns the list of paths that were passed to the RuleBuilderCommand.FlagWithRspFileInputList method. +func (r *RuleBuilder) RspFileInputs() Paths { + var rspFileInputs Paths + for _, c := range r.commands { + if c.rspFileInputs != nil { + if rspFileInputs != nil { + panic("Multiple commands in a rule may not have rsp file inputs") + } + rspFileInputs = c.rspFileInputs + } + } + + return rspFileInputs +} + +// Commands returns a slice containing the built command line for each call to RuleBuilder.Command. func (r *RuleBuilder) Commands() []string { var commands []string for _, c := range r.commands { - commands = append(commands, c.buf.String()) + commands = append(commands, c.String()) + } + return commands +} + +// NinjaEscapedCommands returns a slice containin the built command line after ninja escaping for each call to +// RuleBuilder.Command. +func (r *RuleBuilder) NinjaEscapedCommands() []string { + var commands []string + for _, c := range r.commands { + commands = append(commands, c.NinjaEscapedString()) } return commands } @@ -324,7 +349,7 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string } tools := r.Tools() - commands := r.Commands() + commands := r.NinjaEscapedCommands() outputs := r.Outputs() if len(commands) == 0 { @@ -334,7 +359,7 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string panic("No outputs specified from any Commands") } - commandString := strings.Join(proptools.NinjaEscapeList(commands), " && ") + commandString := strings.Join(commands, " && ") if r.sbox { sboxOutputs := make([]string, len(outputs)) @@ -363,17 +388,27 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string } // Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to - // ImplicitOutputs. RuleBuilder never uses "$out", so the distinction between Outputs and ImplicitOutputs - // doesn't matter. + // ImplicitOutputs. RuleBuilder only uses "$out" for the rsp file location, so the distinction between Outputs and + // ImplicitOutputs doesn't matter. output := outputs[0] implicitOutputs := outputs[1:] + var rspFile, rspFileContent string + rspFileInputs := r.RspFileInputs() + if rspFileInputs != nil { + rspFile = "$out.rsp" + rspFileContent = "$in" + } + ctx.Build(pctx, BuildParams{ Rule: ctx.Rule(pctx, name, blueprint.RuleParams{ - Command: commandString, - CommandDeps: tools.Strings(), - Restat: r.restat, + Command: commandString, + CommandDeps: tools.Strings(), + Restat: r.restat, + Rspfile: rspFile, + RspfileContent: rspFileContent, }), + Inputs: rspFileInputs, Implicits: r.Inputs(), Output: output, ImplicitOutputs: implicitOutputs, @@ -388,11 +423,15 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string // RuleBuilderCommand, so they can be used chained or unchained. All methods that add text implicitly add a single // space as a separator from the previous method. type RuleBuilderCommand struct { - buf strings.Builder - inputs Paths - outputs WritablePaths - depFiles WritablePaths - tools Paths + buf strings.Builder + inputs Paths + outputs WritablePaths + depFiles WritablePaths + tools Paths + rspFileInputs Paths + + // spans [start,end) of the command that should not be ninja escaped + unescapedSpans [][2]int sbox bool sboxOutDir WritablePath @@ -624,11 +663,56 @@ func (c *RuleBuilderCommand) FlagWithDepFile(flag string, path WritablePath) *Ru return c.Text(flag + c.outputStr(path)) } +// FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with no separator +// between them. The paths will be written to the rspfile. +func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, paths Paths) *RuleBuilderCommand { + if c.rspFileInputs != nil { + panic("FlagWithRspFileInputList cannot be called if rsp file inputs have already been provided") + } + + // Use an empty slice if paths is nil, the non-nil slice is used as an indicator that the rsp file must be + // generated. + if paths == nil { + paths = Paths{} + } + + c.rspFileInputs = paths + + rspFile := "$out.rsp" + c.FlagWithArg(flag, rspFile) + c.unescapedSpans = append(c.unescapedSpans, [2]int{c.buf.Len() - len(rspFile), c.buf.Len()}) + return c +} + // String returns the command line. func (c *RuleBuilderCommand) String() string { return c.buf.String() } +// String returns the command line. +func (c *RuleBuilderCommand) NinjaEscapedString() string { + return ninjaEscapeExceptForSpans(c.String(), c.unescapedSpans) +} + +func ninjaEscapeExceptForSpans(s string, spans [][2]int) string { + if len(spans) == 0 { + return proptools.NinjaEscape(s) + } + + sb := strings.Builder{} + sb.Grow(len(s) * 11 / 10) + + i := 0 + for _, span := range spans { + sb.WriteString(proptools.NinjaEscape(s[i:span[0]])) + sb.WriteString(s[span[0]:span[1]]) + i = span[1] + } + sb.WriteString(proptools.NinjaEscape(s[i:])) + + return sb.String() +} + func ninjaNameEscape(s string) string { b := []byte(s) escaped := false diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go index cfbc2abe..6eba4f12 100644 --- a/android/rule_builder_test.go +++ b/android/rule_builder_test.go @@ -38,6 +38,7 @@ func pathContext() PathContext { "ls": nil, "turbine": nil, "java": nil, + "javac": nil, }) } @@ -235,6 +236,34 @@ func ExampleRuleBuilderCommand_FlagWithList() { // ls --sort=time,size } +func ExampleRuleBuilderCommand_FlagWithRspFileInputList() { + ctx := pathContext() + fmt.Println(NewRuleBuilder().Command(). + Tool(PathForSource(ctx, "javac")). + FlagWithRspFileInputList("@", PathsForTesting("a.java", "b.java")). + NinjaEscapedString()) + // Output: + // javac @$out.rsp +} + +func ExampleRuleBuilderCommand_String() { + fmt.Println(NewRuleBuilder().Command(). + Text("FOO=foo"). + Text("echo $FOO"). + String()) + // Output: + // FOO=foo echo $FOO +} + +func ExampleRuleBuilderCommand_NinjaEscapedString() { + fmt.Println(NewRuleBuilder().Command(). + Text("FOO=foo"). + Text("echo $FOO"). + NinjaEscapedString()) + // Output: + // FOO=foo echo $$FOO +} + func TestRuleBuilder(t *testing.T) { fs := map[string][]byte{ "dep_fixer": nil, @@ -503,3 +532,77 @@ func TestRuleBuilder_Build(t *testing.T) { "cp bar "+outFile, outFile, outFile+".d", true, nil) }) } + +func Test_ninjaEscapeExceptForSpans(t *testing.T) { + type args struct { + s string + spans [][2]int + } + tests := []struct { + name string + args args + want string + }{ + { + name: "empty", + args: args{ + s: "", + }, + want: "", + }, + { + name: "unescape none", + args: args{ + s: "$abc", + }, + want: "$$abc", + }, + { + name: "unescape all", + args: args{ + s: "$abc", + spans: [][2]int{{0, 4}}, + }, + want: "$abc", + }, + { + name: "unescape first", + args: args{ + s: "$abc$", + spans: [][2]int{{0, 1}}, + }, + want: "$abc$$", + }, + { + name: "unescape last", + args: args{ + s: "$abc$", + spans: [][2]int{{4, 5}}, + }, + want: "$$abc$", + }, + { + name: "unescape middle", + args: args{ + s: "$a$b$c$", + spans: [][2]int{{2, 5}}, + }, + want: "$$a$b$c$$", + }, + { + name: "unescape multiple", + args: args{ + s: "$a$b$c$", + spans: [][2]int{{2, 3}, {4, 5}}, + }, + want: "$$a$b$c$$", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ninjaEscapeExceptForSpans(tt.args.s, tt.args.spans); got != tt.want { + t.Errorf("ninjaEscapeExceptForSpans() = %v, want %v", got, tt.want) + } + }) + } +} |