sbox: best-effort copy output files on failure
Error messages printed by failing commands may reference output files
that were created by the command, for example printing a command line
to copy and paste to update a baseline file. Copy output files in the
sandbox to their final locations, ignoring missing files, so that the
messages are valid.
Bug: 185516277
Test: m out/soong/.intermediates/frameworks/base/system-api-stubs-docs-non-updatable/android_common/metalava/api_lint.timestamp with lint error
Change-Id: I604a11c9b54e409ca5bc5c016cd04b3133f74a60
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index 7bd0868..9736ff6 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -230,7 +230,7 @@
}
// Copy in any files specified by the manifest.
- err = copyFiles(command.CopyBefore, "", tempDir)
+ err = copyFiles(command.CopyBefore, "", tempDir, false)
if err != nil {
return "", err
}
@@ -276,6 +276,14 @@
}
err = cmd.Run()
+ if err != nil {
+ // The command failed, do a best effort copy of output files out of the sandbox. This is
+ // especially useful for linters with baselines that print an error message on failure
+ // with a command to copy the output lint errors to the new baseline. Use a copy instead of
+ // a move to leave the sandbox intact for manual inspection
+ copyFiles(command.CopyAfter, tempDir, "", true)
+ }
+
if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
return "", fmt.Errorf("sbox command failed with err:\n%s\n%w\n", commandDescription, err)
} else if err != nil {
@@ -351,12 +359,13 @@
return missingOutputErrors
}
-// copyFiles copies files in or out of the sandbox.
-func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+// copyFiles copies files in or out of the sandbox. If allowFromNotExists is true then errors
+// caused by a from path not existing are ignored.
+func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string, allowFromNotExists bool) error {
for _, copyPair := range copies {
fromPath := joinPath(fromDir, copyPair.GetFrom())
toPath := joinPath(toDir, copyPair.GetTo())
- err := copyOneFile(fromPath, toPath, copyPair.GetExecutable())
+ err := copyOneFile(fromPath, toPath, copyPair.GetExecutable(), allowFromNotExists)
if err != nil {
return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
}
@@ -364,8 +373,9 @@
return nil
}
-// copyOneFile copies a file.
-func copyOneFile(from string, to string, executable bool) error {
+// copyOneFile copies a file and its permissions. If forceExecutable is true it adds u+x to the
+// permissions. If allowFromNotExists is true it returns nil if the from path doesn't exist.
+func copyOneFile(from string, to string, forceExecutable, allowFromNotExists bool) error {
err := os.MkdirAll(filepath.Dir(to), 0777)
if err != nil {
return err
@@ -373,11 +383,14 @@
stat, err := os.Stat(from)
if err != nil {
+ if os.IsNotExist(err) && allowFromNotExists {
+ return nil
+ }
return err
}
perm := stat.Mode()
- if executable {
+ if forceExecutable {
perm = perm | 0100 // u+x
}
@@ -454,7 +467,7 @@
to := applyPathMappings(rspFile.PathMappings, from)
// Copy the file into the sandbox.
- err := copyOneFile(from, joinPath(toDir, to), false)
+ err := copyOneFile(from, joinPath(toDir, to), false, false)
if err != nil {
return err
}