Allow Bazel to write to an external DIST_DIR (outside of OUT_DIR).

Also get Bazel to write real files there (not symlinks) so that the DIST_DIR can be independent.

Test: Manually using e.g. DIST_DIR=/tmp/foo USE_BAZEL=1 m dist
Change-Id: I39d5219500864c9ecc85f356a028e9b5bf2607f4
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 4ffe944..68480d4 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -18,6 +18,7 @@
 	"context"
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -173,6 +174,10 @@
 
 	build.SetupOutDir(buildCtx, config)
 
+	if config.UseBazel() {
+		defer populateExternalDistDir(buildCtx, config)
+	}
+
 	// Set up files to be outputted in the log directory.
 	logsDir := config.LogsDir()
 
@@ -510,3 +515,72 @@
 	// command not found
 	return nil, nil, fmt.Errorf("Command not found: %q", args)
 }
+
+// For Bazel support, this moves files and directories from e.g. out/dist/$f to DIST_DIR/$f if necessary.
+func populateExternalDistDir(ctx build.Context, config build.Config) {
+	// Make sure that internalDistDirPath and externalDistDirPath are both absolute paths, so we can compare them
+	var err error
+	var internalDistDirPath string
+	var externalDistDirPath string
+	if internalDistDirPath, err = filepath.Abs(config.DistDir()); err != nil {
+		ctx.Fatalf("Unable to find absolute path of %s: %s", internalDistDirPath, err)
+	}
+	if externalDistDirPath, err = filepath.Abs(config.RealDistDir()); err != nil {
+		ctx.Fatalf("Unable to find absolute path of %s: %s", externalDistDirPath, err)
+	}
+	if externalDistDirPath == internalDistDirPath {
+		return
+	}
+
+	// Make sure the external DIST_DIR actually exists before trying to write to it
+	if err = os.MkdirAll(externalDistDirPath, 0755); err != nil {
+		ctx.Fatalf("Unable to make directory %s: %s", externalDistDirPath, err)
+	}
+
+	ctx.Println("Populating external DIST_DIR...")
+
+	populateExternalDistDirHelper(ctx, config, internalDistDirPath, externalDistDirPath)
+}
+
+func populateExternalDistDirHelper(ctx build.Context, config build.Config, internalDistDirPath string, externalDistDirPath string) {
+	files, err := ioutil.ReadDir(internalDistDirPath)
+	if err != nil {
+		ctx.Fatalf("Can't read internal distdir %s: %s", internalDistDirPath, err)
+	}
+	for _, f := range files {
+		internalFilePath := filepath.Join(internalDistDirPath, f.Name())
+		externalFilePath := filepath.Join(externalDistDirPath, f.Name())
+
+		if f.IsDir() {
+			// Moving a directory - check if there is an existing directory to merge with
+			externalLstat, err := os.Lstat(externalFilePath)
+			if err != nil {
+				if !os.IsNotExist(err) {
+					ctx.Fatalf("Can't lstat external %s: %s", externalDistDirPath, err)
+				}
+				// Otherwise, if the error was os.IsNotExist, that's fine and we fall through to the rename at the bottom
+			} else {
+				if externalLstat.IsDir() {
+					// Existing dir - try to merge the directories?
+					populateExternalDistDirHelper(ctx, config, internalFilePath, externalFilePath)
+					continue
+				} else {
+					// Existing file being replaced with a directory. Delete the existing file...
+					if err := os.RemoveAll(externalFilePath); err != nil {
+						ctx.Fatalf("Unable to remove existing %s: %s", externalFilePath, err)
+					}
+				}
+			}
+		} else {
+			// Moving a file (not a dir) - delete any existing file or directory
+			if err := os.RemoveAll(externalFilePath); err != nil {
+				ctx.Fatalf("Unable to remove existing %s: %s", externalFilePath, err)
+			}
+		}
+
+		// The actual move - do a rename instead of a copy in order to save disk space.
+		if err := os.Rename(internalFilePath, externalFilePath); err != nil {
+			ctx.Fatalf("Unable to rename %s -> %s due to error %s", internalFilePath, externalFilePath, err)
+		}
+	}
+}