Support sandboxing inputs in RuleBuilder

When RuleBuilder.SandboxInputs() is called configure sbox to copy
all the input files into the sandbox directory and then change the
working directory there when running the command.

Copying input files into the sandbox directory gets tricky when
the input file is the output file from another rule, and could
be at an arbitrary, possibly absolute path based on the value
of OUT_DIR.  They will need to be copied to a directory in the
sandbox using the path relative to OUT_DIR.

RSP files need special handling, they need to both be copied into
the sandbox as an input, rewritten to contain paths as seen in the
sandbox, and references to them on the command line need to use
sandbox paths.

Bug: 182612695
Test: rule_builder_test.go
Change-Id: Ic0db961961b186e4ed9b76246881e3f04971825c
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 75f1b5d..cc6c9b4 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -50,6 +50,7 @@
 	remoteable       RemoteRuleSupports
 	outDir           WritablePath
 	sboxTools        bool
+	sboxInputs       bool
 	sboxManifestPath WritablePath
 	missingDeps      []string
 }
@@ -155,6 +156,25 @@
 	return r
 }
 
+// SandboxInputs enables input sandboxing for the rule by copying any referenced inputs into the
+// sandbox.  It also implies SandboxTools().
+//
+// Sandboxing inputs requires RuleBuilder to be aware of all references to input paths.  Paths
+// that are passed to RuleBuilder outside of the methods that expect inputs, for example
+// FlagWithArg, must use RuleBuilderCommand.PathForInput to translate the path to one that matches
+// the sandbox layout.
+func (r *RuleBuilder) SandboxInputs() *RuleBuilder {
+	if !r.sbox {
+		panic("SandboxInputs() must be called after Sbox()")
+	}
+	if len(r.commands) > 0 {
+		panic("SandboxInputs() may not be called after Command()")
+	}
+	r.sboxTools = true
+	r.sboxInputs = true
+	return r
+}
+
 // Install associates an output of the rule with an install location, which can be retrieved later using
 // RuleBuilder.Installs.
 func (r *RuleBuilder) Install(from Path, to string) {
@@ -425,6 +445,26 @@
 		Inputs(depFiles.Paths())
 }
 
+// composeRspFileContent returns a string that will serve as the contents of the rsp file to pass
+// the listed input files to the command running in the sandbox.
+func (r *RuleBuilder) composeRspFileContent(rspFileInputs Paths) string {
+	if r.sboxInputs {
+		if len(rspFileInputs) > 0 {
+			// When SandboxInputs is used the paths need to be rewritten to be relative to the sandbox
+			// directory so that they are valid after sbox chdirs into the sandbox directory.
+			return proptools.NinjaEscape(strings.Join(r.sboxPathsForInputsRel(rspFileInputs), " "))
+		} else {
+			// If the list of inputs is empty fall back to "$in" so that the rspfilecontent Ninja
+			// variable is set to something non-empty, otherwise ninja will complain.  The inputs
+			// will be empty (all the non-rspfile inputs are implicits), so $in will evaluate to
+			// an empty string.
+			return "$in"
+		}
+	} else {
+		return "$in"
+	}
+}
+
 // Build adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for
 // Outputs.
 func (r *RuleBuilder) Build(name string, desc string) {
@@ -511,6 +551,27 @@
 			}
 		}
 
+		// If sandboxing inputs is enabled, add copy rules to the manifest to copy each input
+		// into the sbox directory.
+		if r.sboxInputs {
+			for _, input := range inputs {
+				command.CopyBefore = append(command.CopyBefore, &sbox_proto.Copy{
+					From: proto.String(input.String()),
+					To:   proto.String(r.sboxPathForInputRel(input)),
+				})
+			}
+
+			// If using an rsp file copy it into the sbox directory.
+			if rspFilePath != nil {
+				command.CopyBefore = append(command.CopyBefore, &sbox_proto.Copy{
+					From: proto.String(rspFilePath.String()),
+					To:   proto.String(r.sboxPathForInputRel(rspFilePath)),
+				})
+			}
+
+			command.Chdir = proto.Bool(true)
+		}
+
 		// Add copy rules to the manifest to copy each output file from the sbox directory.
 		// to the output directory after running the commands.
 		sboxOutputs := make([]string, len(outputs))
@@ -580,7 +641,7 @@
 	var rspFile, rspFileContent string
 	if rspFilePath != nil {
 		rspFile = rspFilePath.String()
-		rspFileContent = "$in"
+		rspFileContent = r.composeRspFileContent(rspFileInputs)
 	}
 
 	var pool blueprint.Pool
