Make $(depfile) work with sbox

Most notably, the sandbox depfile path should be passed into the tool.

Bug: 68336760
Test: m -j checkbuild

Change-Id: I22f944a3f57d613fda26de0ea777a915cafcd020
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index 5064626..e3e68c9 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -32,7 +32,7 @@
 	}
 }
 
-var usage = "Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> <outputFile> [<outputFile>...]\n" +
+var usage = "Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> [--depfile-out depFile] <outputFile> [<outputFile>...]\n" +
 	"\n" +
 	"Runs <commandToRun> and moves each <outputFile> out of <sandboxPath>\n" +
 	"If any file in <outputFiles> is specified by absolute path, then <outputRoot> must be specified as well,\n" +
@@ -43,13 +43,18 @@
 }
 
 func run() error {
-	var outFiles []string
+	// the contents of the __SBOX_OUT_FILES__ variable
+	var outputsVarEntries []string
+	// all outputs
+	var allOutputs []string
+
 	args := os.Args[1:]
 
 	var rawCommand string
 	var sandboxesRoot string
 	removeTempDir := true
 	var outputRoot string
+	var depfile string
 
 	for i := 0; i < len(args); i++ {
 		arg := args[i]
@@ -64,17 +69,20 @@
 			i++
 		} else if arg == "--keep-out-dir" {
 			removeTempDir = false
+		} else if arg == "--depfile-out" {
+			depfile = args[i+1]
+			i++
 		} else {
-			outFiles = append(outFiles, arg)
+			outputsVarEntries = append(outputsVarEntries, arg)
 		}
 	}
