Report which files are in the dependency graph for dist builds.

Bug: 383124666
Test: m dist and verify
Change-Id: Icc95327e8288bf1fbed86cd3d894f72229967155
diff --git a/ui/build/source_inputs.go b/ui/build/source_inputs.go
new file mode 100644
index 0000000..d1cc1a2
--- /dev/null
+++ b/ui/build/source_inputs.go
@@ -0,0 +1,100 @@
+package build
+
+import (
+	"compress/gzip"
+	"fmt"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"android/soong/shared"
+	"android/soong/ui/metrics"
+)
+
+func sortedStringSetKeys(m map[string]bool) []string {
+	result := make([]string, 0, len(m))
+	for key := range m {
+		result = append(result, key)
+	}
+	sort.Strings(result)
+	return result
+}
+
+func addSlash(str string) string {
+	if len(str) == 0 {
+		return ""
+	}
+	if str[len(str)-1] == '/' {
+		return str
+	}
+	return str + "/"
+}
+
+func hasPrefixStrings(str string, prefixes []string) bool {
+	for _, prefix := range prefixes {
+		if strings.HasPrefix(str, prefix) {
+			return true
+		}
+	}
+	return false
+}
+
+// Output DIST_DIR/source_inputs.txt.gz, which will contain a listing of the files
+// in the source tree (not including in the out directory) that were declared as ninja
+// inputs to the build that was just done.
+func runSourceInputs(ctx Context, config Config) {
+	ctx.BeginTrace(metrics.RunSoong, "runSourceInputs")
+	defer ctx.EndTrace()
+
+	success := false
+	outputFilename := shared.JoinPath(config.RealDistDir(), "source_inputs.txt.gz")
+
+	outputFile, err := os.Create(outputFilename)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "source_files_used: unable to open file for write: %s\n", outputFilename)
+		return
+	}
+	defer func() {
+		outputFile.Close()
+		if !success {
+			os.Remove(outputFilename)
+		}
+	}()
+
+	output := gzip.NewWriter(outputFile)
+	defer output.Close()
+
+	// Skip out dir, both absolute and relative. There are some files
+	// generated during analysis that ninja thinks are inputs not intermediates.
+	absOut, _ := filepath.Abs(config.OutDir())
+	excludes := []string{
+		addSlash(config.OutDir()),
+		addSlash(absOut),
+	}
+
+	goals := config.NinjaArgs()
+
+	result := make(map[string]bool)
+	for _, goal := range goals {
+		inputs, err := runNinjaInputs(ctx, config, goal)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "source_files_used: %v\n", err)
+			return
+		}
+
+		for _, filename := range inputs {
+			if !hasPrefixStrings(filename, excludes) {
+				result[filename] = true
+			}
+		}
+	}
+
+	for _, filename := range sortedStringSetKeys(result) {
+		output.Write([]byte(filename))
+		output.Write([]byte("\n"))
+	}
+
+	output.Flush()
+	success = true
+}