Merge "Add bp2build support for cc_library_static."
diff --git a/android/Android.bp b/android/Android.bp
index 4da0f4e..773aa6a 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -14,6 +14,7 @@
         "soong-bazel",
         "soong-cquery",
         "soong-remoteexec",
+        "soong-response",
         "soong-shared",
         "soong-ui-metrics_proto",
     ],
diff --git a/android/paths.go b/android/paths.go
index b3cf804..1278961 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -174,8 +174,35 @@
 	// example, Rel on a PathsForModuleSrc would return the path relative to the module source
 	// directory, and OutputPath.Join("foo").Rel() would return "foo".
 	Rel() string
+
+	// RelativeToTop returns a new path relative to the top, it is provided solely for use in tests.
+	//
+	// It is guaranteed to always return the same type as it is called on, e.g. if called on an
+	// InstallPath then the returned value can be converted to an InstallPath.
+	//
+	// A standard build has the following structure:
+	//   ../top/
+	//          out/ - make install files go here.
+	//          out/soong - this is the buildDir passed to NewTestConfig()
+	//          ... - the source files
+	//
+	// This function converts a path so that it appears relative to the ../top/ directory, i.e.
+	// * Make install paths, which have the pattern "buildDir/../<path>" are converted into the top
+	//   relative path "out/<path>"
+	// * Soong install paths and other writable paths, which have the pattern "buildDir/<path>" are
+	//   converted into the top relative path "out/soong/<path>".
+	// * Source paths are already relative to the top.
+	// * Phony paths are not relative to anything.
+	// * toolDepPath have an absolute but known value in so don't need making relative to anything in
+	//   order to test.
+	RelativeToTop() Path
 }
 
+const (
+	OutDir      = "out"
+	OutSoongDir = OutDir + "/soong"
+)
+
 // WritablePath is a type of path that can be used as an output for build rules.
 type WritablePath interface {
 	Path
@@ -271,6 +298,20 @@
 // Paths is a slice of Path objects, with helpers to operate on the collection.
 type Paths []Path
 
+// RelativeToTop creates a new Paths containing the result of calling Path.RelativeToTop on each
+// item in this slice.
+func (p Paths) RelativeToTop() Paths {
+	ensureTestOnly()
+	if p == nil {
+		return p
+	}
+	ret := make(Paths, len(p))
+	for i, path := range p {
+		ret[i] = path.RelativeToTop()
+	}
+	return ret
+}
+
 func (paths Paths) containsPath(path Path) bool {
 	for _, p := range paths {
 		if p == path {
@@ -910,6 +951,20 @@
 // WritablePaths is a slice of WritablePath, used for multiple outputs.
 type WritablePaths []WritablePath
 
+// RelativeToTop creates a new WritablePaths containing the result of calling Path.RelativeToTop on
+// each item in this slice.
+func (p WritablePaths) RelativeToTop() WritablePaths {
+	ensureTestOnly()
+	if p == nil {
+		return p
+	}
+	ret := make(WritablePaths, len(p))
+	for i, path := range p {
+		ret[i] = path.RelativeToTop().(WritablePath)
+	}
+	return ret
+}
+
 // Strings returns the string forms of the writable paths.
 func (p WritablePaths) Strings() []string {
 	if p == nil {
@@ -972,6 +1027,11 @@
 	srcDir string
 }
 
+func (p SourcePath) RelativeToTop() Path {
+	ensureTestOnly()
+	return p
+}
+
 var _ Path = SourcePath{}
 
 func (p SourcePath) withRel(rel string) SourcePath {
@@ -1167,6 +1227,16 @@
 	return p.buildDir
 }
 
+func (p OutputPath) RelativeToTop() Path {
+	return p.outputPathRelativeToTop()
+}
+
+func (p OutputPath) outputPathRelativeToTop() OutputPath {
+	p.fullPath = StringPathRelativeToTop(p.buildDir, p.fullPath)
+	p.buildDir = OutSoongDir
+	return p
+}
+
 func (p OutputPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
 	return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
 }
@@ -1180,6 +1250,11 @@
 	basePath
 }
 
+func (t toolDepPath) RelativeToTop() Path {
+	ensureTestOnly()
+	return t
+}
+
 var _ Path = toolDepPath{}
 
 // pathForBuildToolDep returns a toolDepPath representing the given path string.
@@ -1357,7 +1432,13 @@
 	OutputPath
 }
 
+func (p ModuleOutPath) RelativeToTop() Path {
+	p.OutputPath = p.outputPathRelativeToTop()
+	return p
+}
+
 var _ Path = ModuleOutPath{}
+var _ WritablePath = ModuleOutPath{}
 
 func (p ModuleOutPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
 	return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
@@ -1462,7 +1543,13 @@
 	ModuleOutPath
 }
 
+func (p ModuleGenPath) RelativeToTop() Path {
+	p.OutputPath = p.outputPathRelativeToTop()
+	return p
+}
+
 var _ Path = ModuleGenPath{}
+var _ WritablePath = ModuleGenPath{}
 var _ genPathProvider = ModuleGenPath{}
 var _ objPathProvider = ModuleGenPath{}
 
@@ -1495,7 +1582,13 @@
 	ModuleOutPath
 }
 
+func (p ModuleObjPath) RelativeToTop() Path {
+	p.OutputPath = p.outputPathRelativeToTop()
+	return p
+}
+
 var _ Path = ModuleObjPath{}
+var _ WritablePath = ModuleObjPath{}
 
 // PathForModuleObj returns a Path representing the paths... under the module's
 // 'obj' directory.
@@ -1513,7 +1606,13 @@
 	ModuleOutPath
 }
 
+func (p ModuleResPath) RelativeToTop() Path {
+	p.OutputPath = p.outputPathRelativeToTop()
+	return p
+}
+
 var _ Path = ModuleResPath{}
+var _ WritablePath = ModuleResPath{}
 
 // PathForModuleRes returns a Path representing the paths... under the module's
 // 'res' directory.
@@ -1541,6 +1640,26 @@
 	makePath bool
 }
 
+// Will panic if called from outside a test environment.
+func ensureTestOnly() {
+	// Normal soong test environment
+	if InList("-test.short", os.Args) {
+		return
+	}
+	// IntelliJ test environment
+	if InList("-test.v", os.Args) {
+		return
+	}
+
+	panic(fmt.Errorf("Not in test\n%s", strings.Join(os.Args, "\n")))
+}
+
+func (p InstallPath) RelativeToTop() Path {
+	ensureTestOnly()
+	p.buildDir = OutSoongDir
+	return p
+}
+
 func (p InstallPath) getBuildDir() string {
 	return p.buildDir
 }
@@ -1814,6 +1933,13 @@
 	return ""
 }
 
+func (p PhonyPath) RelativeToTop() Path {
+	ensureTestOnly()
+	// A phony path cannot contain any / so does not have a build directory so switching to a new
+	// build directory has no effect so just return this path.
+	return p
+}
+
 func (p PhonyPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
 	panic("Not implemented")
 }
@@ -1825,10 +1951,17 @@
 	basePath
 }
 
+func (p testPath) RelativeToTop() Path {
+	ensureTestOnly()
+	return p
+}
+
 func (p testPath) String() string {
 	return p.path
 }
 
+var _ Path = testPath{}
+
 // PathForTesting returns a Path constructed from joining the elements of paths with '/'.  It should only be used from
 // within tests.
 func PathForTesting(paths ...string) Path {
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 04864a1..ebccaa7 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -272,11 +272,9 @@
 	}
 }
 
-// PrebuiltPostDepsMutator does two operations.  It replace dependencies on the
-// source module with dependencies on the prebuilt when both modules exist and
-// the prebuilt should be used.  When the prebuilt should not be used, disable
-// installing it.  Secondly, it also adds a sourcegroup to any filegroups found
-// in the prebuilt's 'Srcs' property.
+// PrebuiltPostDepsMutator replaces dependencies on the source module with dependencies on the
+// prebuilt when both modules exist and the prebuilt should be used.  When the prebuilt should not
+// be used, disable installing it.
 func PrebuiltPostDepsMutator(ctx BottomUpMutatorContext) {
 	if m, ok := ctx.Module().(PrebuiltInterface); ok && m.Prebuilt() != nil {
 		p := m.Prebuilt()
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 0d8e2b7..72c0d10 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -28,6 +28,7 @@
 
 	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/remoteexec"
+	"android/soong/response"
 	"android/soong/shared"
 )
 
@@ -408,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.
@@ -459,26 +451,6 @@
 		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) {
@@ -520,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
@@ -575,11 +546,22 @@
 				})
 			}
 
-			// 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)),
+			// 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(rspFile.file.String()),
+					// These have to match the logic in sboxPathForInputRel
+					PathMappings: []*sbox_proto.PathMapping{
+						{
+							From: proto.String(r.outDir.String()),
+							To:   proto.String(sboxOutSubDir),
+						},
+						{
+							From: proto.String(PathForOutput(r.ctx).String()),
+							To:   proto.String(sboxOutSubDir),
+						},
+					},
 				})
 			}
 
