Merge "Require that NDK symbol files end with .map.txt."
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index 8fed040..5064626 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -32,10 +32,14 @@
 	}
 }
 
-var usage = "Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> <outputFiles>"
+var usage = "Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> <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" +
+	"to enable sbox to compute the relative path within the sandbox of the specified output files"
 
 func usageError(violation string) error {
-	return fmt.Errorf("Usage error: %s.\n %s", violation, usage)
+	return fmt.Errorf("Usage error: %s.\n\n%s", violation, usage)
 }
 
 func run() error {
@@ -45,6 +49,7 @@
 	var rawCommand string
 	var sandboxesRoot string
 	removeTempDir := true
+	var outputRoot string
 
 	for i := 0; i < len(args); i++ {
 		arg := args[i]
@@ -54,6 +59,11 @@
 		} else if arg == "-c" {
 			rawCommand = args[i+1]
 			i++
+		} else if arg == "--output-root" {
+			outputRoot = args[i+1]
+			i++
+		} else if arg == "--keep-out-dir" {
+			removeTempDir = false
 		} else {
 			outFiles = append(outFiles, arg)
 		}
@@ -73,6 +83,21 @@
 		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
+	}
+
 	os.MkdirAll(sandboxesRoot, 0777)
 
 	tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
@@ -109,13 +134,16 @@
 		os.MkdirAll(path.Join(tempDir, filepath.Dir(filePath)), 0777)
 	}
 
+	commandDescription := rawCommand
+
 	cmd := exec.Command("bash", "-c", rawCommand)
 	cmd.Stdin = os.Stdin
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
 	err = cmd.Run()
+
 	if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
-		return fmt.Errorf("sbox command %#v failed with err %#v\n", cmd, err)
+		return fmt.Errorf("sbox command (%s) failed with err %#v\n", commandDescription, err.Error())
 	} else if err != nil {
 		return err
 	}
@@ -137,12 +165,16 @@
 		// Keep the temporary output directory around in case a user wants to inspect it for debugging purposes.
 		// Soong will delete it later anyway.
 		removeTempDir = false
-		return fmt.Errorf("mismatch between declared and actual outputs in sbox command (%s):\n%v", rawCommand, outputErrors)
+		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 {
 		tempPath := filepath.Join(tempDir, filePath)
-		err := os.Rename(tempPath, filePath)
+		destPath := filePath
+		if len(outputRoot) != 0 {
+			destPath = filepath.Join(outputRoot, filePath)
+		}
+		err := os.Rename(tempPath, destPath)
 		if err != nil {
 			return err
 		}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index b98ccfd..dc4e968 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -194,15 +194,11 @@
 		case "genDir":
 			genPath := android.PathForModuleGen(ctx, "").String()
 			var relativePath string
-			if path.IsAbs(genPath) {
-				var err error
-				outputPath := android.PathForOutput(ctx).String()
-				relativePath, err = filepath.Rel(genPath, outputPath)
-				if err != nil {
-					panic(err)
-				}
-			} else {
-				relativePath = genPath
+			var err error
+			outputPath := android.PathForOutput(ctx).String()
+			relativePath, err = filepath.Rel(outputPath, genPath)
+			if err != nil {
+				panic(err)
 			}
 			return path.Join("__SBOX_OUT_DIR__", relativePath), nil
 		default:
@@ -224,11 +220,12 @@
 	}
 
 	// tell the sbox command which directory to use as its sandbox root
-	sandboxPath := shared.TempDirForOutDir(android.PathForOutput(ctx).String())
+	buildDir := android.PathForOutput(ctx).String()
+	sandboxPath := shared.TempDirForOutDir(buildDir)
 
 	// 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 -c %q $out", sandboxPath, rawCommand)
+	sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %q $out", sandboxPath, buildDir, rawCommand)
 
 	ruleParams := blueprint.RuleParams{
 		Command:     sandboxCommand,
diff --git a/ui/build/exec.go b/ui/build/exec.go
index 79310dc..90fb19d 100644
--- a/ui/build/exec.go
+++ b/ui/build/exec.go
@@ -30,9 +30,6 @@
 	ctx    Context
 	config Config
 	name   string
-
-	// doneChannel closes to signal the command's termination
-	doneChannel chan bool
 }
 
 func Command(ctx Context, config Config, name string, executable string, args ...string) *Cmd {
@@ -41,10 +38,9 @@
 		Environment: config.Environment().Copy(),
 		Sandbox:     noSandbox,
 
-		ctx:         ctx,
-		config:      config,
-		name:        name,
-		doneChannel: make(chan bool),
+		ctx:    ctx,
+		config: config,
+		name:   name,
 	}
 
 	return ret
@@ -61,10 +57,6 @@
 	c.ctx.Verboseln(c.Path, c.Args)
 }
 
-func (c *Cmd) teardown() {
-	close(c.doneChannel)
-}
-
 func (c *Cmd) Start() error {
 	c.prepare()
 	return c.Cmd.Start()
@@ -72,21 +64,18 @@
 
 func (c *Cmd) Run() error {
 	c.prepare()
-	defer c.teardown()
 	err := c.Cmd.Run()
 	return err
 }
 
 func (c *Cmd) Output() ([]byte, error) {
 	c.prepare()
-	defer c.teardown()
 	bytes, err := c.Cmd.Output()
 	return bytes, err
 }
 
 func (c *Cmd) CombinedOutput() ([]byte, error) {
 	c.prepare()
-	defer c.teardown()
 	bytes, err := c.Cmd.CombinedOutput()
 	return bytes, err
 }
@@ -133,13 +122,3 @@
 	c.reportError(err)
 	return ret
 }
-
-// Done() tells whether this command has finished executing
-func (c *Cmd) Done() bool {
-	select {
-	case <-c.doneChannel:
-		return true
-	default:
-		return false
-	}
-}
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 22771e7..78d1170 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -81,11 +81,19 @@
 		}
 	}
 	// Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics
+	done := make(chan struct{})
+	defer close(done)
+	ticker := time.NewTicker(ninjaHeartbeatDuration)
+	defer ticker.Stop()
 	checker := &statusChecker{}
 	go func() {
-		for !cmd.Done() {
-			checker.check(ctx, config, logPath)
-			time.Sleep(ninjaHeartbeatDuration)
+		for {
+			select {
+			case <-ticker.C:
+				checker.check(ctx, config, logPath)
+			case <-done:
+				return
+			}
 		}
 	}()
 
@@ -127,5 +135,5 @@
 	output := cmd.CombinedOutputOrFatal()
 	ctx.Verbose(string(output))
 
-	ctx.Printf("done\n")
+	ctx.Verbosef("done\n")
 }