Support multiple rsp files in RuleBuilder

The lint rule is manually creating a second rsp file because Ninja
only supports on per rule.  Move the support into RuleBuilder so
that it can apply the same rewrites that it does to the primary
one.

Test: TestRuleBuilder_Build
Change-Id: Iec250a2d60e74ccf1b4ad085a960fec6867ea559
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 4813d7a..72c0d10 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -409,30 +409,21 @@
 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
+		for _, rspFile := range c.rspFiles {
+			rspFileInputs = append(rspFileInputs, rspFile.paths...)
 		}
 	}
 
 	return rspFileInputs
 }
 
-// RspFile returns the path to the rspfile that was passed to the RuleBuilderCommand.FlagWithRspFileInputList method.
-func (r *RuleBuilder) RspFile() WritablePath {
-	var rspFile WritablePath
+func (r *RuleBuilder) rspFiles() []rspFileAndPaths {
+	var rspFiles []rspFileAndPaths
 	for _, c := range r.commands {
-		if c.rspFile != nil {
-			if rspFile != nil {
-				panic("Multiple commands in a rule may not have rsp file inputs")
-			}
-			rspFile = c.rspFile
-		}
+		rspFiles = append(rspFiles, c.rspFiles...)
 	}
 
-	return rspFile
+	return rspFiles
 }
 
 // Commands returns a slice containing the built command line for each call to RuleBuilder.Command.
@@ -501,8 +492,7 @@
 	commands := r.Commands()
 	outputs := r.Outputs()
 	inputs := r.Inputs()
-	rspFileInputs := r.RspFileInputs()
-	rspFilePath := r.RspFile()
+	rspFiles := r.rspFiles()
 
 	if len(commands) == 0 {
 		return
@@ -556,10 +546,11 @@
 				})
 			}
 