-	if len(rawCommand) == 0 {
+	if rawCommand == "" {
 		return usageError("-c <commandToRun> is required and must be non-empty")
 	}
-	if outFiles == nil {
+	if len(outputsVarEntries) == 0 {
 		return usageError("at least one output file must be given")
 	}
-	if len(sandboxesRoot) == 0 {
+	if sandboxesRoot == "" {
 		// In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
 		// and the sbox executable will most likely be at a fixed location relative to OUT_DIR too, so
 		// the value of sandboxesRoot will most likely be at a fixed location relative to the sbox executable
@@ -82,25 +90,39 @@
 		// and by passing it as a parameter we don't need to duplicate its value
 		return usageError("--sandbox-path <sandboxPath> is required and must be non-empty")
 	}
-
-	// Rewrite output file paths to be relative to output root
-	// This facilitates matching them up against the corresponding paths in the temporary directory in case they're absolute
-	for i, filePath := range outFiles {
-		if path.IsAbs(filePath) {
-			if len(outputRoot) == 0 {
-				return fmt.Errorf("Absolute path %s requires nonempty value for --output-root", filePath)
-			}
-		}
-		relativePath, err := filepath.Rel(outputRoot, filePath)
-		if err != nil {
-			return err
-		}
-		outFiles[i] = relativePath
+	if len(outputRoot) == 0 {
+		return usageError("--output-root <outputRoot> is required and must be non-empty")
 	}
 
 	os.MkdirAll(sandboxesRoot, 0777)
 
 	tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
+
+	// Rewrite output file paths to be relative to output root
+	// This facilitates matching them up against the corresponding paths in the temporary directory in case they're absolute
+	for i, filePath := range outputsVarEntries {
+		relativePath, err := filepath.Rel(outputRoot, filePath)
+		if err != nil {
+			return err
+		}
+		outputsVarEntries[i] = relativePath
+	}
+
+	allOutputs = append([]string(nil), outputsVarEntries...)
+
+	if depfile != "" {
+		sandboxedDepfile, err := filepath.Rel(outputRoot, depfile)
+		if err != nil {
+			return err
+		}
+		allOutputs = append(allOutputs, sandboxedDepfile)
+		if !strings.Contains(rawCommand, "__SBOX_DEPFILE__") {
+			return fmt.Errorf("the --depfile-out argument only makes sense if the command contains the text __SBOX_DEPFILE__")
+		}
+		rawCommand = strings.Replace(rawCommand, "__SBOX_DEPFILE__", filepath.Join(tempDir, sandboxedDepfile), -1)
+
+	}
+
 	if err != nil {
 		return fmt.Errorf("Failed to create temp dir: %s", err)
 	}
@@ -122,7 +144,7 @@
 	if strings.Contains(rawCommand, "__SBOX_OUT_FILES__") {
 		// expands into a space-separated list of output files to be generated into the sandbox directory
 		tempOutPaths := []string{}
-		for _, outputPath := range outFiles {
+		for _, outputPath := range outputsVarEntries {
 			tempOutPath := path.Join(tempDir, outputPath)
 			tempOutPaths = append(tempOutPaths, tempOutPath)
 		}
@@ -130,8 +152,12 @@
 		rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_FILES__", pathsText, -1)
 	}
 
-	for _, filePath := range outFiles {
-		os.MkdirAll(path.Join(tempDir, filepath.Dir(filePath)), 0777)
+	for _, filePath := range allOutputs {
+		dir := path.Join(tempDir, filepath.Dir(filePath))
+		err = os.MkdirAll(dir, 0777)
+		if err != nil {
+			return err
+		}
 	}
 
 	commandDescription := rawCommand
@@ -150,7 +176,7 @@
 
 	// validate that all files are created properly
 	var outputErrors []error
-	for _, filePath := range outFiles {
+	for _, filePath := range allOutputs {
 		tempPath := filepath.Join(tempDir, filePath)
 		fileInfo, err := os.Stat(tempPath)
 		if err != nil {
@@ -168,7 +194,7 @@
 		return fmt.Errorf("mismatch between declared and actual outputs in sbox command (%s):\n%v", commandDescription, outputErrors)
 	}
 	// the created files match the declared files; now move them
-	for _, filePath := range outFiles {
+	for _, filePath := range allOutputs {
 		tempPath := filepath.Join(tempDir, filePath)
 		destPath := filePath
 		if len(outputRoot) != 0 {
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 03e10ba..6af18ee 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"path"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -139,6 +138,17 @@
 	}
 }
 
+// Given an output file, returns an expression for the corresponding file path within the sandbox
+func sandboxPathForOutput(ctx android.ModuleContext, outputFile string) (relative string, err error) {
+	var relativePath string
+	basedir := ctx.AConfig().BuildDir()
+	relativePath, err = filepath.Rel(basedir, outputFile)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join("__SBOX_OUT_DIR__", relativePath), nil
+}
+
 func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
 		ctx.ModuleErrorf("at least one `tools` or `tool_files` is required")
@@ -209,6 +219,8 @@
 		}
 	}
 
+	referencedDepfile := false
+
 	rawCommand, err := android.Expand(g.properties.Cmd, func(name string) (string, error) {
 		switch name {
 		case "location":
@@ -222,20 +234,17 @@
 		case "out":
 			return "__SBOX_OUT_FILES__", nil
 		case "depfile":
+			referencedDepfile = true
 			if !g.properties.Depfile {
 				return "", fmt.Errorf("$(depfile) used without depfile property")
 			}
-			return "${depfile}", nil
+			return "__SBOX_DEPFILE__", nil
 		case "genDir":
-			genPath := android.PathForModuleGen(ctx, "").String()
-			var relativePath string
-			var err error
-			outputPath := android.PathForOutput(ctx).String()
-			relativePath, err = filepath.Rel(outputPath, genPath)
+			path, err := sandboxPathForOutput(ctx, android.PathForModuleGen(ctx, "").String())
 			if err != nil {
-				panic(err)
+				return "", err
 			}
-			return path.Join("__SBOX_OUT_DIR__", relativePath), nil
+			return path, nil
 		default:
 			if strings.HasPrefix(name, "location ") {
 				label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -249,6 +258,10 @@
 		}
 	})
 
+	if g.properties.Depfile && !referencedDepfile {
+		ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
+	}
+
 	if err != nil {
 		ctx.PropertyErrorf("cmd", "%s", err.Error())
 		return
@@ -260,7 +273,11 @@
 
 	// recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written,
 	// to be replaced later by ninja_strings.go
-	sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %q $allouts", sandboxPath, buildDir, rawCommand)
+	depfilePlaceholder := ""
+	if g.properties.Depfile {
+		depfilePlaceholder = "$depfileArgs"
+	}
+	sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %q %s $allouts", sandboxPath, buildDir, rawCommand, depfilePlaceholder)
 
 	ruleParams := blueprint.RuleParams{
 		Command:     sandboxCommand,
@@ -269,7 +286,7 @@
 	args := []string{"allouts"}
 	if g.properties.Depfile {
 		ruleParams.Deps = blueprint.DepsGCC
-		args = append(args, "depfile")
+		args = append(args, "depfileArgs")
 	}
 	g.rule = ctx.Rule(pctx, "generator", ruleParams, args...)
 
@@ -289,6 +306,11 @@
 		desc += " " + task.out[0].Base()
 	}
 
+	var depFile android.ModuleGenPath
+	if g.properties.Depfile {
+		depFile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
+	}
+
 	params := android.BuildParams{
 		Rule:            g.rule,
 		Description:     "generate",
@@ -301,9 +323,10 @@
 		},
 	}
 	if g.properties.Depfile {
-		depfile := android.GenPathWithExt(ctx, "", task.out[0], task.out[0].Ext()+".d")
-		params.Depfile = depfile
+		params.Depfile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
+		params.Args["depfileArgs"] = "--depfile-out " + depFile.String()
 	}
+
 	ctx.Build(pctx, params)
 
 	for _, outputFile := range task.out {