feat(extern): add madb extern command.

This CL adds the 'madb extern' command, which can run any external
commands for all Android devices in parallel. The ANDROID_SERIAL
variable is set for each subshell, so that external commands that
internally call adb command may work correctly, even without the
'-s <serial>' argument provided.

Closes #31.

Change-Id: I158dbb2a36d673fef6444833b6c79128ffc9a421
diff --git a/doc.go b/doc.go
index 5b02494..c8e6186 100644
--- a/doc.go
+++ b/doc.go
@@ -18,6 +18,7 @@
    clear-data  Clear your app data from all devices
    exec        Run the provided adb command on all devices and emulators
                concurrently
+   extern      Run the provided external command for all devices
    install     Install your app on all devices
    name        Manage device nicknames
    shell       Run the provided adb shell command on all devices and emulators
@@ -167,6 +168,58 @@
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
+Madb extern - Run the provided external command for all devices
+
+Runs the provided external command for all devices and emulators concurrently.
+
+For each available device, this command will spawn a sub-shell with the
+ANDROID_SERIAL environmental variable set to the target device serial, and then
+will run the provided external command.
+
+There are a few pre-defined keywords that can be expanded within an argument.
+
+    "{{index}}"  : the index of the current device, starting from 1.
+    "{{name}}"   : the nickname of the current device, or the serial number if a nickname is not set.
+    "{{serial}}" : the serial number of the current device.
+
+For example, the following line:
+
+    madb extern echo I am {{name}}, and my serial number is {{serial}}.
+
+prints out the name and serial pairs for each device.
+
+Note that you should type in "{{name}}" as-is, with the opening/closing curly
+braces, similar to when you're using a template library such as mustache.
+
+This command is intended to be used with external commands that are designed to
+work with only a single device at a time (e.g. gomobile, flutter).
+
+Usage:
+   madb extern [flags] <external_command>
+
+<external_command> is an external shell command to run for all devices and
+emulators.
+
+The madb extern flags are:
+ -d=false
+   Restrict the command to only run on real devices.
+ -e=false
+   Restrict the command to only run on emulators.
+ -n=
+   Comma-separated device serials, qualifiers, device indices (e.g., '@1',
+   '@2'), or nicknames (set by 'madb name'). A device index is specified by an
+   '@' sign followed by the index of the device in the output of 'adb devices'
+   command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
+ -seq=false
+   Run the command sequentially, instead of running it in parallel.
+
 Madb install - Install your app on all devices
 
 Installs your app on all devices.
diff --git a/extern.go b/extern.go
new file mode 100644
index 0000000..e418b0e
--- /dev/null
+++ b/extern.go
@@ -0,0 +1,68 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/gosh"
+)
+
+var cmdMadbExtern = &cmdline.Command{
+	Runner: subCommandRunner{subCmd: runMadbExternForDevice},
+	Name:   "extern",
+	Short:  "Run the provided external command for all devices",
+	Long: `
+Runs the provided external command for all devices and emulators concurrently.
+
+For each available device, this command will spawn a sub-shell with the
+ANDROID_SERIAL environmental variable set to the target device serial, and then
+will run the provided external command.
+
+There are a few pre-defined keywords that can be expanded within an argument.
+
+    "{{index}}"  : the index of the current device, starting from 1.
+    "{{name}}"   : the nickname of the current device, or the serial number if a nickname is not set.
+    "{{serial}}" : the serial number of the current device.
+
+For example, the following line:
+
+    madb extern echo I am {{name}}, and my serial number is {{serial}}.
+
+prints out the name and serial pairs for each device.
+
+Note that you should type in "{{name}}" as-is, with the opening/closing curly
+braces, similar to when you're using a template library such as mustache.
+
+This command is intended to be used with external commands that are designed to
+work with only a single device at a time (e.g. gomobile, flutter).
+`,
+	ArgsName: "<external_command>",
+	ArgsLong: `
+<external_command> is an external shell command to run for all devices and emulators.
+`,
+}
+
+func runMadbExternForDevice(env *cmdline.Env, args []string, d device, properties variantProperties) error {
+	return runExternalCommandForDevice(env, args, d, properties)
+}
+
+func runExternalCommandForDevice(env *cmdline.Env, args []string, d device, properties variantProperties) error {
+	sh := gosh.NewShell(nil)
+	defer sh.Cleanup()
+
+	sh.ContinueOnError = true
+
+	// Expand the keywords before running the command.
+	cmdArgs := make([]string, len(args))
+	for i, arg := range args {
+		cmdArgs[i] = expandKeywords(arg, d)
+	}
+
+	// Set the ANDROID_SERIAL variable.
+	sh.Vars["ANDROID_SERIAL"] = d.Serial
+
+	cmd := sh.Cmd(cmdArgs[0], cmdArgs[1:]...)
+	return runGoshCommandForDevice(cmd, d, false)
+}
diff --git a/madb.go b/madb.go
index 1705925..5ccbb5b 100644
--- a/madb.go
+++ b/madb.go
@@ -84,6 +84,7 @@
 	Children: []*cmdline.Command{
 		cmdMadbClearData,
 		cmdMadbExec,
+		cmdMadbExtern,
 		cmdMadbInstall,
 		cmdMadbName,
 		cmdMadbShell,