@@ -641,20 +623,30 @@
 		inputs = append(inputs, sboxCmd.inputs...)
 
 		if r.rbeParams != nil {
-			var remoteInputs []string
-			remoteInputs = append(remoteInputs, inputs.Strings()...)
-			remoteInputs = append(remoteInputs, tools.Strings()...)
-			remoteInputs = append(remoteInputs, rspFileInputs.Strings()...)
-			if rspFilePath != nil {
-				remoteInputs = append(remoteInputs, rspFilePath.String())
+			// RBE needs a list of input files to copy to the remote builder.  For inputs already
+			// listed in an rsp file, pass the rsp file directly to rewrapper.  For the rest,
+			// create a new rsp file to pass to rewrapper.
+			var remoteRspFiles Paths
+			var remoteInputs Paths
+
+			remoteInputs = append(remoteInputs, inputs...)
+			remoteInputs = append(remoteInputs, tools...)
+
+			for _, rspFile := range rspFiles {
+				remoteInputs = append(remoteInputs, rspFile.file)
+				remoteRspFiles = append(remoteRspFiles, rspFile.file)
 			}
-			inputsListFile := r.sboxManifestPath.ReplaceExtension(r.ctx, "rbe_inputs.list")
-			inputsListContents := rspFileForInputs(remoteInputs)
-			WriteFileRule(r.ctx, inputsListFile, inputsListContents)
-			inputs = append(inputs, inputsListFile)
+
+			if len(remoteInputs) > 0 {
+				inputsListFile := r.sboxManifestPath.ReplaceExtension(r.ctx, "rbe_inputs.list")
+				writeRspFileRule(r.ctx, inputsListFile, remoteInputs)
+				remoteRspFiles = append(remoteRspFiles, inputsListFile)
+				// Add the new rsp file as an extra input to the rule.
+				inputs = append(inputs, inputsListFile)
+			}
 
 			r.rbeParams.OutputFiles = outputs.Strings()
-			r.rbeParams.RSPFile = inputsListFile.String()
+			r.rbeParams.RSPFiles = remoteRspFiles.Strings()
 			rewrapperCommand := r.rbeParams.NoVarTemplate(r.ctx.Config().RBEWrapper())
 			commandString = rewrapperCommand + " bash -c '" + strings.ReplaceAll(commandString, `'`, `'\''`) + "'"
 		}
@@ -672,9 +664,24 @@
 	implicitOutputs := outputs[1:]
 
 	var rspFile, rspFileContent string
-	if rspFilePath != nil {
-		rspFile = rspFilePath.String()
-		rspFileContent = r.composeRspFileContent(rspFileInputs)
+	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
@@ -725,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 {
@@ -1170,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 {
@@ -1264,13 +1272,12 @@
 }
 func (builderContextForTests) Build(PackageContext, BuildParams) {}
 
-func rspFileForInputs(paths []string) string {
-	s := strings.Builder{}
-	for i, path := range paths {
-		if i != 0 {
-			s.WriteByte(' ')
-		}
-		s.WriteString(proptools.ShellEscape(path))
+func writeRspFileRule(ctx BuilderContext, rspFile WritablePath, paths Paths) {
+	buf := &strings.Builder{}
+	err := response.WriteRspFile(buf, paths.Strings())
+	if err != nil {
+		// There should never be I/O errors writing to a bytes.Buffer.
+		panic(err)
 	}
-	return s.String()
+	WriteFileRule(ctx, rspFile, buf.String())
 }
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index 9cd60a2..9c5ca41 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -376,8 +376,6 @@
 		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
 			"out_local/module/DepFile out_local/module/depfile out_local/module/ImplicitDepFile out_local/module/depfile2"
 
-		wantRspFileContent := "$in"
-
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
 		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
@@ -389,8 +387,6 @@
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
-
-		AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
 	})
 
 	t.Run("sbox", func(t *testing.T) {
@@ -409,8 +405,6 @@
 
 		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
-		wantRspFileContent := "$in"
-
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
 		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
@@ -422,8 +416,6 @@
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
-
-		AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
 	})
 
 	t.Run("sbox tools", func(t *testing.T) {
@@ -442,7 +434,34 @@
 
 		wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
-		wantRspFileContent := "$in"
+		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
+
+		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
+		AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
+		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
+		AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs())
+		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
+		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
+		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
+
+		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
+	})
+
+	t.Run("sbox inputs", func(t *testing.T) {
+		rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"),
+			PathForOutput(ctx, "sbox.textproto")).SandboxInputs()
+		addCommands(rule)
+
+		wantCommands := []string{
+			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
+				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
+				"FlagWithRspFileInputList=__SBOX_SANDBOX_DIR__/out/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
+				"__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd",
+			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2",
+			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
+		}
+
+		wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
@@ -455,8 +474,6 @@
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
-
-		AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
 	})
 }
 
@@ -472,8 +489,9 @@
 	properties struct {
 		Srcs []string
 
-		Restat bool
-		Sbox   bool
+		Restat      bool
+		Sbox        bool
+		Sbox_inputs bool
 	}
 }
 
@@ -482,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{}
@@ -498,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()
@@ -540,6 +581,12 @@
 			srcs: ["bar"],
 			sbox: true,
 		}
