Add --dumpvar[s]-mode to replace config.mk uses

  build/soong/soong_ui.bash --dumpvars-mode \
    --vars="..." \
    --abs-vars="..." \
    --var-prefix="..." \
    --abs-var-prefix="..."

is similar to the previous:

  CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
  make -f build/core/config.mk dump-many-vars \
    DUMP_MANY_VARS="..." \
    DUMP_MANY_ABS_VARS="..." \
    DUMP_VAR_PREFIX="..." \
    DUMP_ABS_VAR_PREFIX="..."

and

  build/soong/soong_ui.bash --dumpvar-mode [--abs] VAR

is similar to the previous:

  CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
  make -f build/core/config.mk dumpvar-[abs-]-VAR

But uses soong_ui and ckati, so that we use a consistent make parser and
sandboxing configurations.

One major output difference between the pure make implementation and
this one is that report_config in Go is implemented using embedded
newlines in single quotes, while the make implementation uses `` with
embedded echo commands. This seems to work fine for both bash and zsh,
and report_config isn't meant to be machine-parsed anyways.

Test: build/soong/soong_ui.bash --dumpvar-mode report_config
Test: build/soong/soong_ui.bash --dumpvar-mode TARGET_DEVICE
Test: build/soong/soong_ui.bash --dumpvar-mode --abs PRODUCT_OUT
Test: build/soong/soong_ui.bash --dumpvar-mode --abs ALL_PRODUCTS
Test: build/soong/soong_ui.bash --dumpvars-mode --vars="report_config TARGET_DEVICE" --abs-vars="ALL_PRODUCTS"
Test: build/soong/soong_ui.bash --dumpvars-mode --vars=TARGET_DEVICE --abs-vars=PRODUCT_OUT --var-prefix=v_ --abs-var-prefix=a_
Change-Id: I0fbd0732bbf6fcfcd24084cf3c830a91a4b6bfc2
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 8a26171..0619b5c 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -16,6 +16,8 @@
 
 import (
 	"context"
+	"flag"
+	"fmt"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -45,7 +47,10 @@
 	log := logger.New(os.Stderr)
 	defer log.Cleanup()
 
-	if len(os.Args) < 2 || !inList("--make-mode", os.Args) {
+	if len(os.Args) < 2 || !(inList("--make-mode", os.Args) ||
+		os.Args[1] == "--dumpvars-mode" ||
+		os.Args[1] == "--dumpvar-mode") {
+
 		log.Fatalln("The `soong` native UI is not yet available.")
 	}
 
@@ -66,7 +71,12 @@
 		Tracer:         trace,
 		StdioInterface: build.StdioImpl{},
 	}}
-	config := build.NewConfig(buildCtx, os.Args[1:]...)
+	var config build.Config
+	if os.Args[1] == "--dumpvars-mode" || os.Args[1] == "--dumpvar-mode" {
+		config = build.NewConfig(buildCtx)
+	} else {
+		config = build.NewConfig(buildCtx, os.Args[1:]...)
+	}
 
 	log.SetVerbose(config.IsVerbose())
 	build.SetupOutDir(buildCtx, config)
@@ -99,5 +109,129 @@
 	defer f.Shutdown()
 	build.FindSources(buildCtx, config, f)
 