-			// If using an rsp file copy it into the sbox directory.
-			if rspFilePath != nil {
+			// If using rsp files copy them and their contents into the sbox directory with
+			// the appropriate path mappings.
+			for _, rspFile := range rspFiles {
 				command.RspFiles = append(command.RspFiles, &sbox_proto.RspFile{
-					File: proto.String(rspFilePath.String()),
+					File: proto.String(rspFile.file.String()),
 					// These have to match the logic in sboxPathForInputRel
 					PathMappings: []*sbox_proto.PathMapping{
 						{
@@ -641,9 +632,9 @@
 			remoteInputs = append(remoteInputs, inputs...)
 			remoteInputs = append(remoteInputs, tools...)
 
-			if rspFilePath != nil {
-				remoteInputs = append(remoteInputs, rspFilePath)
-				remoteRspFiles = append(remoteRspFiles, rspFilePath)
+			for _, rspFile := range rspFiles {
+				remoteInputs = append(remoteInputs, rspFile.file)
+				remoteRspFiles = append(remoteRspFiles, rspFile.file)
 			}
 
 			if len(remoteInputs) > 0 {
@@ -673,12 +664,24 @@
 	implicitOutputs := outputs[1:]
 
 	var rspFile, rspFileContent string
-	if rspFilePath != nil {
-		rspFile = rspFilePath.String()
+	var rspFileInputs Paths
+	if len(rspFiles) > 0 {
+		// The first rsp files uses Ninja's rsp file support for the rule
+		rspFile = rspFiles[0].file.String()
 		// Use "$in" for rspFileContent to avoid duplicating the list of files in the dependency
 		// list and in the contents of the rsp file.  Inputs to the rule that are not in the
 		// rsp file will be listed in Implicits instead of Inputs so they don't show up in "$in".
 		rspFileContent = "$in"
+		rspFileInputs = append(rspFileInputs, rspFiles[0].paths...)
+
+		for _, rspFile := range rspFiles[1:] {
+			// Any additional rsp files need an extra rule to write the file.
+			writeRspFileRule(r.ctx, rspFile.file, rspFile.paths)
+			// The main rule needs to depend on the inputs listed in the extra rsp file.
+			inputs = append(inputs, rspFile.paths...)
+			// The main rule needs to depend on the extra rsp file.
+			inputs = append(inputs, rspFile.file)
+		}
 	}
 
 	var pool blueprint.Pool
@@ -729,8 +732,12 @@
 	depFiles       WritablePaths
 	tools          Paths
 	packagedTools  []PackagingSpec
-	rspFileInputs  Paths
-	rspFile        WritablePath
+	rspFiles       []rspFileAndPaths
+}
+
+type rspFileAndPaths struct {
+	file  WritablePath
+	paths Paths
 }
 
 func (c *RuleBuilderCommand) addInput(path Path) string {
@@ -1174,22 +1181,19 @@
 	return c.Text(flag + c.PathForOutput(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.  If sbox is enabled, the rspfile must
-// be outside the sbox directory.
+// 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.  If sbox is enabled, the
+// rspfile must be outside the sbox directory.  The first use of FlagWithRspFileInputList in any
+// RuleBuilderCommand of a RuleBuilder will use Ninja's rsp file support for the rule, additional
+// uses will result in an auxiliary rules to write the rspFile contents.
 func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, rspFile WritablePath, 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
-	c.rspFile = rspFile
+	c.rspFiles = append(c.rspFiles, rspFileAndPaths{rspFile, paths})
 
 	if c.rule.sbox {
 		if _, isRel, _ := maybeRelErr(c.rule.outDir.String(), rspFile.String()); isRel {
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index 3df900f..9c5ca41 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -489,8 +489,9 @@
 	properties struct {
 		Srcs []string
 
-		Restat bool
-		Sbox   bool
+		Restat      bool
+		Sbox        bool
+		Sbox_inputs bool
 	}
 }
 
@@ -499,9 +500,15 @@
 	out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
 	outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
 	outDir := PathForModuleOut(ctx, "gen")
+	rspFile := PathForModuleOut(ctx, "rsp")
+	rspFile2 := PathForModuleOut(ctx, "rsp2")
+	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
+	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
 	manifestPath := PathForModuleOut(ctx, "sbox.textproto")
 
-	testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat, t.properties.Sbox)
+	testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat,
+		t.properties.Sbox, t.properties.Sbox_inputs, rspFile, rspFileContents,
+		rspFile2, rspFileContents2)
 }
 
 type testRuleBuilderSingleton struct{}
@@ -515,18 +522,35 @@
 	out := PathForOutput(ctx, "singleton/gen/baz")
 	outDep := PathForOutput(ctx, "singleton/gen/baz.d")
 	outDir := PathForOutput(ctx, "singleton/gen")
+	rspFile := PathForOutput(ctx, "singleton/rsp")
+	rspFile2 := PathForOutput(ctx, "singleton/rsp2")
+	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
+	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
 	manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
-	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false)
+	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false, false,
+		rspFile, rspFileContents, rspFile2, rspFileContents2)
 }
 
-func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath, restat, sbox bool) {
+func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath,
+	restat, sbox, sboxInputs bool,
+	rspFile WritablePath, rspFileContents Paths, rspFile2 WritablePath, rspFileContents2 Paths) {
+
 	rule := NewRuleBuilder(pctx, ctx)
 
 	if sbox {
 		rule.Sbox(outDir, manifestPath)
+		if sboxInputs {
+			rule.SandboxInputs()
+		}
 	}
 
-	rule.Command().Tool(PathForSource(ctx, "cp")).Inputs(in).Output(out).ImplicitDepFile(outDep)
+	rule.Command().
+		Tool(PathForSource(ctx, "cp")).
+		Inputs(in).
+		Output(out).
+		ImplicitDepFile(outDep).
+		FlagWithRspFileInputList("@", rspFile, rspFileContents).
+		FlagWithRspFileInputList("@", rspFile2, rspFileContents2)
 
 	if restat {
 		rule.Restat()
@@ -557,6 +581,12 @@
 			srcs: ["bar"],
 			sbox: true,
 		}
+		rule_builder_test {
+			name: "foo_sbox_inputs",
+			srcs: ["bar"],
+			sbox: true,
+			sbox_inputs: true,
+		}
 	`
 
 	result := GroupFixturePreparers(
@@ -565,7 +595,10 @@
 		fs.AddToFixture(),
 	).RunTest(t)
 
-	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraImplicits, extraCmdDeps []string) {
+	check := func(t *testing.T, params TestingBuildParams, rspFile2Params TestingBuildParams,
+		wantCommand, wantOutput, wantDepfile, wantRspFile, wantRspFile2 string,
+		wantRestat bool, extraImplicits, extraCmdDeps []string) {
+
 		t.Helper()
 		command := params.RuleParams.Command
 		re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
@@ -578,9 +611,19 @@
 
 		AssertBoolEquals(t, "RuleParams.Restat", wantRestat, params.RuleParams.Restat)
 
+		wantInputs := []string{"rsp_in"}
+		AssertArrayString(t, "Inputs", wantInputs, params.Inputs.Strings())
+
 		wantImplicits := append([]string{"bar"}, extraImplicits...)
+		// The second rsp file and the files listed in it should be in implicits
+		wantImplicits = append(wantImplicits, "rsp_in2", wantRspFile2)
 		AssertPathsRelativeToTopEquals(t, "Implicits", wantImplicits, params.Implicits)
 
+		wantRspFileContent := "$in"
+		AssertStringEquals(t, "RspfileContent", wantRspFileContent, params.RuleParams.RspfileContent)
+
+		AssertStringEquals(t, "Rspfile", wantRspFile, params.RuleParams.Rspfile)
+
 		AssertPathRelativeToTopEquals(t, "Output", wantOutput, params.Output)
 
 		if len(params.ImplicitOutputs) != 0 {
@@ -592,18 +635,42 @@
 		if params.Deps != blueprint.DepsGCC {
 			t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps)
 		}
+
+		rspFile2Content := ContentFromFileRuleForTests(t, rspFile2Params)
+		AssertStringEquals(t, "rspFile2 content", "rsp_in2\n", rspFile2Content)
 	}
 
 	t.Run("module", func(t *testing.T) {
 		outFile := "out/soong/.intermediates/foo/gen/foo"
-		check(t, result.ModuleForTests("foo", "").Rule("rule").RelativeToTop(),
-			"cp bar "+outFile,
-			outFile, outFile+".d", true, nil, nil)
+		rspFile := "out/soong/.intermediates/foo/rsp"
+		rspFile2 := "out/soong/.intermediates/foo/rsp2"
+		module := result.ModuleForTests("foo", "")
+		check(t, module.Rule("rule").RelativeToTop(), module.Output(rspFile2).RelativeToTop(),
+			"cp bar "+outFile+" @"+rspFile+" @"+rspFile2,
+			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
 	})
 	t.Run("sbox", func(t *testing.T) {
 		outDir := "out/soong/.intermediates/foo_sbox"
 		outFile := filepath.Join(outDir, "gen/foo_sbox")
 		depFile := filepath.Join(outDir, "gen/foo_sbox.d")
+		rspFile := filepath.Join(outDir, "rsp")
+		rspFile2 := filepath.Join(outDir, "rsp2")
+		manifest := filepath.Join(outDir, "sbox.textproto")
+		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
+		sandboxPath := shared.TempDirForOutDir("out/soong")
+
+		cmd := `rm -rf ` + outDir + `/gen && ` +
+			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
+		module := result.ModuleForTests("foo_sbox", "")
+		check(t, module.Output("gen/foo_sbox").RelativeToTop(), module.Output(rspFile2).RelativeToTop(),
+			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
+	})
+	t.Run("sbox_inputs", func(t *testing.T) {
+		outDir := "out/soong/.intermediates/foo_sbox_inputs"
+		outFile := filepath.Join(outDir, "gen/foo_sbox_inputs")
+		depFile := filepath.Join(outDir, "gen/foo_sbox_inputs.d")
+		rspFile := filepath.Join(outDir, "rsp")
+		rspFile2 := filepath.Join(outDir, "rsp2")
 		manifest := filepath.Join(outDir, "sbox.textproto")
 		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir("out/soong")
@@ -611,13 +678,18 @@
 		cmd := `rm -rf ` + outDir + `/gen && ` +
 			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
 
-		check(t, result.ModuleForTests("foo_sbox", "").Output("gen/foo_sbox").RelativeToTop(),
-			cmd, outFile, depFile, false, []string{manifest}, []string{sbox})
+		module := result.ModuleForTests("foo_sbox_inputs", "")
+		check(t, module.Output("gen/foo_sbox_inputs").RelativeToTop(), module.Output(rspFile2).RelativeToTop(),
+			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
 	})
 	t.Run("singleton", func(t *testing.T) {
 		outFile := filepath.Join("out/soong/singleton/gen/baz")
-		check(t, result.SingletonForTests("rule_builder_test").Rule("rule").RelativeToTop(),
-			"cp bar "+outFile, outFile, outFile+".d", true, nil, nil)
+		rspFile := filepath.Join("out/soong/singleton/rsp")
+		rspFile2 := filepath.Join("out/soong/singleton/rsp2")
+		singleton := result.SingletonForTests("rule_builder_test")
+		check(t, singleton.Rule("rule").RelativeToTop(), singleton.Output(rspFile2).RelativeToTop(),
+			"cp bar "+outFile+" @"+rspFile+" @"+rspFile2,
+			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
 	})
 }
 
diff --git a/java/java_test.go b/java/java_test.go
index 13b3e2a..5a13044 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -628,7 +628,7 @@
 	}
 
 	t.Run("empty/missing directory", func(t *testing.T) {
-		test(t, "empty-directory", []string{})
+		test(t, "empty-directory", nil)
 	})
 
 	t.Run("non-empty set of sources", func(t *testing.T) {