+		rule_builder_test {
+			name: "foo_sbox_inputs",
+			srcs: ["bar"],
+			sbox: true,
+			sbox_inputs: true,
+		}
 	`
 
 	result := GroupFixturePreparers(
@@ -548,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]*$")
@@ -561,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 {
@@ -575,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")
@@ -594,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/android/testing.go b/android/testing.go
index c077678..3f3f769 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -935,19 +935,14 @@
 // PathRelativeToTop returns a string representation of the path relative to a notional top
 // directory.
 //
-// For a WritablePath it applies StringPathRelativeToTop to it, using the buildDir returned from the
-// WritablePath's buildDir() method. For all other paths, i.e. source paths, that are already
-// relative to the top it just returns their string representation.
+// It return "<nil path>" if the supplied path is nil, otherwise it returns the result of calling
+// Path.RelativeToTop to obtain a relative Path and then calling Path.String on that to get the
+// string representation.
 func PathRelativeToTop(path Path) string {
 	if path == nil {
 		return "<nil path>"
 	}
-	p := path.String()
-	if w, ok := path.(WritablePath); ok {
-		buildDir := w.getBuildDir()
-		return StringPathRelativeToTop(buildDir, p)
-	}
-	return p
+	return path.RelativeToTop().String()
 }
 
 // PathsRelativeToTop creates a slice of strings where each string is the result of applying
@@ -964,23 +959,13 @@
 // StringPathRelativeToTop returns a string representation of the path relative to a notional top
 // directory.
 //
-// A standard build has the following structure:
-//   ../top/
-//          out/ - make install files go here.
-//          out/soong - this is the buildDir passed to NewTestConfig()
-//          ... - the source files
-//
-// This function converts a path so that it appears relative to the ../top/ directory, i.e.
-// * Make install paths, which have the pattern "buildDir/../<path>" are converted into the top
-//   relative path "out/<path>"
-// * Soong install paths and other writable paths, which have the pattern "buildDir/<path>" are
-//   converted into the top relative path "out/soong/<path>".
-// * Source paths are already relative to the top.
+// See Path.RelativeToTop for more details as to what `relative to top` means.
 //
 // This is provided for processing paths that have already been converted into a string, e.g. paths
 // in AndroidMkEntries structures. As a result it needs to be supplied the soong output dir against
 // which it can try and relativize paths. PathRelativeToTop must be used for process Path objects.
 func StringPathRelativeToTop(soongOutDir string, path string) string {
+	ensureTestOnly()
 
 	// A relative path must be a source path so leave it as it is.
 	if !filepath.IsAbs(path) {
diff --git a/apex/testing.go b/apex/testing.go
index e662cad..926125f 100644
--- a/apex/testing.go
+++ b/apex/testing.go
@@ -19,4 +19,11 @@
 var PrepareForTestWithApexBuildComponents = android.GroupFixturePreparers(
 	android.FixtureRegisterWithContext(registerApexBuildComponents),
 	android.FixtureRegisterWithContext(registerApexKeyBuildComponents),
+	// Additional files needed in tests that disallow non-existent source files.
+	// This includes files that are needed by all, or at least most, instances of an apex module type.
+	android.MockFS{
+		// Needed by apex.
+		"system/core/rootdir/etc/public.libraries.android.txt": nil,
+		"build/soong/scripts/gen_ndk_backedby_apex.sh":         nil,
+	}.AddToFixture(),
 )
diff --git a/cc/builder.go b/cc/builder.go
index 273cdd3..4771b89 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -73,7 +73,7 @@
 			Labels:          map[string]string{"type": "link", "tool": "clang"},
 			ExecStrategy:    "${config.RECXXLinksExecStrategy}",
 			Inputs:          []string{"${out}.rsp", "$implicitInputs"},
-			RSPFile:         "${out}.rsp",
+			RSPFiles:        []string{"${out}.rsp"},
 			OutputFiles:     []string{"${out}", "$implicitOutputs"},
 			ToolchainInputs: []string{"$ldCmd"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXLinksPool}"},
@@ -256,7 +256,7 @@
 			Labels:          map[string]string{"type": "tool", "name": "abi-linker"},
 			ExecStrategy:    "${config.REAbiLinkerExecStrategy}",
 			Inputs:          []string{"$sAbiLinkerLibs", "${out}.rsp", "$implicitInputs"},
-			RSPFile:         "${out}.rsp",
+			RSPFiles:        []string{"${out}.rsp"},
 			OutputFiles:     []string{"$out"},
 			ToolchainInputs: []string{"$sAbiLinker"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXPool}"},
diff --git a/cc/cc.go b/cc/cc.go
index cab7459..0c46b24 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1833,12 +1833,6 @@
 	if c.compiler != nil {
 		deps = c.compiler.compilerDeps(ctx, deps)
 	}
-	// Add the PGO dependency (the clang_rt.profile runtime library), which
-	// sometimes depends on symbols from libgcc, before libgcc gets added
-	// in linkerDeps().
-	if c.pgo != nil {
-		deps = c.pgo.deps(ctx, deps)
-	}
 	if c.linker != nil {
 		deps = c.linker.linkerDeps(ctx, deps)
 	}
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 7196615..8b3a6bd 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -166,6 +166,15 @@
 	recoveryVariant = "android_recovery_arm64_armv8-a_shared"
 )
 
+// Test that the PrepareForTestWithCcDefaultModules provides all the files that it uses by
+// running it in a fixture that requires all source files to exist.
+func TestPrepareForTestWithCcDefaultModules(t *testing.T) {
+	android.GroupFixturePreparers(
+		PrepareForTestWithCcDefaultModules,
+		android.PrepareForTestDisallowNonExistentPaths,
+	).RunTest(t)
+}
+
 func TestFuchsiaDeps(t *testing.T) {
 	t.Helper()
 
diff --git a/cc/config/toolchain.go b/cc/config/toolchain.go
index 59c0422..fce28c1 100644
--- a/cc/config/toolchain.go
+++ b/cc/config/toolchain.go
@@ -245,10 +245,6 @@
 	return LibclangRuntimeLibrary(t, "tsan")
 }
 
-func ProfileRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "profile")
-}
-
 func ScudoRuntimeLibrary(t Toolchain) string {
 	return LibclangRuntimeLibrary(t, "scudo")
 }
diff --git a/cc/pgo.go b/cc/pgo.go
index ada694b..95c9c2e 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -22,7 +22,6 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
-	"android/soong/cc/config"
 )
 
 var (
@@ -271,14 +270,6 @@
 	}
 }
 
-func (pgo *pgo) deps(ctx BaseModuleContext, deps Deps) Deps {
-	if pgo.Properties.ShouldProfileModule {
-		runtimeLibrary := config.ProfileRuntimeLibrary(ctx.toolchain())
-		deps.LateStaticLibs = append(deps.LateStaticLibs, runtimeLibrary)
-	}
-	return deps
-}
-
 func (pgo *pgo) flags(ctx ModuleContext, flags Flags) Flags {
 	if ctx.Host() {
 		return flags
diff --git a/cc/testing.go b/cc/testing.go
index d8adc61..6e35655 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -619,11 +619,26 @@
 
 		RegisterVndkLibraryTxtTypes(ctx)
 	}),
+
+	// Additional files needed in tests that disallow non-existent source files.
+	// This includes files that are needed by all, or at least most, instances of a cc module type.
+	android.MockFS{
+		// Needed for ndk_prebuilt_(shared|static)_stl.
+		"prebuilts/ndk/current/sources/cxx-stl/llvm-libc++/libs": nil,
+	}.AddToFixture(),
 )
 
 // Preparer that will define default cc modules, e.g. standard prebuilt modules.
 var PrepareForTestWithCcDefaultModules = android.GroupFixturePreparers(
 	PrepareForTestWithCcBuildComponents,
+
+	// Additional files needed in tests that disallow non-existent source.
+	android.MockFS{
+		"defaults/cc/common/libc.map.txt":  nil,
+		"defaults/cc/common/libdl.map.txt": nil,
+		"defaults/cc/common/libm.map.txt":  nil,
+	}.AddToFixture(),
+
 	// Place the default cc test modules that are common to all platforms in a location that will not
 	// conflict with default test modules defined by other packages.
 	android.FixtureAddTextFile(DefaultCcCommonTestModulesDir+"Android.bp", commonDefaultModules()),
diff --git a/cmd/merge_zips/Android.bp b/cmd/merge_zips/Android.bp
index 930d040..c516f99 100644
--- a/cmd/merge_zips/Android.bp
+++ b/cmd/merge_zips/Android.bp
@@ -22,7 +22,7 @@
         "android-archive-zip",
         "blueprint-pathtools",
         "soong-jar",
-        "soong-zip",
+        "soong-response",
     ],
     srcs: [
         "merge_zips.go",
diff --git a/cmd/merge_zips/merge_zips.go b/cmd/merge_zips/merge_zips.go
index 274c8ee..712c7fc 100644
--- a/cmd/merge_zips/merge_zips.go
+++ b/cmd/merge_zips/merge_zips.go
@@ -25,12 +25,14 @@
 	"os"
 	"path/filepath"
 	"sort"
+	"strings"
+
+	"android/soong/response"
 
 	"github.com/google/blueprint/pathtools"
 
 	"android/soong/jar"
 	"android/soong/third_party/zip"
-	soongZip "android/soong/zip"
 )
 
 // Input zip: we can open it, close it, and obtain an array of entries
@@ -690,15 +692,20 @@
 	inputs := make([]string, 0)
 	for _, input := range args[1:] {
 		if input[0] == '@' {
-			bytes, err := ioutil.ReadFile(input[1:])
+			f, err := os.Open(strings.TrimPrefix(input[1:], "@"))
 			if err != nil {
 				log.Fatal(err)
 			}
-			inputs = append(inputs, soongZip.ReadRespFile(bytes)...)
-			continue
+
+			rspInputs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				log.Fatal(err)
+			}
+			inputs = append(inputs, rspInputs...)
+		} else {
+			inputs = append(inputs, input)
 		}
-		inputs = append(inputs, input)
-		continue
 	}
 
 	log.SetFlags(log.Lshortfile)
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
index d88505f..b8d75ed 100644
--- a/cmd/sbox/Android.bp
+++ b/cmd/sbox/Android.bp
@@ -21,6 +21,7 @@
     deps: [
         "sbox_proto",
         "soong-makedeps",
+        "soong-response",
     ],
     srcs: [
         "sbox.go",
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index f47c601..fcc80a9 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -32,6 +32,7 @@
 
 	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/makedeps"
+	"android/soong/response"
 
 	"github.com/golang/protobuf/proto"
 )
@@ -218,6 +219,11 @@
 		return "", fmt.Errorf("command is required")
 	}
 
+	pathToTempDirInSbox := tempDir
+	if command.GetChdir() {
+		pathToTempDirInSbox = "."
+	}
+
 	err = os.MkdirAll(tempDir, 0777)
 	if err != nil {
 		return "", fmt.Errorf("failed to create %q: %w", tempDir, err)
@@ -228,10 +234,9 @@
 	if err != nil {
 		return "", err
 	}
-
-	pathToTempDirInSbox := tempDir
-	if command.GetChdir() {
-		pathToTempDirInSbox = "."
+	err = copyRspFiles(command.RspFiles, tempDir, pathToTempDirInSbox)
+	if err != nil {
+		return "", err
 	}
 
 	if strings.Contains(rawCommand, depFilePlaceholder) {
@@ -409,6 +414,83 @@
 	return nil
 }
 
+// copyRspFiles copies rsp files into the sandbox with path mappings, and also copies the files
+// listed into the sandbox.
+func copyRspFiles(rspFiles []*sbox_proto.RspFile, toDir, toDirInSandbox string) error {
+	for _, rspFile := range rspFiles {
+		err := copyOneRspFile(rspFile, toDir, toDirInSandbox)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// copyOneRspFiles copies an rsp file into the sandbox with path mappings, and also copies the files
+// listed into the sandbox.
+func copyOneRspFile(rspFile *sbox_proto.RspFile, toDir, toDirInSandbox string) error {
+	in, err := os.Open(rspFile.GetFile())
+	if err != nil {
+		return err
+	}
+	defer in.Close()
+
+	files, err := response.ReadRspFile(in)
+	if err != nil {
+		return err
+	}
+
+	for i, from := range files {
+		// Convert the real path of the input file into the path inside the sandbox using the
+		// path mappings.
+		to := applyPathMappings(rspFile.PathMappings, from)
+
+		// Copy the file into the sandbox.
+		err := copyOneFile(from, joinPath(toDir, to), false)
+		if err != nil {
+			return err
+		}
+
+		// Rewrite the name in the list of files to be relative to the sandbox directory.
+		files[i] = joinPath(toDirInSandbox, to)
+	}
+
+	// Convert the real path of the rsp file into the path inside the sandbox using the path
+	// mappings.
+	outRspFile := joinPath(toDir, applyPathMappings(rspFile.PathMappings, rspFile.GetFile()))
+
+	err = os.MkdirAll(filepath.Dir(outRspFile), 0777)
+	if err != nil {
+		return err
+	}
+
+	out, err := os.Create(outRspFile)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+
+	// Write the rsp file with converted paths into the sandbox.
+	err = response.WriteRspFile(out, files)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// applyPathMappings takes a list of path mappings and a path, and returns the path with the first
+// matching path mapping applied.  If the path does not match any of the path mappings then it is
+// returned unmodified.
+func applyPathMappings(pathMappings []*sbox_proto.PathMapping, path string) string {
+	for _, mapping := range pathMappings {
+		if strings.HasPrefix(path, mapping.GetFrom()+"/") {
+			return joinPath(mapping.GetTo()+"/", strings.TrimPrefix(path, mapping.GetFrom()+"/"))
+		}
+	}
+	return path
+}
+
 // moveFiles moves files specified by a set of copy rules.  It uses os.Rename, so it is restricted
 // to moving files where the source and destination are in the same filesystem.  This is OK for
 // sbox because the temporary directory is inside the out directory.  It updates the timestamp
diff --git a/cmd/sbox/sbox_proto/sbox.pb.go b/cmd/sbox/sbox_proto/sbox.pb.go
index 79bb90c..b996481 100644
--- a/cmd/sbox/sbox_proto/sbox.pb.go
+++ b/cmd/sbox/sbox_proto/sbox.pb.go
@@ -86,10 +86,13 @@
 	CopyAfter []*Copy `protobuf:"bytes,4,rep,name=copy_after,json=copyAfter" json:"copy_after,omitempty"`
 	// An optional hash of the input files to ensure the textproto files and the sbox rule reruns
 	// when the lists of inputs changes, even if the inputs are not on the command line.
-	InputHash            *string  `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	InputHash *string `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
+	// A list of files that will be copied before the sandboxed command, and whose contents should be
+	// copied as if they were listed in copy_before.
+	RspFiles             []*RspFile `protobuf:"bytes,6,rep,name=rsp_files,json=rspFiles" json:"rsp_files,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
 }
 
 func (m *Command) Reset()         { *m = Command{} }
@@ -152,6 +155,13 @@
 	return ""
 }
 
+func (m *Command) GetRspFiles() []*RspFile {
+	if m != nil {
+		return m.RspFiles
+	}
+	return nil
+}
+
 // Copy describes a from-to pair of files to copy.  The paths may be relative, the root that they
 // are relative to is specific to the context the Copy is used in and will be different for
 // from and to.
@@ -211,10 +221,110 @@
 	return false
 }
 
