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
This commit is contained in:
Colin Cross
2019-07-11 10:59:15 -07:00
parent 1563815780
commit 0cb0d7b1c5
2 changed files with 201 additions and 14 deletions

View File

@@ -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,
Rspfile: rspFile,
RspfileContent: rspFileContent,
}),
Inputs: rspFileInputs,
Implicits: r.Inputs(),
Output: output,
ImplicitOutputs: implicitOutputs,
@@ -393,6 +428,10 @@ type RuleBuilderCommand struct {
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

View File

@@ -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)
}
})
}
}