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
|
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 {
|
func (r *RuleBuilder) Commands() []string {
|
||||||
var commands []string
|
var commands []string
|
||||||
for _, c := range r.commands {
|
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
|
return commands
|
||||||
}
|
}
|
||||||
@@ -324,7 +349,7 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string
|
|||||||
}
|
}
|
||||||
|
|
||||||
tools := r.Tools()
|
tools := r.Tools()
|
||||||
commands := r.Commands()
|
commands := r.NinjaEscapedCommands()
|
||||||
outputs := r.Outputs()
|
outputs := r.Outputs()
|
||||||
|
|
||||||
if len(commands) == 0 {
|
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")
|
panic("No outputs specified from any Commands")
|
||||||
}
|
}
|
||||||
|
|
||||||
commandString := strings.Join(proptools.NinjaEscapeList(commands), " && ")
|
commandString := strings.Join(commands, " && ")
|
||||||
|
|
||||||
if r.sbox {
|
if r.sbox {
|
||||||
sboxOutputs := make([]string, len(outputs))
|
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
|
// 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
|
// ImplicitOutputs. RuleBuilder only uses "$out" for the rsp file location, so the distinction between Outputs and
|
||||||
// doesn't matter.
|
// ImplicitOutputs doesn't matter.
|
||||||
output := outputs[0]
|
output := outputs[0]
|
||||||
implicitOutputs := outputs[1:]
|
implicitOutputs := outputs[1:]
|
||||||
|
|
||||||
|
var rspFile, rspFileContent string
|
||||||
|
rspFileInputs := r.RspFileInputs()
|
||||||
|
if rspFileInputs != nil {
|
||||||
|
rspFile = "$out.rsp"
|
||||||
|
rspFileContent = "$in"
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Build(pctx, BuildParams{
|
ctx.Build(pctx, BuildParams{
|
||||||
Rule: ctx.Rule(pctx, name, blueprint.RuleParams{
|
Rule: ctx.Rule(pctx, name, blueprint.RuleParams{
|
||||||
Command: commandString,
|
Command: commandString,
|
||||||
CommandDeps: tools.Strings(),
|
CommandDeps: tools.Strings(),
|
||||||
Restat: r.restat,
|
Restat: r.restat,
|
||||||
|
Rspfile: rspFile,
|
||||||
|
RspfileContent: rspFileContent,
|
||||||
}),
|
}),
|
||||||
|
Inputs: rspFileInputs,
|
||||||
Implicits: r.Inputs(),
|
Implicits: r.Inputs(),
|
||||||
Output: output,
|
Output: output,
|
||||||
ImplicitOutputs: implicitOutputs,
|
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
|
// 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.
|
// space as a separator from the previous method.
|
||||||
type RuleBuilderCommand struct {
|
type RuleBuilderCommand struct {
|
||||||
buf strings.Builder
|
buf strings.Builder
|
||||||
inputs Paths
|
inputs Paths
|
||||||
outputs WritablePaths
|
outputs WritablePaths
|
||||||
depFiles WritablePaths
|
depFiles WritablePaths
|
||||||
tools Paths
|
tools Paths
|
||||||
|
rspFileInputs Paths
|
||||||
|
|
||||||
|
// spans [start,end) of the command that should not be ninja escaped
|
||||||
|
unescapedSpans [][2]int
|
||||||
|
|
||||||
sbox bool
|
sbox bool
|
||||||
sboxOutDir WritablePath
|
sboxOutDir WritablePath
|
||||||
@@ -624,11 +663,56 @@ func (c *RuleBuilderCommand) FlagWithDepFile(flag string, path WritablePath) *Ru
|
|||||||
return c.Text(flag + c.outputStr(path))
|
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.
|
// String returns the command line.
|
||||||
func (c *RuleBuilderCommand) String() string {
|
func (c *RuleBuilderCommand) String() string {
|
||||||
return c.buf.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 {
|
func ninjaNameEscape(s string) string {
|
||||||
b := []byte(s)
|
b := []byte(s)
|
||||||
escaped := false
|
escaped := false
|
||||||
|
@@ -38,6 +38,7 @@ func pathContext() PathContext {
|
|||||||
"ls": nil,
|
"ls": nil,
|
||||||
"turbine": nil,
|
"turbine": nil,
|
||||||
"java": nil,
|
"java": nil,
|
||||||
|
"javac": nil,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +236,34 @@ func ExampleRuleBuilderCommand_FlagWithList() {
|
|||||||
// ls --sort=time,size
|
// 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) {
|
func TestRuleBuilder(t *testing.T) {
|
||||||
fs := map[string][]byte{
|
fs := map[string][]byte{
|
||||||
"dep_fixer": nil,
|
"dep_fixer": nil,
|
||||||
@@ -503,3 +532,77 @@ func TestRuleBuilder_Build(t *testing.T) {
|
|||||||
"cp bar "+outFile, outFile, outFile+".d", true, nil)
|
"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