cmdline: add support for command-line flags that contain variables

MultiPart: 2/3

Change-Id: I220e735a751ee8f34480870863c71621526173ae
diff --git a/cmd/build/doc.go b/cmd/build/doc.go
index 40cd568..d81c46c 100644
--- a/cmd/build/doc.go
+++ b/cmd/build/doc.go
@@ -69,9 +69,9 @@
 against all packages with that prefix.
 
 The build build flags are:
- -arch=$GOARCH
+ -arch=${GOARCH}
    Target architecture.
- -os=$GOOS
+ -os=${GOOS}
    Target operating system.
 
 Build Help
diff --git a/cmd/build/impl.go b/cmd/build/impl.go
index f2fc050..46a517d 100644
--- a/cmd/build/impl.go
+++ b/cmd/build/impl.go
@@ -1,12 +1,12 @@
 package main
 
 import (
+	"flag"
 	"fmt"
 	"go/build"
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"runtime"
 	"strings"
 	"time"
 
@@ -16,29 +16,20 @@
 )
 
 const (
-	defaultArch = "$GOARCH"
-	defaultOS   = "$GOOS"
+	defaultArch = "${GOARCH}"
+	defaultOS   = "${GOOS}"
 )
 
 var (
-	flagArch string
-	flagOS   string
+	flagArch flag.Getter
+	flagOS   flag.Getter
 )
 
 func init() {
-	cmdBuild.Flags.StringVar(&flagArch, "arch", defaultArch, "Target architecture.")
-	cmdBuild.Flags.StringVar(&flagOS, "os", defaultOS, "Target operating system.")
-}
-
-// substituteVarsInFlags substitutes environment variables in default
-// values of relevant flags.
-func substituteVarsInFlags() {
-	if flagArch == defaultArch {
-		flagArch = runtime.GOARCH
-	}
-	if flagOS == defaultOS {
-		flagOS = runtime.GOOS
-	}
+	flagArch = cmdline.RuntimeFlag(defaultArch)
+	flagOS = cmdline.RuntimeFlag(defaultOS)
+	cmdBuild.Flags.Var(flagArch, "arch", "Target architecture.")
+	cmdBuild.Flags.Var(flagOS, "os", "Target operating system.")
 }
 
 var cmdRoot = &cmdline.Command{
@@ -159,7 +150,8 @@
 		defer cancel()
 
 		client := vbuild.BuilderClient(name)
-		stream, err := client.Build(ctx, vbuild.Architecture(flagArch), vbuild.OperatingSystem(flagOS))
+		arch, os := flagArch.Get().(string), flagOS.Get().(string)
+		stream, err := client.Build(ctx, vbuild.Architecture(arch), vbuild.OperatingSystem(os))
 		if err != nil {
 			errchan <- fmt.Errorf("Build() failed: %v", err)
 			return
diff --git a/cmd/build/main.go b/cmd/build/main.go
index 6d3c698..03caf91 100644
--- a/cmd/build/main.go
+++ b/cmd/build/main.go
@@ -17,7 +17,6 @@
 func main() {
 	var shutdown v23.Shutdown
 	gctx, shutdown = v23.Init()
-	substituteVarsInFlags()
 	exitCode := root().Main()
 	shutdown()
 	os.Exit(exitCode)
diff --git a/cmd/mgmt/device/doc.go b/cmd/mgmt/device/doc.go
index 0ed952d..7030b16 100644
--- a/cmd/mgmt/device/doc.go
+++ b/cmd/mgmt/device/doc.go
@@ -286,7 +286,8 @@
 
 Device Acl
 
-The acl tool manages AccessLists on the device manger, installations and instances.
+The acl tool manages AccessLists on the device manger, installations and
+instances.
 
 Usage:
    device acl <command>
@@ -332,8 +333,8 @@
 
 The device acl set flags are:
  -f=false
-   Instead of making the AccessLists additive, do a complete replacement based on the
-   specified settings.
+   Instead of making the AccessLists additive, do a complete replacement based
+   on the specified settings.
 
 Device Publish
 
@@ -351,9 +352,9 @@
    Name of application service.
  -binserv=binaryd
    Name of binary service.
- -goarch=$GOARCH
+ -goarch=${GOARCH}
    GOARCH for application.
- -goos=$GOOS
+ -goos=${GOOS}
    GOOS for application.
  -readers=dev.v.io
    If non-empty, comma-separated blessing patterns to add to Read and Resolve
diff --git a/cmd/mgmt/device/impl/publish.go b/cmd/mgmt/device/impl/publish.go
index 31f4acf..34c6236 100644
--- a/cmd/mgmt/device/impl/publish.go
+++ b/cmd/mgmt/device/impl/publish.go
@@ -1,6 +1,7 @@
 package impl
 
 import (
+	"flag"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -38,7 +39,7 @@
 	ArgsName: "<binary name> ...",
 }
 
-var binaryService, applicationService, goos, goarch, readBlessings string
+var binaryService, applicationService, readBlessings string
 
 const (
 	defaultArch = "$GOARCH"
@@ -46,26 +47,17 @@
 )
 
 var (
-	flagArch string
-	flagOS   string
+	goarchFlag flag.Getter
+	goosFlag   flag.Getter
 )
 
-// SubstituteVarsInFlags substitutes environment variables in default
-// values of relevant flags.
-func SubstituteVarsInFlags() {
-	if flagArch == defaultArch {
-		flagArch = runtime.GOARCH
-	}
-	if flagOS == defaultOS {
-		flagOS = runtime.GOOS
-	}
-}
-
 func init() {
 	cmdPublish.Flags.StringVar(&binaryService, "binserv", "binaryd", "Name of binary service.")
 	cmdPublish.Flags.StringVar(&applicationService, "appserv", "applicationd", "Name of application service.")
-	cmdPublish.Flags.StringVar(&goos, "goos", defaultOS, "GOOS for application.")
-	cmdPublish.Flags.StringVar(&goarch, "goarch", defaultArch, "GOARCH for application.")
+	goosFlag = cmdline.RuntimeFlag("${GOOS}")
+	goarchFlag = cmdline.RuntimeFlag("${GOARCH}")
+	cmdPublish.Flags.Var(goosFlag, "goos", "GOOS for application.")
+	cmdPublish.Flags.Var(goarchFlag, "goarch", "GOARCH for application.")
 	cmdPublish.Flags.StringVar(&readBlessings, "readers", "dev.v.io", "If non-empty, comma-separated blessing patterns to add to Read and Resolve AccessList.")
 }
 
@@ -95,6 +87,9 @@
 }
 
 func publishOne(cmd *cmdline.Command, binPath, binaryName string) error {
+	goos := goosFlag.Get().(string)
+	goarch := goarchFlag.Get().(string)
+
 	// Step 1, upload the binary to the binary service.
 
 	// TODO(caprita): Instead of the current timestamp, use each binary's
@@ -157,6 +152,8 @@
 		return cmd.UsageErrorf("publish: $VANADIUM_ROOT environment variable should be set")
 	}
 	binPath := filepath.Join(vroot, "release/go/bin")
+	goos := goosFlag.Get().(string)
+	goarch := goarchFlag.Get().(string)
 	if goos != runtime.GOOS || goarch != runtime.GOARCH {
 		binPath = filepath.Join(binPath, fmt.Sprintf("%s_%s", goos, goarch))
 	}
diff --git a/cmd/mgmt/device/main.go b/cmd/mgmt/device/main.go
index 93fd359..c5714e2 100644
--- a/cmd/mgmt/device/main.go
+++ b/cmd/mgmt/device/main.go
@@ -15,7 +15,6 @@
 func main() {
 	gctx, shutdown := v23.Init()
 	impl.SetGlobalContext(gctx)
-	impl.SubstituteVarsInFlags()
 	exitCode := impl.Root().Main()
 	shutdown()
 	os.Exit(exitCode)