Improve flags for compliance tools.

Test: m droid dist reportmissinglicenses

Change-Id: I4090dae3d5d33d1908d67dff31aeee92d2b261da
diff --git a/tools/compliance/cmd/rtrace/rtrace.go b/tools/compliance/cmd/rtrace/rtrace.go
index 91171c4..667cdce 100644
--- a/tools/compliance/cmd/rtrace/rtrace.go
+++ b/tools/compliance/cmd/rtrace/rtrace.go
@@ -15,6 +15,7 @@
 package main
 
 import (
+	"bytes"
 	"flag"
 	"fmt"
 	"io"
@@ -24,21 +25,19 @@
 	"sort"
 	"strings"
 
+	"android/soong/response"
 	"android/soong/tools/compliance"
 )
 
 var (
-	sources         = newMultiString("rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)")
-	stripPrefix     = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
-
 	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
 	failNoSources     = fmt.Errorf("\nNo projects or metadata files to trace back from")
 	failNoLicenses    = fmt.Errorf("No licenses found")
 )
 
 type context struct {
-	sources         []string
-	stripPrefix     []string
+	sources     []string
+	stripPrefix []string
 }
 
 func (ctx context) strip(installPath string) string {
@@ -54,8 +53,44 @@
 	return installPath
 }
 
-func init() {
-	flag.Usage = func() {
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
 		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
 
 Outputs a space-separated Target ActsOn Origin Condition tuple for each
@@ -72,50 +107,75 @@
 
 Options:
 `, filepath.Base(os.Args[0]))
-		flag.PrintDefaults()
+		flags.PrintDefaults()
 	}
-}
 
-// newMultiString creates a flag that allows multiple values in an array.
-func newMultiString(name, usage string) *multiString {
-	var f multiString
-	flag.Var(&f, name, usage)
-	return &f
-}
+	outputFile := flags.String("o", "-", "Where to write the output. (default stdout)")
+	sources := newMultiString(flags, "rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
 
-// multiString implements the flag `Value` interface for multiple strings.
-type multiString []string
-
-func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
-func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
-
-func main() {
-	flag.Parse()
+	flags.Parse(expandedArgs)
 
 	// Must specify at least one root target.
-	if flag.NArg() == 0 {
-		flag.Usage()
+	if flags.NArg() == 0 {
+		flags.Usage()
 		os.Exit(2)
 	}
 
 	if len(*sources) == 0 {
-		flag.Usage()
+		flags.Usage()
 		fmt.Fprintf(os.Stderr, "\nMust specify at least 1 --rtrace source.\n")
 		os.Exit(2)
 	}
 
-	ctx := &context{
-		sources:         *sources,
-		stripPrefix:     *stripPrefix,
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
 	}
-	_, err := traceRestricted(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+
+	ctx := &context{
+		sources:     *sources,
+		stripPrefix: *stripPrefix,
+	}
+	_, err := traceRestricted(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
-			flag.Usage()
+			flags.Usage()
 		}
 		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
 		os.Exit(1)
 	}
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
+			os.Exit(1)
+		}
+	}
 	os.Exit(0)
 }