@@ -636,29 +697,45 @@
 }
 
 func (c *RuleBuilderCommand) addInput(path Path) string {
-	if c.rule.sbox {
-		if rel, isRel, _ := maybeRelErr(c.rule.outDir.String(), path.String()); isRel {
-			return filepath.Join(sboxOutDir, rel)
-		}
-	}
 	c.inputs = append(c.inputs, path)
-	return path.String()
+	return c.PathForInput(path)
 }
 
-func (c *RuleBuilderCommand) addImplicit(path Path) string {
-	if c.rule.sbox {
-		if rel, isRel, _ := maybeRelErr(c.rule.outDir.String(), path.String()); isRel {
-			return filepath.Join(sboxOutDir, rel)
-		}
-	}
+func (c *RuleBuilderCommand) addImplicit(path Path) {
 	c.implicits = append(c.implicits, path)
-	return path.String()
 }
 
 func (c *RuleBuilderCommand) addOrderOnly(path Path) {
 	c.orderOnlys = append(c.orderOnlys, path)
 }
 
+// PathForInput takes an input path and returns the appropriate path to use on the command line.  If
+// sbox was enabled via a call to RuleBuilder.Sbox() and the path was an output path it returns a
+// path with the placeholder prefix used for outputs in sbox.  If sbox is not enabled it returns the
+// original path.
+func (c *RuleBuilderCommand) PathForInput(path Path) string {
+	if c.rule.sbox {
+		rel, inSandbox := c.rule._sboxPathForInputRel(path)
+		if inSandbox {
+			rel = filepath.Join(sboxSandboxBaseDir, rel)
+		}
+		return rel
+	}
+	return path.String()
+}
+
+// PathsForInputs takes a list of input paths and returns the appropriate paths to use on the
+// command line.  If sbox was enabled via a call to RuleBuilder.Sbox() a path was an output path, it
+// returns the path with the placeholder prefix used for outputs in sbox.  If sbox is not enabled it
+// returns the original paths.
+func (c *RuleBuilderCommand) PathsForInputs(paths Paths) []string {
+	ret := make([]string, len(paths))
+	for i, path := range paths {
+		ret[i] = c.PathForInput(path)
+	}
+	return ret
+}
+
 // PathForOutput takes an output path and returns the appropriate path to use on the command
 // line.  If sbox was enabled via a call to RuleBuilder.Sbox(), it returns a path with the
 // placeholder prefix used for outputs in sbox.  If sbox is not enabled it returns the
@@ -690,6 +767,37 @@
 	return filepath.Join(sboxToolsSubDir, "src", path.String())
 }
 
+func (r *RuleBuilder) _sboxPathForInputRel(path Path) (rel string, inSandbox bool) {
+	// Errors will be handled in RuleBuilder.Build where we have a context to report them
+	rel, isRelSboxOut, _ := maybeRelErr(r.outDir.String(), path.String())
+	if isRelSboxOut {
+		return filepath.Join(sboxOutSubDir, rel), true
+	}
+	if r.sboxInputs {
+		// When sandboxing inputs all inputs have to be copied into the sandbox.  Input files that
+		// are outputs of other rules could be an arbitrary absolute path if OUT_DIR is set, so they
+		// will be copied to relative paths under __SBOX_OUT_DIR__/out.
+		rel, isRelOut, _ := maybeRelErr(PathForOutput(r.ctx).String(), path.String())
+		if isRelOut {
+			return filepath.Join(sboxOutSubDir, rel), true
+		}
+	}
+	return path.String(), false
+}
+
+func (r *RuleBuilder) sboxPathForInputRel(path Path) string {
+	rel, _ := r._sboxPathForInputRel(path)
+	return rel
+}
+
+func (r *RuleBuilder) sboxPathsForInputsRel(paths Paths) []string {
+	ret := make([]string, len(paths))
+	for i, path := range paths {
+		ret[i] = r.sboxPathForInputRel(path)
+	}
+	return ret
+}
+
 // SboxPathForPackagedTool takes a PackageSpec for a tool and returns the corresponding path for the
 // tool after copying it into the sandbox.  This can be used  on the RuleBuilder command line to
 // reference the tool.
@@ -1053,7 +1161,7 @@
 		}
 	}
 
-	c.FlagWithArg(flag, rspFile.String())
+	c.FlagWithArg(flag, c.PathForInput(rspFile))
 	return c
 }