+// RspFile describes an rspfile that should be copied into the sandbox directory.
+type RspFile struct {
+	// The path to the rsp file.
+	File *string `protobuf:"bytes,1,req,name=file" json:"file,omitempty"`
+	// A list of path mappings that should be applied to each file listed in the rsp file.
+	PathMappings         []*PathMapping `protobuf:"bytes,2,rep,name=path_mappings,json=pathMappings" json:"path_mappings,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *RspFile) Reset()         { *m = RspFile{} }
+func (m *RspFile) String() string { return proto.CompactTextString(m) }
+func (*RspFile) ProtoMessage()    {}
+func (*RspFile) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{3}
+}
+
+func (m *RspFile) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_RspFile.Unmarshal(m, b)
+}
+func (m *RspFile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_RspFile.Marshal(b, m, deterministic)
+}
+func (m *RspFile) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RspFile.Merge(m, src)
+}
+func (m *RspFile) XXX_Size() int {
+	return xxx_messageInfo_RspFile.Size(m)
+}
+func (m *RspFile) XXX_DiscardUnknown() {
+	xxx_messageInfo_RspFile.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RspFile proto.InternalMessageInfo
+
+func (m *RspFile) GetFile() string {
+	if m != nil && m.File != nil {
+		return *m.File
+	}
+	return ""
+}
+
+func (m *RspFile) GetPathMappings() []*PathMapping {
+	if m != nil {
+		return m.PathMappings
+	}
+	return nil
+}
+
+// PathMapping describes a mapping from a path outside the sandbox to the path inside the sandbox.
+type PathMapping struct {
+	From                 *string  `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
+	To                   *string  `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *PathMapping) Reset()         { *m = PathMapping{} }
+func (m *PathMapping) String() string { return proto.CompactTextString(m) }
+func (*PathMapping) ProtoMessage()    {}
+func (*PathMapping) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{4}
+}
+
+func (m *PathMapping) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_PathMapping.Unmarshal(m, b)
+}
+func (m *PathMapping) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_PathMapping.Marshal(b, m, deterministic)
+}
+func (m *PathMapping) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PathMapping.Merge(m, src)
+}
+func (m *PathMapping) XXX_Size() int {
+	return xxx_messageInfo_PathMapping.Size(m)
+}
+func (m *PathMapping) XXX_DiscardUnknown() {
+	xxx_messageInfo_PathMapping.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PathMapping proto.InternalMessageInfo
+
+func (m *PathMapping) GetFrom() string {
+	if m != nil && m.From != nil {
+		return *m.From
+	}
+	return ""
+}
+
+func (m *PathMapping) GetTo() string {
+	if m != nil && m.To != nil {
+		return *m.To
+	}
+	return ""
+}
+
 func init() {
 	proto.RegisterType((*Manifest)(nil), "sbox.Manifest")
 	proto.RegisterType((*Command)(nil), "sbox.Command")
 	proto.RegisterType((*Copy)(nil), "sbox.Copy")
+	proto.RegisterType((*RspFile)(nil), "sbox.RspFile")
+	proto.RegisterType((*PathMapping)(nil), "sbox.PathMapping")
 }
 
 func init() {
@@ -222,22 +332,27 @@
 }
 
 var fileDescriptor_9d0425bf0de86ed1 = []byte{
-	// 268 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x4f, 0x4b, 0xc3, 0x40,
-	0x10, 0xc5, 0xc9, 0x9f, 0xd2, 0x64, 0x6a, 0x7b, 0x18, 0x3c, 0xec, 0x45, 0x09, 0x01, 0x21, 0x45,
-	0xe8, 0xc1, 0x6f, 0x60, 0xf5, 0x20, 0x82, 0x97, 0x1c, 0x45, 0x08, 0x9b, 0x64, 0x43, 0x02, 0x4d,
-	0x26, 0xec, 0x6e, 0xa0, 0xfd, 0x56, 0x7e, 0x44, 0xd9, 0x49, 0x2a, 0x82, 0xb7, 0x99, 0xdf, 0xe3,
-	0xcd, 0x7b, 0x0c, 0x80, 0x29, 0xe9, 0x7c, 0x18, 0x35, 0x59, 0xc2, 0xd0, 0xcd, 0xe9, 0x17, 0x44,
-	0x1f, 0x72, 0xe8, 0x1a, 0x65, 0x2c, 0xee, 0x21, 0xaa, 0xa8, 0xef, 0xe5, 0x50, 0x1b, 0xe1, 0x25,
-	0x41, 0xb6, 0x79, 0xda, 0x1e, 0xd8, 0xf0, 0x32, 0xd3, 0xfc, 0x57, 0xc6, 0x07, 0xd8, 0xd1, 0x64,
-	0xc7, 0xc9, 0x16, 0xb5, 0x1a, 0x9b, 0xee, 0xa4, 0x84, 0x9f, 0x78, 0x59, 0x9c, 0x6f, 0x67, 0xfa,
-	0x3a, 0xc3, 0xf4, 0xdb, 0x83, 0xf5, 0x62, 0xc6, 0x47, 0xd8, 0x54, 0x34, 0x5e, 0x8a, 0x52, 0x35,
-	0xa4, 0xd5, 0x12, 0x00, 0xd7, 0x80, 0xf1, 0x92, 0x83, 0x93, 0x8f, 0xac, 0xe2, 0x2d, 0xac, 0xaa,
-	0xb6, 0xee, 0x34, 0x9f, 0x8d, 0xf2, 0x79, 0x41, 0x01, 0xeb, 0xa5, 0x81, 0x08, 0x12, 0x3f, 0x8b,
-	0xf3, 0xeb, 0x8a, 0x7b, 0x60, 0x77, 0x21, 0x1b, 0xab, 0xb4, 0x08, 0xff, 0xdd, 0x8e, 0x9d, 0xfa,
-	0xec, 0x44, 0xbc, 0x03, 0xe8, 0x06, 0xd7, 0xbc, 0x95, 0xa6, 0x15, 0x2b, 0xae, 0x1d, 0x33, 0x79,
-	0x93, 0xa6, 0x4d, 0xdf, 0x21, 0x74, 0x0e, 0x44, 0x08, 0x1b, 0x4d, 0xbd, 0xf0, 0x38, 0x88, 0x67,
-	0xdc, 0x81, 0x6f, 0x49, 0xf8, 0x4c, 0x7c, 0x4b, 0x78, 0x0f, 0xa0, 0xce, 0xaa, 0x9a, 0xac, 0x2c,
-	0x4f, 0x4a, 0x04, 0x5c, 0xf5, 0x0f, 0x39, 0xde, 0x7c, 0xf2, 0xc3, 0x0b, 0x7e, 0xf8, 0x4f, 0x00,
-	0x00, 0x00, 0xff, 0xff, 0x78, 0x37, 0x3e, 0x6a, 0x7d, 0x01, 0x00, 0x00,
+	// 342 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x5f, 0x4b, 0xc3, 0x30,
+	0x14, 0xc5, 0x69, 0xd7, 0xb9, 0xf6, 0xee, 0x0f, 0x18, 0x7c, 0xc8, 0x8b, 0x32, 0x0a, 0xc2, 0xa6,
+	0x30, 0xd0, 0x07, 0xdf, 0x9d, 0x22, 0x22, 0x0c, 0x24, 0xe0, 0x8b, 0x08, 0x25, 0xeb, 0x52, 0x5b,
+	0x58, 0x9b, 0x90, 0x64, 0xb0, 0x7d, 0x57, 0x3f, 0x8c, 0xe4, 0xa6, 0xd3, 0x82, 0x2f, 0xbe, 0xdd,
+	0x7b, 0x0e, 0xf7, 0xdc, 0x5f, 0xc2, 0x05, 0x30, 0x6b, 0xb9, 0x5f, 0x28, 0x2d, 0xad, 0x24, 0x91,
+	0xab, 0xd3, 0x0f, 0x88, 0x57, 0xbc, 0xa9, 0x0a, 0x61, 0x2c, 0x99, 0x43, 0x9c, 0xcb, 0xba, 0xe6,
+	0xcd, 0xc6, 0xd0, 0x60, 0xda, 0x9b, 0x0d, 0x6f, 0xc7, 0x0b, 0x1c, 0x78, 0xf0, 0x2a, 0xfb, 0xb1,
+	0xc9, 0x25, 0x4c, 0xe4, 0xce, 0xaa, 0x9d, 0xcd, 0x36, 0x42, 0x15, 0xd5, 0x56, 0xd0, 0x70, 0x1a,
+	0xcc, 0x12, 0x36, 0xf6, 0xea, 0xa3, 0x17, 0xd3, 0xaf, 0x00, 0x06, 0xed, 0x30, 0xb9, 0x86, 0x61,
+	0x2e, 0xd5, 0x21, 0x5b, 0x8b, 0x42, 0x6a, 0xd1, 0x2e, 0x80, 0xe3, 0x02, 0x75, 0x60, 0xe0, 0xec,
+	0x25, 0xba, 0xe4, 0x0c, 0xfa, 0x79, 0xb9, 0xa9, 0x34, 0xc6, 0xc6, 0xcc, 0x37, 0x84, 0xc2, 0xa0,
+	0x25, 0xa0, 0xbd, 0x69, 0x38, 0x4b, 0xd8, 0xb1, 0x25, 0x73, 0xc0, 0xe9, 0x8c, 0x17, 0x56, 0x68,
+	0x1a, 0xfd, 0xc9, 0x4e, 0x9c, 0x7b, 0xef, 0x4c, 0x72, 0x0e, 0x50, 0x35, 0x8e, 0xbc, 0xe4, 0xa6,
+	0xa4, 0x7d, 0xc4, 0x4e, 0x50, 0x79, 0xe6, 0xa6, 0x24, 0x57, 0x90, 0x68, 0xa3, 0x32, 0x87, 0x6f,
+	0xe8, 0x49, 0xf7, 0x17, 0x98, 0x51, 0x4f, 0xd5, 0x56, 0xb0, 0x58, 0xfb, 0xc2, 0xa4, 0x2f, 0x10,
+	0xb9, 0x74, 0x42, 0x20, 0x2a, 0xb4, 0xac, 0x69, 0x80, 0x50, 0x58, 0x93, 0x09, 0x84, 0x56, 0xd2,
+	0x10, 0x95, 0xd0, 0x4a, 0x72, 0x01, 0x20, 0xf6, 0x22, 0xdf, 0x59, 0xbe, 0xde, 0x0a, 0xda, 0xc3,
+	0x67, 0x75, 0x94, 0xf4, 0x0d, 0x06, 0xed, 0x02, 0x8c, 0x73, 0x5f, 0x7a, 0x8c, 0x73, 0xda, 0x1d,
+	0x8c, 0x15, 0xb7, 0x65, 0x56, 0x73, 0xa5, 0xaa, 0xe6, 0xd3, 0xd0, 0x10, 0xd1, 0x4e, 0x3d, 0xda,
+	0x2b, 0xb7, 0xe5, 0xca, 0x3b, 0x6c, 0xa4, 0x7e, 0x1b, 0x93, 0xde, 0xc0, 0xb0, 0x63, 0xfe, 0x87,
+	0x74, 0x39, 0x7a, 0xc7, 0x33, 0xc9, 0xf0, 0x4c, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff, 0x83, 0x82,
+	0xb0, 0xc3, 0x33, 0x02, 0x00, 0x00,
 }
diff --git a/cmd/sbox/sbox_proto/sbox.proto b/cmd/sbox/sbox_proto/sbox.proto
index 695b0e8..bdf92c6 100644
--- a/cmd/sbox/sbox_proto/sbox.proto
+++ b/cmd/sbox/sbox_proto/sbox.proto
@@ -47,6 +47,10 @@
   // An optional hash of the input files to ensure the textproto files and the sbox rule reruns
   // when the lists of inputs changes, even if the inputs are not on the command line.
   optional string input_hash = 5;
+
+  // A list of files that will be copied before the sandboxed command, and whose contents should be
+  // copied as if they were listed in copy_before.
+  repeated RspFile rsp_files = 6;
 }
 
 // Copy describes a from-to pair of files to copy.  The paths may be relative, the root that they
@@ -58,4 +62,19 @@
 
   // If true, make the file executable after copying it.
   optional bool executable = 3;
-}
\ No newline at end of file
+}
+
+// RspFile describes an rspfile that should be copied into the sandbox directory.
+message RspFile {
+  // The path to the rsp file.
+  required string file = 1;
+
+  // A list of path mappings that should be applied to each file listed in the rsp file.
+  repeated PathMapping path_mappings = 2;
+}
+
+// PathMapping describes a mapping from a path outside the sandbox to the path inside the sandbox.
+message PathMapping {
+  required string from = 1;
+  required string to = 2;
+}
diff --git a/java/builder.go b/java/builder.go
index fc740a8..cde8731 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -150,7 +150,7 @@
 		&remoteexec.REParams{Labels: map[string]string{"type": "tool", "name": "turbine"},
 			ExecStrategy:      "${config.RETurbineExecStrategy}",
 			Inputs:            []string{"${config.TurbineJar}", "${out}.rsp", "$implicits"},
-			RSPFile:           "${out}.rsp",
+			RSPFiles:          []string{"${out}.rsp"},
 			OutputFiles:       []string{"$out.tmp"},
 			OutputDirectories: []string{"$outDir"},
 			ToolchainInputs:   []string{"${config.JavaCmd}"},
@@ -167,7 +167,7 @@
 		&remoteexec.REParams{
 			ExecStrategy: "${config.REJarExecStrategy}",
 			Inputs:       []string{"${config.SoongZipCmd}", "${out}.rsp"},
-			RSPFile:      "${out}.rsp",
+			RSPFiles:     []string{"${out}.rsp"},
 			OutputFiles:  []string{"$out"},
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		}, []string{"jarArgs"}, nil)
@@ -182,7 +182,7 @@
 		&remoteexec.REParams{
 			ExecStrategy: "${config.REZipExecStrategy}",
 			Inputs:       []string{"${config.SoongZipCmd}", "${out}.rsp", "$implicits"},
-			RSPFile:      "${out}.rsp",
+			RSPFiles:     []string{"${out}.rsp"},
 			OutputFiles:  []string{"$out"},
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		}, []string{"jarArgs"}, []string{"implicits"})
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 6dc408b..7137f33 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -214,7 +214,7 @@
 var artApexNames = []string{
 	"com.android.art",
 	"com.android.art.debug",
-	"com.android.art,testing",
+	"com.android.art.testing",
 	"com.google.android.art",
 	"com.google.android.art.debug",
 	"com.google.android.art.testing",
diff --git a/java/droiddoc.go b/java/droiddoc.go
index a892b36..0becb84 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -1209,7 +1209,7 @@
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
 	cmd := rule.Command()
-	cmd.FlagWithArg("ANDROID_SDK_HOME=", homeDir.String())
+	cmd.FlagWithArg("ANDROID_PREFS_ROOT=", homeDir.String())
 
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA") {
 		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
@@ -1231,10 +1231,10 @@
 			Labels:               labels,
 			ExecStrategy:         execStrategy,
 			Inputs:               inputs,
-			RSPFile:              implicitsRsp.String(),
+			RSPFiles:             []string{implicitsRsp.String()},
 			ToolchainInputs:      []string{config.JavaCmd(ctx).String()},
 			Platform:             map[string]string{remoteexec.PoolKey: pool},
-			EnvironmentVariables: []string{"ANDROID_SDK_HOME"},
+			EnvironmentVariables: []string{"ANDROID_PREFS_ROOT"},
 		}).NoVarTemplate(ctx.Config().RBEWrapper()))
 	}
 
diff --git a/java/java_test.go b/java/java_test.go
index 9924be7..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) {
@@ -1429,7 +1429,7 @@
 	barStubsOutput := barStubsOutputs[0]
 	barDoc := ctx.ModuleForTests("bar-doc", "android_common")
 	javaDoc := barDoc.Rule("javadoc").RelativeToTop()
-	if g, w := javaDoc.Implicits.Strings(), barStubsOutput.String(); !inList(w, g) {
+	if g, w := android.PathsRelativeToTop(javaDoc.Implicits), android.PathRelativeToTop(barStubsOutput); !inList(w, g) {
 		t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g)
 	}
 
@@ -1439,7 +1439,7 @@
 	}
 
 	aidl := barDoc.Rule("aidl")
-	if g, w := javaDoc.Implicits.Strings(), aidl.Output.String(); !inList(w, g) {
+	if g, w := android.PathsRelativeToTop(javaDoc.Implicits), android.PathRelativeToTop(aidl.Output); !inList(w, g) {
 		t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g)
 	}
 
diff --git a/java/lint.go b/java/lint.go
index 938e2b0..475e8dc 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -182,11 +182,6 @@
 	cacheDir   android.WritablePath
 	homeDir    android.WritablePath
 	srcjarDir  android.WritablePath
-
-	deps android.Paths
-
-	remoteInputs    android.Paths
-	remoteRSPInputs android.Paths
 }
 
 func lintRBEExecStrategy(ctx android.ModuleContext) string {
@@ -194,39 +189,6 @@
 }
 
 func (l *linter) writeLintProjectXML(ctx android.ModuleContext, rule *android.RuleBuilder) lintPaths {
-	var deps android.Paths
-	var remoteInputs android.Paths
-	var remoteRSPInputs android.Paths
-
-	// Paths passed to trackInputDependency will be added as dependencies of the rule that runs
-	// lint and passed as inputs to the remote execution proxy.
-	trackInputDependency := func(paths ...android.Path) {
-		deps = append(deps, paths...)
-		remoteInputs = append(remoteInputs, paths...)
-	}
-
-	// Paths passed to trackRSPDependency will be added as dependencies of the rule that runs
-	// lint, but the RSP file will be used by the remote execution proxy to find the files so that
-	// it doesn't overflow command line limits.
-	trackRSPDependency := func(paths android.Paths, rsp android.Path) {
-		deps = append(deps, paths...)
-		remoteRSPInputs = append(remoteRSPInputs, rsp)
-	}
-
-	var resourcesList android.WritablePath
-	if len(l.resources) > 0 {
-		// The list of resources may be too long to put on the command line, but
-		// we can't use the rsp file because it is already being used for srcs.
-		// Insert a second rule to write out the list of resources to a file.
-		resourcesList = android.PathForModuleOut(ctx, "resources.list")
-		resListRule := android.NewRuleBuilder(pctx, ctx)
-		resListRule.Command().Text("cp").
-			FlagWithRspFileInputList("", resourcesList.ReplaceExtension(ctx, "rsp"), l.resources).
-			Output(resourcesList)
-		resListRule.Build("lint_resources_list", "lint resources list")
-		trackRSPDependency(l.resources, resourcesList)
-	}
-
 	projectXMLPath := android.PathForModuleOut(ctx, "lint", "project.xml")
 	// Lint looks for a lint.xml file next to the project.xml file, give it one.
 	configXMLPath := android.PathForModuleOut(ctx, "lint", "lint.xml")
@@ -235,20 +197,6 @@
 
 	srcJarDir := android.PathForModuleOut(ctx, "lint", "srcjars")
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
-	// TODO(ccross): this is a little fishy.  The files extracted from the srcjars are referenced
-	// by the project.xml and used by the later lint rule, but the lint rule depends on the srcjars,
-	// not the extracted files.
-	trackRSPDependency(l.srcJars, srcJarList)
-
-	// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
-	// lint separately.
-	srcsList := android.PathForModuleOut(ctx, "lint", "srcs.list")
-	srcsListRsp := android.PathForModuleOut(ctx, "lint-srcs.list.rsp")
-	rule.Command().Text("cp").
-		FlagWithRspFileInputList("", srcsListRsp, l.srcs).
-		Output(srcsList)
-	trackRSPDependency(l.srcs, srcsList)
-	rule.Temporary(srcsList)
 
 	cmd := rule.Command().
 		BuiltTool("lint-project-xml").
@@ -263,32 +211,31 @@
 		cmd.Flag("--test")
 	}
 	if l.manifest != nil {
-		cmd.FlagWithArg("--manifest ", cmd.PathForInput(l.manifest))
-		trackInputDependency(l.manifest)
+		cmd.FlagWithInput("--manifest ", l.manifest)
 	}
 	if l.mergedManifest != nil {
-		cmd.FlagWithArg("--merged_manifest ", cmd.PathForInput(l.mergedManifest))
-		trackInputDependency(l.mergedManifest)
+		cmd.FlagWithInput("--merged_manifest ", l.mergedManifest)
 	}
 
-	cmd.FlagWithInput("--srcs ", srcsList)
+	// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
+	// lint separately.
+	srcsList := android.PathForModuleOut(ctx, "lint-srcs.list")
+	cmd.FlagWithRspFileInputList("--srcs ", srcsList, l.srcs)
 
 	cmd.FlagWithInput("--generated_srcs ", srcJarList)
 
-	if resourcesList != nil {
-		cmd.FlagWithInput("--resources ", resourcesList)
+	if len(l.resources) > 0 {
+		resourcesList := android.PathForModuleOut(ctx, "lint-resources.list")
+		cmd.FlagWithRspFileInputList("--resources ", resourcesList, l.resources)
 	}
 
 	if l.classes != nil {
-		cmd.FlagWithArg("--classes ", cmd.PathForInput(l.classes))
-		trackInputDependency(l.classes)
+		cmd.FlagWithInput("--classes ", l.classes)
 	}
 
-	cmd.FlagForEachArg("--classpath ", cmd.PathsForInputs(l.classpath))
-	trackInputDependency(l.classpath...)
+	cmd.FlagForEachInput("--classpath ", l.classpath)
 
-	cmd.FlagForEachArg("--extra_checks_jar ", cmd.PathsForInputs(l.extraLintCheckJars))
-	trackInputDependency(l.extraLintCheckJars...)
+	cmd.FlagForEachInput("--extra_checks_jar ", l.extraLintCheckJars)
 
 	cmd.FlagWithArg("--root_dir ", "$PWD")
 
@@ -309,11 +256,6 @@
 		configXML:  configXMLPath,
 		cacheDir:   cacheDir,
 		homeDir:    homeDir,
-
-		deps: deps,
-
-		remoteInputs:    remoteInputs,
-		remoteRSPInputs: remoteRSPInputs,
 	}
 
 }
@@ -424,8 +366,7 @@
 		Flag("--exitcode").
 		Flags(l.properties.Lint.Flags).
 		Implicit(annotationsZipPath).
-		Implicit(apiVersionsXMLPath).
-		Implicits(lintPaths.deps)
+		Implicit(apiVersionsXMLPath)
 
 	rule.Temporary(lintPaths.projectXML)
 	rule.Temporary(lintPaths.configXML)
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 03e82c2..c3d13ae 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -221,12 +221,40 @@
 	metadata android.Path
 }
 
+// isModulePreferredByCompatConfig checks to see whether the module is preferred for use by
+// platform compat config.
+func isModulePreferredByCompatConfig(module android.Module) bool {
+	// A versioned prebuilt_platform_compat_config, i.e. foo-platform-compat-config@current should be
+	// ignored.
+	if s, ok := module.(android.SdkAware); ok {
+		if !s.ContainingSdk().Unversioned() {
+			return false
+		}
+	}
+
+	// A prebuilt module should only be used when it is preferred.
+	if pi, ok := module.(android.PrebuiltInterface); ok {
+		if p := pi.Prebuilt(); p != nil {
+			return p.UsePrebuilt()
+		}
+	}
+
+	// Otherwise, a module should only be used if it has not been replaced by a prebuilt.
+	return !module.IsReplacedByPrebuilt()
+}
+
 func (p *platformCompatConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 
 	var compatConfigMetadata android.Paths
 
 	ctx.VisitAllModules(func(module android.Module) {
+		if !module.Enabled() {
+			return
+		}
 		if c, ok := module.(platformCompatConfigMetadataProvider); ok {
+			if !isModulePreferredByCompatConfig(module) {
+				return
+			}
 			metadata := c.compatConfigMetadata()
 			compatConfigMetadata = append(compatConfigMetadata, metadata)
 		}
diff --git a/remoteexec/remoteexec.go b/remoteexec/remoteexec.go
index 166f68c..ef4672a 100644
--- a/remoteexec/remoteexec.go
+++ b/remoteexec/remoteexec.go
@@ -64,9 +64,8 @@
 	ExecStrategy string
 	// Inputs is a list of input paths or ninja variables.
 	Inputs []string
-	// RSPFile is the name of the ninja variable used by the rule as a placeholder for an rsp
-	// input.
-	RSPFile string
+	// RSPFiles is the name of the files used by the rule as a placeholder for an rsp input.
+	RSPFiles []string
 	// OutputFiles is a list of output file paths or ninja variables as placeholders for rule
 	// outputs.
 	OutputFiles []string
@@ -134,8 +133,8 @@
 		args += " --inputs=" + strings.Join(r.Inputs, ",")
 	}
 
-	if r.RSPFile != "" {
-		args += " --input_list_paths=" + r.RSPFile
+	if len(r.RSPFiles) > 0 {
+		args += " --input_list_paths=" + strings.Join(r.RSPFiles, ",")
 	}
 
 	if len(r.OutputFiles) > 0 {
diff --git a/remoteexec/remoteexec_test.go b/remoteexec/remoteexec_test.go
index 875aa6a..b117b89 100644
--- a/remoteexec/remoteexec_test.go
+++ b/remoteexec/remoteexec_test.go
@@ -45,14 +45,14 @@
 				Inputs:          []string{"$in"},
 				OutputFiles:     []string{"$out"},
 				ExecStrategy:    "remote",
-				RSPFile:         "$out.rsp",
+				RSPFiles:        []string{"$out.rsp", "out2.rsp"},
 				ToolchainInputs: []string{"clang++"},
 				Platform: map[string]string{
 					ContainerImageKey: DefaultImage,
 					PoolKey:           "default",
 				},
 			},
-			want: fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=remote --inputs=$in --input_list_paths=$out.rsp --output_files=$out --toolchain_inputs=clang++ -- ", DefaultImage),
+			want: fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=remote --inputs=$in --input_list_paths=$out.rsp,out2.rsp --output_files=$out --toolchain_inputs=clang++ -- ", DefaultImage),
 		},
 	}
 	for _, test := range tests {
diff --git a/response/Android.bp b/response/Android.bp
new file mode 100644
index 0000000..e19981f
--- /dev/null
+++ b/response/Android.bp
@@ -0,0 +1,16 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-response",
+    pkgPath: "android/soong/response",
+    deps: [
+    ],
+    srcs: [
+        "response.go",
+    ],
+    testSrcs: [
+        "response_test.go",
+    ],
+}
diff --git a/response/response.go b/response/response.go
new file mode 100644
index 0000000..b65503e
--- /dev/null
+++ b/response/response.go
@@ -0,0 +1,112 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package response
+
+import (
+	"io"
+	"io/ioutil"
+	"strings"
+	"unicode"
+)
+
+const noQuote = '\x00'
+
+// ReadRspFile reads a file in Ninja's response file format and returns its contents.
+func ReadRspFile(r io.Reader) ([]string, error) {
+	var files []string
+	var file []byte
+
+	buf, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+
+	isEscaping := false
+	quotingStart := byte(noQuote)
+	for _, c := range buf {
+		switch {
+		case isEscaping:
+			if quotingStart == '"' {
+				if !(c == '"' || c == '\\') {
+					// '\"' or '\\' will be escaped under double quoting.
+					file = append(file, '\\')
+				}
+			}
+			file = append(file, c)
+			isEscaping = false
+		case c == '\\' && quotingStart != '\'':
+			isEscaping = true
+		case quotingStart == noQuote && (c == '\'' || c == '"'):
+			quotingStart = c
+		case quotingStart != noQuote && c == quotingStart:
+			quotingStart = noQuote
+		case quotingStart == noQuote && unicode.IsSpace(rune(c)):
+			// Current character is a space outside quotes
+			if len(file) != 0 {
+				files = append(files, string(file))
+			}
+			file = file[:0]
+		default:
+			file = append(file, c)
+		}
+	}
+
+	if len(file) != 0 {
+		files = append(files, string(file))
+	}
+
+	return files, nil
+}
+
+func rspUnsafeChar(r rune) bool {
+	switch {
+	case 'A' <= r && r <= 'Z',
+		'a' <= r && r <= 'z',
+		'0' <= r && r <= '9',
+		r == '_',
+		r == '+',
+		r == '-',
+		r == '.',
+		r == '/':
+		return false
+	default:
+		return true
+	}
+}
+
+var rspEscaper = strings.NewReplacer(`'`, `'\''`)
+
+// WriteRspFile writes a list of files to a file in Ninja's response file format.
+func WriteRspFile(w io.Writer, files []string) error {
+	for i, f := range files {
+		if i != 0 {
+			_, err := io.WriteString(w, " ")
+			if err != nil {
+				return err
+			}
+		}
+
+		if strings.IndexFunc(f, rspUnsafeChar) != -1 {
+			f = `'` + rspEscaper.Replace(f) + `'`
+		}
+
+		_, err := io.WriteString(w, f)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/response/response_test.go b/response/response_test.go
new file mode 100644
index 0000000..4d1fb41
--- /dev/null
+++ b/response/response_test.go
@@ -0,0 +1,123 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package response
+
+import (
+	"bytes"
+	"reflect"
+	"testing"
+)
+
+func TestReadRspFile(t *testing.T) {
+	testCases := []struct {
+		name, in string
+		out      []string
+	}{
+		{
+			name: "single quoting test case 1",
+			in:   `./cmd '"'-C`,
+			out:  []string{"./cmd", `"-C`},
+		},
+		{
+			name: "single quoting test case 2",
+			in:   `./cmd '-C`,
+			out:  []string{"./cmd", `-C`},
+		},
+		{
+			name: "single quoting test case 3",
+			in:   `./cmd '\"'-C`,
+			out:  []string{"./cmd", `\"-C`},
+		},
+		{
+			name: "single quoting test case 4",
+			in:   `./cmd '\\'-C`,
+			out:  []string{"./cmd", `\\-C`},
+		},
+		{
+			name: "none quoting test case 1",
+			in:   `./cmd \'-C`,
+			out:  []string{"./cmd", `'-C`},
+		},
+		{
+			name: "none quoting test case 2",
+			in:   `./cmd \\-C`,
+			out:  []string{"./cmd", `\-C`},
+		},
+		{
+			name: "none quoting test case 3",
+			in:   `./cmd \"-C`,
+			out:  []string{"./cmd", `"-C`},
+		},
+		{
+			name: "double quoting test case 1",
+			in:   `./cmd "'"-C`,
+			out:  []string{"./cmd", `'-C`},
+		},
+		{
+			name: "double quoting test case 2",
+			in:   `./cmd "\\"-C`,
+			out:  []string{"./cmd", `\-C`},
+		},
+		{
+			name: "double quoting test case 3",
+			in:   `./cmd "\""-C`,
+			out:  []string{"./cmd", `"-C`},
+		},
+		{
+			name: "ninja rsp file",
+			in:   "'a'\nb\n'@'\n'foo'\\''bar'\n'foo\"bar'",
+			out:  []string{"a", "b", "@", "foo'bar", `foo"bar`},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			got, err := ReadRspFile(bytes.NewBuffer([]byte(testCase.in)))
+			if err != nil {
+				t.Errorf("unexpected error: %q", err)
+			}
+			if !reflect.DeepEqual(got, testCase.out) {
+				t.Errorf("expected %q got %q", testCase.out, got)
+			}
+		})
+	}
+}
+
+func TestWriteRspFile(t *testing.T) {
+	testCases := []struct {
+		name string
+		in   []string
+		out  string
+	}{
+		{
+			name: "ninja rsp file",
+			in:   []string{"a", "b", "@", "foo'bar", `foo"bar`},
+			out:  "a b '@' 'foo'\\''bar' 'foo\"bar'",
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			buf := &bytes.Buffer{}
+			err := WriteRspFile(buf, testCase.in)
+			if err != nil {
+				t.Errorf("unexpected error: %q", err)
+			}
+			if buf.String() != testCase.out {
+				t.Errorf("expected %q got %q", testCase.out, buf.String())
+			}
+		})
+	}
+}
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 9626a04..a886a18 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -486,6 +486,9 @@
 		}
 	`)
 
+	// TODO(b/183322862): Remove this and fix the issue.
+	errorHandler := android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module source path "snapshot/include_gen/generated_foo/gen/protos" does not exist`)
+
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -518,6 +521,9 @@
 .intermediates/mynativelib/android_arm64_armv8-a_shared/mynativelib.so -> arm64/lib/mynativelib.so
 .intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so
 `),
+		snapshotTestErrorHandler(checkSnapshotWithoutSource, errorHandler),
+		snapshotTestErrorHandler(checkSnapshotWithSourcePreferred, errorHandler),
+		snapshotTestErrorHandler(checkSnapshotPreferredWithSource, errorHandler),
 	)
 }
 
@@ -1816,7 +1822,10 @@
 .intermediates/mynativelib/android_arm64_armv8-a_static/mynativelib.a -> arm64/lib/mynativelib.a
 .intermediates/mynativelib/android_arm64_armv8-a_shared/mynativelib.so -> arm64/lib/mynativelib.so
 .intermediates/mynativelib/android_arm_armv7-a-neon_static/mynativelib.a -> arm/lib/mynativelib.a
-.intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so`),
+.intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so
+`),
+		// TODO(b/183315522): Remove this and fix the issue.
+		snapshotTestErrorHandler(checkSnapshotPreferredWithSource, android.FixtureExpectsAtLeastOneErrorMatchingPattern(`\Qunrecognized property "arch.arm.shared.export_include_dirs"\E`)),
 	)
 }
 
@@ -2664,6 +2673,11 @@
 		}
 	`)
 