-	build.Build(buildCtx, config, build.BuildAll)
+	if os.Args[1] == "--dumpvar-mode" {
+		dumpVar(buildCtx, config, os.Args[2:])
+	} else if os.Args[1] == "--dumpvars-mode" {
+		dumpVars(buildCtx, config, os.Args[2:])
+	} else {
+		build.Build(buildCtx, config, build.BuildAll)
+	}
+}
+
+func dumpVar(ctx build.Context, config build.Config, args []string) {
+	flags := flag.NewFlagSet("dumpvar", flag.ExitOnError)
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0])
+		fmt.Fprintln(os.Stderr, "In dumpvar mode, print the value of the legacy make variable VAR to stdout")
+		fmt.Fprintln(os.Stderr, "")
+
+		fmt.Fprintln(os.Stderr, "'report_config' is a special case that prints the human-readable config banner")
+		fmt.Fprintln(os.Stderr, "from the beginning of the build.")
+		fmt.Fprintln(os.Stderr, "")
+		flags.PrintDefaults()
+	}
+	abs := flags.Bool("abs", false, "Print the absolute path of the value")
+	flags.Parse(args)
+
+	if flags.NArg() != 1 {
+		flags.Usage()
+		os.Exit(1)
+	}
+
+	varName := flags.Arg(0)
+	if varName == "report_config" {
+		varData, err := build.DumpMakeVars(ctx, config, nil, build.BannerVars)
+		if err != nil {
+			ctx.Fatal(err)
+		}
+
+		fmt.Println(build.Banner(varData))
+	} else {
+		varData, err := build.DumpMakeVars(ctx, config, nil, []string{varName})
+		if err != nil {
+			ctx.Fatal(err)
+		}
+
+		if *abs {
+			var res []string
+			for _, path := range strings.Fields(varData[varName]) {
+				if abs, err := filepath.Abs(path); err == nil {
+					res = append(res, abs)
+				} else {
+					ctx.Fatalln("Failed to get absolute path of", path, err)
+				}
+			}
+			fmt.Println(strings.Join(res, " "))
+		} else {
+			fmt.Println(varData[varName])
+		}
+	}
+}
+
+func dumpVars(ctx build.Context, config build.Config, args []string) {
+	flags := flag.NewFlagSet("dumpvars", flag.ExitOnError)
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0])
+		fmt.Fprintln(os.Stderr, "In dumpvars mode, dump the values of one or more legacy make variables, in")
+		fmt.Fprintln(os.Stderr, "shell syntax. The resulting output may be sourced directly into a shell to")
+		fmt.Fprintln(os.Stderr, "set corresponding shell variables.")
+		fmt.Fprintln(os.Stderr, "")
+
+		fmt.Fprintln(os.Stderr, "'report_config' is a special case that dumps a variable containing the")
+		fmt.Fprintln(os.Stderr, "human-readable config banner from the beginning of the build.")
+		fmt.Fprintln(os.Stderr, "")
+		flags.PrintDefaults()
+	}
+
+	varsStr := flags.String("vars", "", "Space-separated list of variables to dump")
+	absVarsStr := flags.String("abs-vars", "", "Space-separated list of variables to dump (using absolute paths)")
+
+	varPrefix := flags.String("var-prefix", "", "String to prepend to all variable names when dumping")
+	absVarPrefix := flags.String("abs-var-prefix", "", "String to prepent to all absolute path variable names when dumping")
+
+	flags.Parse(args)
+
+	if flags.NArg() != 0 {
+		flags.Usage()
+		os.Exit(1)
+	}
+
+	vars := strings.Fields(*varsStr)
+	absVars := strings.Fields(*absVarsStr)
+
+	allVars := append([]string{}, vars...)
+	allVars = append(allVars, absVars...)
+
+	if i := indexList("report_config", allVars); i != -1 {
+		allVars = append(allVars[:i], allVars[i+1:]...)
+		allVars = append(allVars, build.BannerVars...)
+	}
+
+	if len(allVars) == 0 {
+		return
+	}
+
+	varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
+	if err != nil {
+		ctx.Fatal(err)
+	}
+
+	for _, name := range vars {
+		if name == "report_config" {
+			fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(varData))
+		} else {
+			fmt.Printf("%s%s='%s'\n", *varPrefix, name, varData[name])
+		}
+	}
+	for _, name := range absVars {
+		var res []string
+		for _, path := range strings.Fields(varData[name]) {
+			abs, err := filepath.Abs(path)
+			if err != nil {
+				ctx.Fatalln("Failed to get absolute path of", path, err)
+			}
+			res = append(res, abs)
+		}
+		fmt.Printf("%s%s='%s'\n", *absVarPrefix, name, strings.Join(res, " "))
+	}
 }
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index e6c3f56..fb20d63 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -15,6 +15,7 @@
 package build
 
 import (
+	"bytes"
 	"fmt"
 	"strings"
 )
