devtools/madb: initial commit of the madb tool
This CL includes the very basic functionality of the madb tool.
The proposal document can be found at: go/madb-proposal
Change-Id: I4b0904d14e312a330c890a7b7a3f71f70ba33d6a
diff --git a/doc.go b/doc.go
new file mode 100644
index 0000000..d218c7c
--- /dev/null
+++ b/doc.go
@@ -0,0 +1,73 @@
+// Copyright 2015 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.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+Multi-device Android Debug Bridge
+
+The madb command wraps Android Debug Bridge (adb) command line tool and provides
+various features for controlling multiple Android devices concurrently.
+
+Usage:
+ madb [flags] <command>
+
+The madb commands are:
+ exec Run the provided adb command on all the specified devices
+ concurrently
+ help Display help for commands or topics
+
+The global flags are:
+ -metadata=<just specify -metadata to activate>
+ Displays metadata for the program and exits.
+ -time=false
+ Dump timing information to stderr before exiting the program.
+
+Madb exec - Run the provided adb command on all the specified devices concurrently
+
+Runs the provided adb command on all the specified devices concurrently.
+
+For example, the following line:
+
+ madb -a exec push ./foo.txt /sdcard/foo.txt
+
+copies the ./foo.txt file to /sdcard/foo.txt for all the currently connected
+Android devices (specified by -a flag).
+
+To see the list of available adb commands, type 'adb help'.
+
+Usage:
+ madb exec [flags] <command>
+
+<command> is a normal adb command, which will be executed on all the specified
+devices.
+
+Madb help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+ madb help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The madb help flags are:
+ -style=compact
+ The formatting style for help output:
+ compact - Good for compact cmdline output.
+ full - Good for cmdline output, shows all global flags.
+ godoc - Good for godoc processing.
+ shortonly - Only output short description.
+ Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+ Format output to this target width in runes, or unlimited if width < 0.
+ Defaults to the terminal width if available. Override the default by setting
+ the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/exec.go b/exec.go
new file mode 100644
index 0000000..653829b
--- /dev/null
+++ b/exec.go
@@ -0,0 +1,64 @@
+// Copyright 2015 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 (
+ "os"
+
+ "v.io/x/lib/cmdline"
+ "v.io/x/lib/gosh"
+)
+
+var cmdMadbExec = &cmdline.Command{
+ Runner: cmdline.RunnerFunc(runMadbExec),
+ Name: "exec",
+ Short: "Run the provided adb command on all the specified devices concurrently",
+ Long: `
+Runs the provided adb command on all the specified devices concurrently.
+
+For example, the following line:
+
+ madb -a exec push ./foo.txt /sdcard/foo.txt
+
+copies the ./foo.txt file to /sdcard/foo.txt for all the currently connected Android devices (specified by -a flag).
+
+To see the list of available adb commands, type 'adb help'.
+`,
+ ArgsName: "<command>",
+ ArgsLong: `
+<command> is a normal adb command, which will be executed on all the specified devices.
+`,
+}
+
+func runMadbExec(env *cmdline.Env, args []string) error {
+ if err := startAdbServer(); err != nil {
+ return err
+ }
+
+ devices, err := getDevices()
+ if err != nil {
+ return err
+ }
+
+ for _, device := range devices {
+ sh := gosh.NewShell(gosh.Opts{})
+ defer sh.Cleanup()
+
+ cmdArgs := append([]string{"-s", device}, args...)
+ cmd := sh.Cmd("adb", cmdArgs...)
+
+ // TODO(youngseokyoon): use pipes instead to prefix console messages with their device names.
+ // For now, just forward all the messages to stdout/stderr.
+ cmd.AddStdoutWriter(gosh.NopWriteCloser(os.Stdout))
+ cmd.AddStderrWriter(gosh.NopWriteCloser(os.Stderr))
+
+ cmd.Start()
+ defer cmd.Wait()
+
+ // TODO(youngseokyoon): check for exit code of each command
+ }
+
+ return nil
+}
diff --git a/madb.go b/madb.go
new file mode 100644
index 0000000..11020fd
--- /dev/null
+++ b/madb.go
@@ -0,0 +1,80 @@
+// Copyright 2015 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $JIRI_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+ "fmt"
+ "os/exec"
+ "strings"
+
+ "v.io/x/lib/cmdline"
+ "v.io/x/lib/gosh"
+)
+
+var cmdMadb = &cmdline.Command{
+ Children: []*cmdline.Command{cmdMadbExec},
+ Name: "madb",
+ Short: "Multi-device Android Debug Bridge",
+ Long: `
+Multi-device Android Debug Bridge
+
+The madb command wraps Android Debug Bridge (adb) command line tool
+and provides various features for controlling multiple Android devices concurrently.
+`,
+}
+
+func main() {
+ cmdline.Main(cmdMadb)
+}
+
+// Makes sure that adb server is running.
+// Intended to be called at the beginning of each subcommand.
+func startAdbServer() error {
+ // TODO(youngseokyoon): search for installed adb tool more rigourously.
+ if err := exec.Command("adb", "start-server").Run(); err != nil {
+ return fmt.Errorf("Failed to start adb server. Please make sure that adb is in your PATH: %v", err)
+ }
+
+ return nil
+}
+
+// Runs "adb devices" command, and parses the result to get all the device serial numbers.
+func getDevices() ([]string, error) {
+ sh := gosh.NewShell(gosh.Opts{})
+ defer sh.Cleanup()
+
+ output := sh.Cmd("adb", "devices", "-l").Stdout()
+
+ return parseDevicesOutput(output)
+}
+
+// Parses the output generated from "adb devices -l" command and return the list of device serial numbers
+// Devices that are currently offline are excluded from the returned list.
+func parseDevicesOutput(output string) ([]string, error) {
+ lines := strings.Split(output, "\n")
+
+ result := []string{}
+
+ // Check the first line of the output
+ if len(lines) <= 0 || strings.TrimSpace(lines[0]) != "List of devices attached" {
+ return result, fmt.Errorf("The output from 'adb devices -l' command does not look as expected.")
+ }
+
+ // Iterate over all the device serial numbers, starting from the second line.
+ for _, line := range lines[1:] {
+ fields := strings.Fields(line)
+
+ if len(fields) <= 1 || fields[1] == "offline" {
+ continue
+ }
+
+ result = append(result, strings.Fields(line)[0])
+ }
+
+ return result, nil
+}
diff --git a/madb_test.go b/madb_test.go
new file mode 100644
index 0000000..6f271f8
--- /dev/null
+++ b/madb_test.go
@@ -0,0 +1,56 @@
+// Copyright 2015 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 (
+ "reflect"
+ "testing"
+)
+
+func TestParseDevicesOutput(t *testing.T) {
+ var output string
+
+ // Normal case
+ output = `List of devices attached
+deviceid01 device usb:3-3.4.3 product:bullhead model:Nexus_5X device:bullhead
+deviceid02 device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
+
+`
+
+ result, err := parseDevicesOutput(output)
+ if err != nil {
+ t.Fatalf("failed to parse the output: %v", err)
+ }
+ if got, want := result, []string{"deviceid01", "deviceid02"}; !reflect.DeepEqual(got, want) {
+ t.Fatalf("unmatched results: got %v, want %v", got, want)
+ }
+
+ // No devices at all
+ output = `List of devices attached
+
+`
+
+ result, err = parseDevicesOutput(output)
+ if err != nil {
+ t.Fatalf("failed to parse the output: %v", err)
+ }
+ if got, want := result, []string{}; !reflect.DeepEqual(got, want) {
+ t.Fatalf("unmatched results: got %v, want %v", got, want)
+ }
+
+ // Offline devices should be excluded
+ output = `List of devices attached
+deviceid01 offline usb:3-3.4.3 product:bullhead model:Nexus_5X device:bullhead
+deviceid02 device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
+
+`
+ result, err = parseDevicesOutput(output)
+ if err != nil {
+ t.Fatalf("failed to parse the output: %v", err)
+ }
+ if got, want := result, []string{"deviceid02"}; !reflect.DeepEqual(got, want) {
+ t.Fatalf("unmatched results: got %v, want %v", got, want)
+ }
+}