+	// Mixing the snapshot with the source (irrespective of which one is preferred) causes a problem
+	// due to missing variants.
+	// TODO(b/183204176): Remove this and fix the cause.
+	snapshotWithSourceErrorHandler := android.FixtureExpectsAtLeastOneErrorMatchingPattern(`\QReplaceDependencies could not find identical variant {os:android,image:,arch:arm64_armv8-a,sdk:,link:shared,version:} for module mynativelib\E`)
+
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -2688,6 +2702,9 @@
 		checkAllCopyRules(`
 myinclude/Test.h -> include/myinclude/Test.h
 arm64/include/Arm64Test.h -> arm64/include/arm64/include/Arm64Test.h
-.intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so`),
+.intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so
+`),
+		snapshotTestErrorHandler(checkSnapshotWithSourcePreferred, snapshotWithSourceErrorHandler),
+		snapshotTestErrorHandler(checkSnapshotPreferredWithSource, snapshotWithSourceErrorHandler),
 	)
 }
diff --git a/sdk/compat_config_sdk_test.go b/sdk/compat_config_sdk_test.go
index dffc02a..00073c2 100644
--- a/sdk/compat_config_sdk_test.go
+++ b/sdk/compat_config_sdk_test.go
@@ -66,5 +66,26 @@
 		checkAllCopyRules(`
 .intermediates/myconfig/android_common/myconfig_meta.xml -> compat_configs/myconfig/myconfig_meta.xml
 `),
+		snapshotTestChecker(checkSnapshotWithoutSource,
+			func(t *testing.T, result *android.TestResult) {
+				// Make sure that the snapshot metadata is collated by the platform compat config singleton.
+				java.CheckMergedCompatConfigInputs(t, result, "snapshot module", "snapshot/compat_configs/myconfig/myconfig_meta.xml")
+			}),
+
+		snapshotTestChecker(checkSnapshotWithSourcePreferred,
+			func(t *testing.T, result *android.TestResult) {
+				// Make sure that the snapshot metadata is collated by the platform compat config singleton.
+				java.CheckMergedCompatConfigInputs(t, result, "snapshot module",
+					"out/soong/.intermediates/myconfig/android_common/myconfig_meta.xml",
+				)
+			}),
+
+		snapshotTestChecker(checkSnapshotPreferredWithSource,
+			func(t *testing.T, result *android.TestResult) {
+				// Make sure that the snapshot metadata is collated by the platform compat config singleton.
+				java.CheckMergedCompatConfigInputs(t, result, "snapshot module",
+					"snapshot/compat_configs/myconfig/myconfig_meta.xml",
+				)
+			}),
 	)
 }
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index fa0eb3f..2bc248d 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -24,6 +24,16 @@
 var prepareForSdkTestWithJava = android.GroupFixturePreparers(
 	java.PrepareForTestWithJavaBuildComponents,
 	PrepareForTestWithSdkBuildComponents,
+
+	// Ensure that all source paths are provided. This helps ensure that the snapshot generation is
+	// consistent and all files referenced from the snapshot's Android.bp file have actually been
+	// copied into the snapshot.
+	android.PrepareForTestDisallowNonExistentPaths,
+
+	// Files needs by most of the tests.
+	android.MockFS{
+		"Test.java": nil,
+	}.AddToFixture(),
 )
 
 var prepareForSdkTestWithJavaSdkLibrary = android.GroupFixturePreparers(
@@ -48,42 +58,17 @@
 			system_modules: "none",
 			sdk_version: "none",
 		}
-
-		java_import {
-			name: "sdkmember",
-			prefer: true,
-			jars: ["prebuilt.jar"],
-		}
 	`)
 
 	// Make sure that the mysdk module depends on "sdkmember" and not "prebuilt_sdkmember".