@@ -78,6 +79,49 @@
 	return ret, nil
 }
 
+// Variables to print out in the top banner
+var BannerVars = []string{
+	"PLATFORM_VERSION_CODENAME",
+	"PLATFORM_VERSION",
+	"TARGET_PRODUCT",
+	"TARGET_BUILD_VARIANT",
+	"TARGET_BUILD_TYPE",
+	"TARGET_BUILD_APPS",
+	"TARGET_ARCH",
+	"TARGET_ARCH_VARIANT",
+	"TARGET_CPU_VARIANT",
+	"TARGET_2ND_ARCH",
+	"TARGET_2ND_ARCH_VARIANT",
+	"TARGET_2ND_CPU_VARIANT",
+	"HOST_ARCH",
+	"HOST_2ND_ARCH",
+	"HOST_OS",
+	"HOST_OS_EXTRA",
+	"HOST_CROSS_OS",
+	"HOST_CROSS_ARCH",
+	"HOST_CROSS_2ND_ARCH",
+	"HOST_BUILD_TYPE",
+	"BUILD_ID",
+	"OUT_DIR",
+	"AUX_OS_VARIANT_LIST",
+	"TARGET_BUILD_PDK",
+	"PDK_FUSION_PLATFORM_ZIP",
+}
+
+func Banner(make_vars map[string]string) string {
+	b := &bytes.Buffer{}
+
+	fmt.Fprintln(b, "============================================")
+	for _, name := range BannerVars {
+		if make_vars[name] != "" {
+			fmt.Fprintf(b, "%s=%s\n", name, make_vars[name])
+		}
+	}
+	fmt.Fprint(b, "============================================")
+
+	return b.String()
+}
+
 func runMakeProductConfig(ctx Context, config Config) {
 	// Variables to export into the environment of Kati/Ninja
 	exportEnvVars := []string{
@@ -99,35 +143,6 @@
 		"CCACHE_CPP2",
 	}
 
-	// Variables to print out in the top banner
-	bannerVars := []string{
-		"PLATFORM_VERSION_CODENAME",
-		"PLATFORM_VERSION",
-		"TARGET_PRODUCT",
-		"TARGET_BUILD_VARIANT",
-		"TARGET_BUILD_TYPE",
-		"TARGET_BUILD_APPS",
-		"TARGET_ARCH",
-		"TARGET_ARCH_VARIANT",
-		"TARGET_CPU_VARIANT",
-		"TARGET_2ND_ARCH",
-		"TARGET_2ND_ARCH_VARIANT",
-		"TARGET_2ND_CPU_VARIANT",
-		"HOST_ARCH",
-		"HOST_2ND_ARCH",
-		"HOST_OS",
-		"HOST_OS_EXTRA",
-		"HOST_CROSS_OS",
-		"HOST_CROSS_ARCH",
-		"HOST_CROSS_2ND_ARCH",
-		"HOST_BUILD_TYPE",
-		"BUILD_ID",
-		"OUT_DIR",
-		"AUX_OS_VARIANT_LIST",
-		"TARGET_BUILD_PDK",
-		"PDK_FUSION_PLATFORM_ZIP",
-	}
-
 	allVars := append(append([]string{
 		// Used to execute Kati and Ninja
 		"NINJA_GOALS",
@@ -135,7 +150,7 @@
 
 		// To find target/product/<DEVICE>
 		"TARGET_DEVICE",
-	}, exportEnvVars...), bannerVars...)
+	}, exportEnvVars...), BannerVars...)
 
 	make_vars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true)
 	if err != nil {
@@ -143,13 +158,7 @@
 	}
 
 	// Print the banner like make does
-	fmt.Fprintln(ctx.Stdout(), "============================================")
-	for _, name := range bannerVars {
-		if make_vars[name] != "" {
-			fmt.Fprintf(ctx.Stdout(), "%s=%s\n", name, make_vars[name])
-		}
-	}
-	fmt.Fprintln(ctx.Stdout(), "============================================")
+	fmt.Fprintln(ctx.Stdout(), Banner(make_vars))
 
 	// Populate the environment
 	env := config.Environment()