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:
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user