-	java.CheckModuleDependencies(t, result.TestContext, "mysdk", "android_common", []string{"sdkmember"})
+	sdkChecker := func(t *testing.T, result *android.TestResult) {
+		java.CheckModuleDependencies(t, result.TestContext, "mysdk", "android_common", []string{"sdkmember"})
+	}
 
 	CheckSnapshot(t, result, "mysdk", "",
-		checkAndroidBpContents(`// This is auto-generated. DO NOT EDIT.
-
-java_import {
-    name: "mysdk_sdkmember@current",
-    sdk_member_name: "sdkmember",
-    visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    jars: ["java/sdkmember.jar"],
-}
-
-java_import {
-    name: "sdkmember",
-    prefer: false,
-    visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    jars: ["java/sdkmember.jar"],
-}
-
-sdk_snapshot {
-    name: "mysdk@current",
-    visibility: ["//visibility:public"],
-    java_header_libs: ["mysdk_sdkmember@current"],
-}
-`))
+		snapshotTestChecker(checkSnapshotWithSourcePreferred, sdkChecker),
+		snapshotTestChecker(checkSnapshotPreferredWithSource, sdkChecker),
+	)
 }
 
 func TestBasicSdkWithJavaLibrary(t *testing.T) {
@@ -364,6 +349,7 @@
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
+		android.FixtureAddFile("resource.txt", nil),
 	).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
@@ -419,7 +405,11 @@
 }
 
 func TestSnapshotWithJavaBootLibrary(t *testing.T) {
-	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureAddFile("aidl", nil),
+		android.FixtureAddFile("resource.txt", nil),
+	).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
 			java_boot_libs: ["myjavalib"],
@@ -1564,7 +1554,10 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_DoctagFiles(t *testing.T) {
-	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJavaSdkLibrary,
+		android.FixtureAddFile("docs/known_doctags", nil),
+	).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
diff --git a/sdk/sdk.go b/sdk/sdk.go
index e561529..b60fb18 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -486,6 +486,15 @@
 			// sdk containing sdkmember.
 			memberName := versionedSdkMember.MemberName()
 
+			// Convert a panic into a normal error to allow it to be more easily tested for. This is a
+			// temporary workaround, once http://b/183204176 has been fixed this can be removed.
+			// TODO(b/183204176): Remove this after fixing.
+			defer func() {
+				if r := recover(); r != nil {
+					mctx.ModuleErrorf("%s", r)
+				}
+			}()
+
 			// Replace dependencies on sdkmember with a dependency on the current module which
 			// is a versioned prebuilt of the sdkmember if required.
 			mctx.ReplaceDependenciesIf(memberName, func(from blueprint.Module, tag blueprint.DependencyTag, to blueprint.Module) bool {
diff --git a/sdk/testing.go b/sdk/testing.go
index ba40f67..44970f7 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -48,10 +48,10 @@
 		"system/sepolicy/apex/myapex-file_contexts":    nil,
 		"system/sepolicy/apex/myapex2-file_contexts":   nil,
 		"system/sepolicy/apex/mysdkapex-file_contexts": nil,
-		"myapex.avbpubkey":                             nil,
-		"myapex.pem":                                   nil,
-		"myapex.x509.pem":                              nil,
-		"myapex.pk8":                                   nil,
+		"sdk/tests/myapex.avbpubkey":                   nil,
+		"sdk/tests/myapex.pem":                         nil,
+		"sdk/tests/myapex.x509.pem":                    nil,
+		"sdk/tests/myapex.pk8":                         nil,
 	}),
 )
 
@@ -80,6 +80,12 @@
 			{android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", "", true},
 		}
 	}),
+
+	// Make sure that every test provides all the source files.
+	android.PrepareForTestDisallowNonExistentPaths,
+	android.MockFS{
+		"Test.java": nil,
+	}.AddToFixture(),
 )
 
 var PrepareForTestWithSdkBuildComponents = android.GroupFixturePreparers(
@@ -125,6 +131,7 @@
 		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
+		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
 	}
 
 	buildParams := sdk.BuildParamsForTests()
@@ -183,6 +190,24 @@
 	return info
 }
 
+// The enum of different sdk snapshot tests performed by CheckSnapshot.
+type snapshotTest int
+
+const (
+	// The enumeration of the different test configurations.
+	// A test with the snapshot/Android.bp file but without the original Android.bp file.
+	checkSnapshotWithoutSource snapshotTest = iota
+
+	// A test with both the original source and the snapshot, with the source preferred.
+	checkSnapshotWithSourcePreferred
+
+	// A test with both the original source and the snapshot, with the snapshot preferred.
+	checkSnapshotPreferredWithSource
+
+	// The directory into which the snapshot will be 'unpacked'.
+	snapshotSubDir = "snapshot"
+)
+
 // Check the snapshot build rules.
 //
 // Takes a list of functions which check different facets of the snapshot build rules.
@@ -214,31 +239,58 @@
 	// Populate a mock filesystem with the files that would have been copied by
 	// the rules.
 	fs := android.MockFS{}
-	snapshotSubDir := "snapshot"
 	for _, dest := range snapshotBuildInfo.snapshotContents {
 		fs[filepath.Join(snapshotSubDir, dest)] = nil
 	}
 	fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents)
 
-	preparer := result.Preparer()
+	// The preparers from the original source fixture.
+	sourcePreparers := result.Preparer()
 
-	// Process the generated bp file to make sure it is valid. Use the same preparer as was used to
-	// produce this result.
+	// Preparer to combine the snapshot and the source.
+	snapshotPreparer := android.GroupFixturePreparers(sourcePreparers, fs.AddToFixture())
+
+	var runSnapshotTestWithCheckers = func(t *testing.T, testConfig snapshotTest, extraPreparer android.FixturePreparer) {
+		customization := snapshotBuildInfo.snapshotTestCustomization(testConfig)
+
+		// TODO(b/183184375): Set Config.TestAllowNonExistentPaths = false to verify that all the
+		//  files the snapshot needs are actually copied into the snapshot.
+
+		// Run the snapshot with the snapshot preparer and the extra preparer, which must come after as
+		// it may need to modify parts of the MockFS populated by the snapshot preparer.
+		result := android.GroupFixturePreparers(snapshotPreparer, extraPreparer).
+			ExtendWithErrorHandler(customization.errorHandler).
+			RunTest(t)
+
+		// Perform any additional checks the test need on the result of processing the snapshot.
+		for _, checker := range customization.checkers {
+			checker(t, result)
+		}
+	}
+
 	t.Run("snapshot without source", func(t *testing.T) {
-		android.GroupFixturePreparers(
-			preparer,
-			// TODO(b/183184375): Set Config.TestAllowNonExistentPaths = false to verify that all the
-			//  files the snapshot needs are actually copied into the snapshot.
+		// Remove the source Android.bp file to make sure it works without.
+		removeSourceAndroidBp := android.FixtureModifyMockFS(func(fs android.MockFS) {
+			delete(fs, "Android.bp")
+		})
 
-			// Add the files (including bp) created for this snapshot to the test fixture.
-			fs.AddToFixture(),
+		runSnapshotTestWithCheckers(t, checkSnapshotWithoutSource, removeSourceAndroidBp)
+	})
 
-			// Remove the source Android.bp file to make sure it works without.
-			// TODO(b/183184375): Add a test with the source.
-			android.FixtureModifyMockFS(func(fs android.MockFS) {
-				delete(fs, "Android.bp")
-			}),
-		).RunTest(t)
+	t.Run("snapshot with source preferred", func(t *testing.T) {
+		runSnapshotTestWithCheckers(t, checkSnapshotWithSourcePreferred, android.NullFixturePreparer)
+	})
+
+	t.Run("snapshot preferred with source", func(t *testing.T) {
+		// Replace the snapshot/Android.bp file with one where "prefer: false," has been replaced with
+		// "prefer: true,"
+		preferPrebuilts := android.FixtureModifyMockFS(func(fs android.MockFS) {
+			snapshotBpFile := filepath.Join(snapshotSubDir, "Android.bp")
+			unpreferred := string(fs[snapshotBpFile])
+			fs[snapshotBpFile] = []byte(strings.ReplaceAll(unpreferred, "prefer: false,", "prefer: true,"))
+		})
+
+		runSnapshotTestWithCheckers(t, checkSnapshotPreferredWithSource, preferPrebuilts)
 	})
 }
 
@@ -312,6 +364,46 @@
 	}
 }
 
+type resultChecker func(t *testing.T, result *android.TestResult)
+
+// snapshotTestChecker registers a checker that will be run against the result of processing the
+// generated snapshot for the specified snapshotTest.
+func snapshotTestChecker(snapshotTest snapshotTest, checker resultChecker) snapshotBuildInfoChecker {
+	return func(info *snapshotBuildInfo) {
+		customization := info.snapshotTestCustomization(snapshotTest)
+		customization.checkers = append(customization.checkers, checker)
+	}
+}
+
+// snapshotTestErrorHandler registers an error handler to use when processing the snapshot
+// in the specific test case.
+//
+// Generally, the snapshot should work with all the test cases but some do not and just in case
+// there are a lot of issues to resolve, or it will take a lot of time this is a
+// get-out-of-jail-free card that allows progress to be made.
+//
+// deprecated: should only be used as a temporary workaround with an attached to do and bug.
+func snapshotTestErrorHandler(snapshotTest snapshotTest, handler android.FixtureErrorHandler) snapshotBuildInfoChecker {
+	return func(info *snapshotBuildInfo) {
+		customization := info.snapshotTestCustomization(snapshotTest)
+		customization.errorHandler = handler
+	}
+}
+
+// Encapsulates information provided by each test to customize a specific snapshotTest.
+type snapshotTestCustomization struct {
+	// Checkers that are run on the result of processing the preferred snapshot in a specific test
+	// case.
+	checkers []resultChecker
+
+	// Specify an error handler for when processing a specific test case.
+	//
+	// In some cases the generated snapshot cannot be used in a test configuration. Those cases are
+	// invariably bugs that need to be resolved but sometimes that can take a while. This provides a
+	// mechanism to temporarily ignore that error.
+	errorHandler android.FixtureErrorHandler
+}
+
 // Encapsulates information about the snapshot build structure in order to insulate tests from
 // knowing too much about internal structures.
 //
@@ -355,4 +447,21 @@
 
 	// The final output zip.
 	outputZip string
+
+	// The test specific customizations for each snapshot test.
+	snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization
+}
+
+// snapshotTestCustomization gets the test specific customization for the specified snapshotTest.
+//
+// If no customization was created previously then it creates a default customization.
+func (i *snapshotBuildInfo) snapshotTestCustomization(snapshotTest snapshotTest) *snapshotTestCustomization {
+	customization := i.snapshotTestCustomizations[snapshotTest]
+	if customization == nil {
+		customization = &snapshotTestCustomization{
+			errorHandler: android.FixtureExpectsNoErrors,
+		}
+		i.snapshotTestCustomizations[snapshotTest] = customization
+	}
+	return customization
 }
diff --git a/zip/Android.bp b/zip/Android.bp
index b28adbd..14541eb 100644
--- a/zip/Android.bp
+++ b/zip/Android.bp
@@ -25,6 +25,7 @@
         "android-archive-zip",
         "blueprint-pathtools",
         "soong-jar",
+        "soong-response",
     ],
     srcs: [
         "zip.go",
diff --git a/zip/cmd/main.go b/zip/cmd/main.go
index fc976f6..cbc73ed 100644
--- a/zip/cmd/main.go
+++ b/zip/cmd/main.go
@@ -24,7 +24,6 @@
 import (
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"runtime"
 	"runtime/pprof"
@@ -32,6 +31,7 @@
 	"strconv"
 	"strings"
 
+	"android/soong/response"
 	"android/soong/zip"
 )
 
@@ -125,12 +125,18 @@
 	var expandedArgs []string
 	for _, arg := range os.Args {
 		if strings.HasPrefix(arg, "@") {
-			bytes, err := ioutil.ReadFile(strings.TrimPrefix(arg, "@"))
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
 			if err != nil {
 				fmt.Fprintln(os.Stderr, err.Error())
 				os.Exit(1)
 			}
-			respArgs := zip.ReadRespFile(bytes)
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
 			expandedArgs = append(expandedArgs, respArgs...)
 		} else {
 			expandedArgs = append(expandedArgs, arg)
diff --git a/zip/zip.go b/zip/zip.go
index 088ed0d..a6490d4 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -29,7 +29,8 @@
 	"sync"
 	"syscall"
 	"time"
-	"unicode"
+
+	"android/soong/response"
 
 	"github.com/google/blueprint/pathtools"
 
@@ -164,14 +165,12 @@
 	}
 	defer f.Close()
 
-	list, err := ioutil.ReadAll(f)
+	arg := b.state
+	arg.SourceFiles, err = response.ReadRspFile(f)
 	if err != nil {
 		b.err = err
 		return b
 	}
-
-	arg := b.state
-	arg.SourceFiles = ReadRespFile(list)
 	for i := range arg.SourceFiles {
 		arg.SourceFiles[i] = pathtools.MatchEscape(arg.SourceFiles[i])
 	}
@@ -253,49 +252,6 @@
 	Filesystem pathtools.FileSystem
 }
 
-const NOQUOTE = '\x00'
-
-func ReadRespFile(bytes []byte) []string {
-	var args []string
-	var arg []rune
-
-	isEscaping := false
-	quotingStart := NOQUOTE
-	for _, c := range string(bytes) {
-		switch {
-		case isEscaping:
-			if quotingStart == '"' {
-				if !(c == '"' || c == '\\') {
-					// '\"' or '\\' will be escaped under double quoting.
-					arg = append(arg, '\\')
-				}
-			}
-			arg = append(arg, c)
-			isEscaping = false
-		case c == '\\' && quotingStart != '\'':
-			isEscaping = true
-		case quotingStart == NOQUOTE && (c == '\'' || c == '"'):
-			quotingStart = c
-		case quotingStart != NOQUOTE && c == quotingStart:
-			quotingStart = NOQUOTE
-		case quotingStart == NOQUOTE && unicode.IsSpace(c):
-			// Current character is a space outside quotes
-			if len(arg) != 0 {
-				args = append(args, string(arg))
-			}
-			arg = arg[:0]
-		default:
-			arg = append(arg, c)
-		}
-	}
-
-	if len(arg) != 0 {
-		args = append(args, string(arg))
-	}
-
-	return args
-}
-
 func zipTo(args ZipArgs, w io.Writer) error {
 	if args.EmulateJar {
 		args.AddDirectoryEntriesToZip = true
diff --git a/zip/zip_test.go b/zip/zip_test.go
index b456ef8..a37ae41 100644
--- a/zip/zip_test.go
+++ b/zip/zip_test.go
@@ -535,78 +535,6 @@
 	}
 }
 
-func TestReadRespFile(t *testing.T) {
-	testCases := []struct {
-		name, in string
-		out      []string
-	}{
-		{
-			name: "single quoting test case 1",
-			in:   `./cmd '"'-C`,
-			out:  []string{"./cmd", `"-C`},
-		},
-		{
-			name: "single quoting test case 2",
-			in:   `./cmd '-C`,
-			out:  []string{"./cmd", `-C`},
-		},
-		{
-			name: "single quoting test case 3",
-			in:   `./cmd '\"'-C`,
-			out:  []string{"./cmd", `\"-C`},
-		},
-		{
-			name: "single quoting test case 4",
-			in:   `./cmd '\\'-C`,
-			out:  []string{"./cmd", `\\-C`},
-		},
-		{
-			name: "none quoting test case 1",
-			in:   `./cmd \'-C`,
-			out:  []string{"./cmd", `'-C`},
-		},
-		{
-			name: "none quoting test case 2",
-			in:   `./cmd \\-C`,
-			out:  []string{"./cmd", `\-C`},
-		},
-		{
-			name: "none quoting test case 3",
-			in:   `./cmd \"-C`,
-			out:  []string{"./cmd", `"-C`},
-		},
-		{
-			name: "double quoting test case 1",
-			in:   `./cmd "'"-C`,
-			out:  []string{"./cmd", `'-C`},
-		},
-		{
-			name: "double quoting test case 2",
-			in:   `./cmd "\\"-C`,
-			out:  []string{"./cmd", `\-C`},
-		},
-		{
-			name: "double quoting test case 3",
-			in:   `./cmd "\""-C`,
-			out:  []string{"./cmd", `"-C`},
-		},
-		{
-			name: "ninja rsp file",
-			in:   "'a'\nb\n'@'\n'foo'\\''bar'\n'foo\"bar'",
-			out:  []string{"a", "b", "@", "foo'bar", `foo"bar`},
-		},
-	}
-
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			got := ReadRespFile([]byte(testCase.in))
-			if !reflect.DeepEqual(got, testCase.out) {
-				t.Errorf("expected %q got %q", testCase.out, got)
-			}
-		})
-	}
-}
-
 func TestSrcJar(t *testing.T) {
 	mockFs := pathtools.MockFs(map[string][]byte{
 		"wrong_package.java":       []byte("package foo;"),