Initial commit

Change-Id: I8a2bd53b6ff90008ea5c7915c56ed4deb5d0d86f
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..324c57f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+target/
+**/*.rs.bk
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..f4de997
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,212 @@
+[root]
+name = "fargo"
+version = "0.1.0"
+dependencies = [
+ "clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "uname 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "atty"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bitflags"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "clap"
+version = "2.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "itoa"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num-traits"
+version = "0.1.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "quote"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "strsim"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "syn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "synom"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "term_size"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "uname"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-xid"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "vec_map"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
+"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
+"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
+"checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f"
+"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90"
+"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "38f5c2b18a287cf78b4097db62e20f43cace381dc76ae5c0a3073067f78b7ddc"
+"checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6"
+"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
+"checksum serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6a7c6b751a2e8d5df57a5ff71b5b4fc8aaee9ee28ff1341d640dd130bb5f4f7a"
+"checksum serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2f6ca58905ebd3c3b285a8a6d4f3ac92b92c0d7951d5649b1bdd212549c06639"
+"checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a"
+"checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b"
+"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
+"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
+"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
+"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
+"checksum uname 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
+"checksum unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946"
+"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
+"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
+"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..ff6b025
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "fargo"
+version = "0.1.0"
+authors = ["Rob Tsuk <robtsuk@google.com>"]
+
+[dependencies]
+clap = "2"
+serde = "1.0.9"
+serde_derive = "1.0.9"
+serde_json = "1.0.2"
+uname = "0.1.1"
\ No newline at end of file
diff --git a/README.md b/README.md
index 8b90b74..5e204f1 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,36 @@
-Fuchsia Open Source Template Repository
-=======================================
+# fargo
 
-This repository is a template that we will use when creating new open source
-repositories for Fuchsia.
+Fargo is a prototype Fuchsia-specific wrapper around Cargo.
+
+    USAGE:
+        fargo [FLAGS] [SUBCOMMAND]
+
+    FLAGS:
+        -h, --help       Prints help information
+        -V, --version    Prints version information
+        -v               Print verbose output while performing commands
+
+    SUBCOMMANDS:
+        build          Build binary targeting Fuchsia device or emulator
+        build-tests    Build for Fuchsia device or emulator
+        help           Prints this message or the help of the given subcommand(s)
+        restart        Stop all Fuchsia emulators and start a new one
+        run            Run binary on Fuchsia device or emulator
+        ssh            Open a shell on Fuchsia device or emulator
+        start          Start a Fuchsia emulator
+        stop           Stop all Fuchsia emulators
+        test           Run unit tests on Fuchsia device or emulator
+
+The `fargo-test` directory contains something one can use to test-drive.
+
+__At the moment fargo requires the FUCHSIA\_ROOT environmental variable be set to the path to a Fuchsia build.__ The goal is to transition
+fargo to using something like an SDK instead.
+
+Currently fargo does not support building artifacts that need additional libraries.
+
+## To Do
+
+* ~~Clean up build/build-test~~
+* ~~Add support for release builds~~
+* Add support for depending on the FIDL generated crates.
+* Add support for crates like cairo-rs that need native libraries.
diff --git a/fargo-test/Cargo.lock b/fargo-test/Cargo.lock
new file mode 100644
index 0000000..8089307
--- /dev/null
+++ b/fargo-test/Cargo.lock
@@ -0,0 +1,69 @@
+[root]
+name = "fargo-test"
+version = "0.1.0"
+dependencies = [
+ "magenta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mxruntime 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bitflags"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "conv"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "custom_derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "magenta"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "magenta-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "magenta-sys"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mxruntime"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "magenta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "magenta-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mxruntime-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mxruntime-sys"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "magenta-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[metadata]
+"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
+"checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299"
+"checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
+"checksum magenta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92159d95603bd96e44850aa5b458656a372b3324940f3523a20d08f0f9cd8760"
+"checksum magenta-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07fd18dd6819f5a24387638769d95fe6e9145eb15c1756d721620604306b7b72"
+"checksum mxruntime 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d267fa561985cefb55172834a020f0e77339dbda7287482fd4b58936e792089a"
+"checksum mxruntime-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b3f2afc13950ce3a42515cfc3bdc949f552b0e71109c52481d250ec0e0d9eb8"
diff --git a/fargo-test/Cargo.toml b/fargo-test/Cargo.toml
new file mode 100644
index 0000000..5730de4
--- /dev/null
+++ b/fargo-test/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "fargo-test"
+version = "0.1.0"
+authors = ["Rob Tsuk <robtsuk@google.com>"]
+
+[dependencies]
+magenta = "0.1.0"
+mxruntime = "0.1.0"
+
+[[test]]
+name = "bork"
+path = "test/bork_bork.rs"
\ No newline at end of file
diff --git a/fargo-test/src/main.rs b/fargo-test/src/main.rs
new file mode 100644
index 0000000..7cdef14
--- /dev/null
+++ b/fargo-test/src/main.rs
@@ -0,0 +1,30 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+extern crate magenta;
+
+fn main() {
+    println!("Hello, world!");
+    println!("Hello, world!");
+    println!("Hello, world!");
+    println!("Hello, world!");
+    println!("Hello, world!");
+    println!("Hello, world!");
+    println!("Hello, world!");
+}
+
+#[cfg(test)]
+mod tests {
+
+    use magenta::{Channel, ChannelOpts};
+
+    #[test]
+    fn noop_test() {}
+
+    #[test]
+    fn channel_call_test() {
+        // Create a pair of channels
+        let (p1, p2) = Channel::create(ChannelOpts::Normal).unwrap();
+    }
+}
diff --git a/fargo-test/test/bork_bork.rs b/fargo-test/test/bork_bork.rs
new file mode 100644
index 0000000..985c594
--- /dev/null
+++ b/fargo-test/test/bork_bork.rs
@@ -0,0 +1,9 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[test]
+fn test_dogs() {
+    let dogs = "good";
+    assert_eq!(dogs, "good");
+}
diff --git a/src/cargo_interop.rs b/src/cargo_interop.rs
new file mode 100644
index 0000000..88f217a
--- /dev/null
+++ b/src/cargo_interop.rs
@@ -0,0 +1,52 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Structs for deserializing the output of cargo commands
+// Derived from inspection of the output of cargo commands
+// run with the "--message-format json" parameter.
+
+#[derive(Deserialize,Debug)]
+pub struct Profile {
+    pub test: bool,
+}
+
+#[derive(Deserialize,Debug)]
+pub struct Target {
+    pub kind: Vec<String>,
+}
+
+#[derive(Deserialize,Debug)]
+pub struct Artifact {
+    #[serde(default)]
+    pub filenames: Vec<String>,
+    pub profile: Profile,
+    pub target: Target,
+}
+
+#[derive(Deserialize,Debug)]
+pub struct Code {
+    code: Option<String>,
+    explanation: String,
+}
+
+#[derive(Deserialize,Debug)]
+pub struct Span {
+    file_name: String,
+    label: Option<String>,
+    line_start: i32,
+    line_end: i32,
+}
+
+#[derive(Deserialize,Debug)]
+pub struct Message {
+    level: String,
+    message: String,
+    code: Option<Code>,
+    spans: Vec<Span>,
+}
+
+#[derive(Deserialize,Debug)]
+pub struct MessageWrapper {
+    message: Option<Message>,
+}
diff --git a/src/device.rs b/src/device.rs
new file mode 100644
index 0000000..a217de6
--- /dev/null
+++ b/src/device.rs
@@ -0,0 +1,132 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::env;
+use std::path::PathBuf;
+use std::process::{Command, Stdio};
+use std::str;
+use sdk::fuchsia_root;
+use utils::is_mac;
+
+pub fn netaddr() -> Result<String, String> {
+    let fuchsia_root = fuchsia_root();
+    let netaddr_binary = fuchsia_root.join("out/build-magenta/tools/netaddr");
+    let netaddr_result = Command::new(netaddr_binary)
+        .arg("--fuchsia")
+        .output()
+        .expect("Couldn't run netaddr.");
+    if netaddr_result.status.success() {
+        Ok(str::from_utf8(&netaddr_result.stdout).unwrap().trim().to_string())
+    } else {
+        Err(format!("netaddr failed with: {}",
+                    String::from_utf8_lossy(&netaddr_result.stderr)))
+    }
+}
+
+pub fn scp_to_device(netaddr: &String,
+                     source_path: &PathBuf,
+                     destination_path: &String)
+                     -> Result<(), String> {
+    let destination_with_address = format!("[{}]:{}", netaddr, destination_path);
+    let fuchsia_root = fuchsia_root();
+    let ssh_config = fuchsia_root.join("out/debug-x86-64/ssh-keys/ssh_config");
+    let ssh_result = Command::new("scp")
+        .arg("-q")
+        .arg("-F")
+        .arg(ssh_config)
+        .arg(source_path)
+        .arg(destination_with_address)
+        .status()
+        .expect("Unable to run ssh.");
+
+    if ssh_result.success() {
+        Ok(())
+    } else {
+        Err(format!("scp failed with error {:?}", ssh_result))
+    }
+}
+
+pub fn ssh(command: &str) {
+    let netaddr_result = netaddr();
+    let fuchsia_root = fuchsia_root();
+    let ssh_config = fuchsia_root.join("out/debug-x86-64/ssh-keys/ssh_config");
+    match netaddr_result {
+        Ok(netaddr) => {
+            let ssh_result = Command::new("ssh")
+                .arg("-q")
+                .arg("-F")
+                .arg(ssh_config)
+                .arg(netaddr)
+                .arg(command)
+                .status()
+                .expect("Unable to run ssh.");
+            if !ssh_result.success() {
+                println!("ssh failed: {}", ssh_result);
+            }
+
+        }
+
+        Err(netaddr_err) => {
+            println!("{}", netaddr_err);
+        }
+    }
+}
+
+pub fn start_emulator(with_graphics: bool) {
+    let fuchsia_root = fuchsia_root();
+    let run_magenta_script = fuchsia_root.join("scripts/run-magenta-x86-64");
+    let user_bootfs = fuchsia_root.join("out/debug-x86-64/user.bootfs");
+    let user_bootfs_str = user_bootfs.to_str().unwrap();
+    let mut args = vec!["-N", "-x", user_bootfs_str];
+    if with_graphics {
+        args.push("-g");
+    }
+    let child = Command::new(run_magenta_script)
+        .args(&args)
+        .stdout(Stdio::null())
+        .stderr(Stdio::null())
+        .spawn()
+        .expect("Unable to run magenta.");
+    println!("emulator started with process ID {}", child.id());
+
+    if is_mac() {
+
+        let user = env::var("USER").unwrap();
+
+        println!("Calling sudo ifconfig to bring up tap0 interface; password may be required.");
+
+        let chown_status = Command::new("sudo")
+            .arg("chown")
+            .arg(user)
+            .arg("/dev/tap0")
+            .status()
+            .expect("Couldn't run chown.");
+
+        if chown_status.success() {
+            let ifconfig_status = Command::new("sudo")
+                .arg("ifconfig")
+                .arg("tap0")
+                .arg("inet6")
+                .arg("fc00::/7")
+                .arg("up")
+                .status()
+                .expect("Couldn't run ifconfig.");
+
+            if ifconfig_status.success() {
+                println!("tap0 enabled");
+                // If sudo needed a password, sometimes the terminal gets into a funky state
+                Command::new("stty").arg("sane").status().expect("Couldn run stty");
+            } else {
+                println!("ifconfig failed");
+            }
+        }
+    }
+}
+
+pub fn stop_emulator() {
+    let _ = Command::new("killall")
+        .arg("qemu-system-x86_64")
+        .status()
+        .expect("Could not run killall.");
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..4e3022f
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,299 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+extern crate clap;
+extern crate serde;
+#[macro_use]
+extern crate serde_derive;
+extern crate serde_json;
+extern crate uname;
+
+mod cargo_interop;
+mod device;
+mod sdk;
+mod utils;
+
+use cargo_interop::Artifact;
+use clap::{App, Arg, SubCommand};
+use device::{netaddr, scp_to_device, ssh, start_emulator, stop_emulator};
+use sdk::{rust_c_path, rust_linker_path};
+use std::path::PathBuf;
+use std::process::Command;
+use std::str;
+use utils::strip_binary;
+
+fn build_tests(verbose: bool, release: bool, test_target: &String) -> bool {
+    if verbose {
+        println!("# build tests phase 1");
+    }
+    let mut args = vec!["test", "--target", "x86_64-unknown-fuchsia", "--no-run"];
+    if release {
+        args.push("--release");
+    }
+    if test_target.len() > 0 {
+        args.push("--test");
+        args.push(test_target.as_str());
+    }
+    let build_command = Command::new("cargo")
+        .env("RUSTC", rust_c_path().to_str().unwrap())
+        .env("CARGO_TARGET_X86_64_UNKNOWN_FUCHSIA_LINKER",
+             rust_linker_path().to_str().unwrap())
+        .args(args)
+        .status()
+        .expect("cargo command failed to start");
+
+    build_command.success()
+}
+
+fn run_tests(verbose: bool, release: bool, test_target: &String, params: &Vec<String>) {
+    if !build_tests(verbose, release, test_target) {
+        return;
+    }
+    if verbose {
+        println!("# build tests phase 2");
+    }
+    let mut args = vec!["test",
+                        "--target",
+                        "x86_64-unknown-fuchsia",
+                        "-q",
+                        "--no-run",
+                        "--message-format",
+                        "JSON"];
+    if release {
+        args.push("--release");
+    }
+
+    if test_target.len() > 0 {
+        args.push("--test");
+        args.push(test_target.as_str());
+    }
+
+    let output = Command::new("cargo")
+        .env("RUSTC", rust_c_path().to_str().unwrap())
+        .env("CARGO_TARGET_X86_64_UNKNOWN_FUCHSIA_LINKER",
+             rust_linker_path().to_str().unwrap())
+        .args(args)
+        .output()
+        .expect("cargo command failed to start");
+
+    if output.status.success() {
+        let netaddr_result = netaddr();
+        match netaddr_result {
+            Ok(netaddr) => {
+                let artifacts = str::from_utf8(&output.stdout).unwrap().trim().split('\n');
+                for artifact_line in artifacts {
+                    if verbose {
+                        println!("# {}", artifact_line);
+                    }
+                    let artifact: Artifact = serde_json::from_str(&artifact_line).unwrap();
+                    if verbose {
+                        println!("# {:?}", artifact);
+                    }
+                    if artifact.profile.test {
+                        for filename in artifact.filenames {
+                            let source_path = PathBuf::from(&filename);
+                            let stripped_source_path = strip_binary(&source_path);
+                            let destination_path = format!("/tmp/{}",
+                                                           stripped_source_path.file_name()
+                                                               .unwrap()
+                                                               .to_string_lossy());
+                            println!("copying {} to {}",
+                                     source_path.to_string_lossy(),
+                                     destination_path);
+                            let scp_result =
+                                scp_to_device(&netaddr, &stripped_source_path, &destination_path);
+                            match scp_result {
+                                Ok(_) => {
+                                    let command_string = if params.len() > 0 {
+                                        let param_string = params.join(" ");
+                                        destination_path + " " + &param_string
+                                    } else {
+                                        destination_path
+                                    };
+                                    if verbose {
+                                        println!("running {}", command_string);
+                                    }
+                                    ssh(&command_string);
+                                }
+                                Err(scp_err) => {
+                                    println!("scp failed with: {}", scp_err);
+
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            Err(netaddr_err) => {
+                println!("{}", netaddr_err);
+            }
+        }
+    } else {
+        println!("cargo test command failed");
+    }
+}
+
+fn build_binary(verbose: bool, release: bool) -> bool {
+    if verbose {
+        println!("# build binary phase 1");
+    }
+    let mut args = vec!["build", "--target", "x86_64-unknown-fuchsia"];
+    if release {
+        args.push("--release");
+    }
+    let build_command = Command::new("cargo")
+        .env("RUSTC", rust_c_path().to_str().unwrap())
+        .env("CARGO_TARGET_X86_64_UNKNOWN_FUCHSIA_LINKER",
+             rust_linker_path().to_str().unwrap())
+        .args(args)
+        .status()
+        .expect("cargo command failed to start");
+
+    build_command.success()
+}
+
+fn run_binary(verbose: bool, release: bool) {
+    if !build_binary(verbose, release) {
+        return;
+    }
+    let mut args = vec!["build", "--target", "x86_64-unknown-fuchsia"];
+    if release {
+        args.push("--release");
+    }
+    let output = Command::new("cargo")
+        .env("RUSTC", rust_c_path().to_str().unwrap())
+        .env("CARGO_TARGET_X86_64_UNKNOWN_FUCHSIA_LINKER",
+             rust_linker_path().to_str().unwrap())
+        .args(args)
+        .arg("--message-format")
+        .arg("JSON")
+        .output()
+        .expect("cargo command failed to start");
+    if output.status.success() {
+        let netaddr_result = netaddr();
+        match netaddr_result {
+            Ok(netaddr) => {
+                let artifacts = str::from_utf8(&output.stdout).unwrap().trim().split('\n');
+                for artifact_line in artifacts {
+                    if verbose {
+                        println!("# {}", artifact_line);
+                    }
+                    let artifact: Artifact = serde_json::from_str(&artifact_line).unwrap();
+                    if verbose {
+                        println!("# {:?}", artifact);
+                    }
+                    if artifact.target.kind.contains(&"bin".to_string()) {
+                        for filename in artifact.filenames {
+                            let source_path = PathBuf::from(&filename);
+                            let stripped_source_path = strip_binary(&source_path);
+                            let destination_path = format!("/tmp/{}",
+                                                           stripped_source_path.file_name()
+                                                               .unwrap()
+                                                               .to_string_lossy());
+                            println!("copying {} to {}",
+                                     stripped_source_path.to_string_lossy(),
+                                     destination_path);
+                            let scp_result =
+                                scp_to_device(&netaddr, &stripped_source_path, &destination_path);
+                            match scp_result {
+                                Ok(_) => {
+                                    println!("running {}", destination_path);
+                                    ssh(&destination_path);
+                                }
+                                Err(scp_err) => {
+                                    println!("scp failed with: {}", scp_err);
+
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            Err(netaddr_err) => {
+                println!("{}", netaddr_err);
+            }
+        }
+    }
+
+}
+
+fn main() {
+    let matches = App::new("fargo")
+        .version("v0.1.0")
+        .arg(Arg::with_name("verbose")
+            .short("v")
+            .help("Print verbose output while performing commands"))
+        .subcommand(SubCommand::with_name("build-tests")
+            .about("Build for Fuchsia device or emulator")
+            .arg(Arg::with_name("test")
+                .long("test")
+                .value_name("test")
+                .help("Test only the specified test target"))
+            .arg(Arg::with_name("release")
+                .long("release")
+                .help("Build release")))
+        .subcommand(SubCommand::with_name("test")
+            .about("Run unit tests on Fuchsia device or emulator")
+            .arg(Arg::with_name("release")
+                .long("release")
+                .help("Build release"))
+            .arg(Arg::with_name("test")
+                .long("test")
+                .value_name("test")
+                .help("Test only the specified test target"))
+            .arg(Arg::with_name("test_params").index(1).multiple(true)))
+        .subcommand(SubCommand::with_name("build")
+            .about("Build binary targeting Fuchsia device or emulator")
+            .arg(Arg::with_name("release")
+                .long("release")
+                .help("Build release")))
+        .subcommand(SubCommand::with_name("run")
+            .about("Run binary on Fuchsia device or emulator")
+            .arg(Arg::with_name("release")
+                .long("release")
+                .help("Build release")))
+        .subcommand(SubCommand::with_name("start")
+            .about("Start a Fuchsia emulator")
+            .arg(Arg::with_name("graphics")
+                .short("g")
+                .help("Start a simulator with graphics enabled")))
+        .subcommand(SubCommand::with_name("stop").about("Stop all Fuchsia emulators"))
+        .subcommand(SubCommand::with_name("restart")
+            .about("Stop all Fuchsia emulators and start a new one")
+            .arg(Arg::with_name("graphics")
+                .short("g")
+                .help("Start a simulator with graphics enabled")))
+        .subcommand(SubCommand::with_name("ssh")
+            .about("Open a shell on Fuchsia device or emulator"))
+        .get_matches();
+
+    let verbose = matches.is_present("verbose");
+    if let Some(test_matches) = matches.subcommand_matches("test") {
+        let test_params = test_matches.values_of_lossy("test_params").unwrap_or(vec![]);
+        let test_target = test_matches.value_of("test").unwrap_or("").to_string();
+        run_tests(verbose,
+                  test_matches.is_present("release"),
+                  &test_target,
+                  &test_params);
+    } else if let Some(build_matches) = matches.subcommand_matches("build") {
+        build_binary(verbose, build_matches.is_present("release"));
+    } else if let Some(run_matches) = matches.subcommand_matches("run") {
+        run_binary(verbose, run_matches.is_present("release"));
+    } else if let Some(build_test_matches) = matches.subcommand_matches("build-tests") {
+        let test_target = build_test_matches.value_of("test").unwrap_or("").to_string();
+        build_tests(verbose,
+                    build_test_matches.is_present("release"),
+                    &test_target);
+    } else if let Some(start_matches) = matches.subcommand_matches("start") {
+        start_emulator(start_matches.is_present("graphics"));
+    } else if let Some(_) = matches.subcommand_matches("stop") {
+        stop_emulator();
+    } else if let Some(restart_matches) = matches.subcommand_matches("restart") {
+        stop_emulator();
+        start_emulator(restart_matches.is_present("graphics"));
+    } else if let Some(_) = matches.subcommand_matches("ssh") {
+        ssh("");
+    }
+}
diff --git a/src/sdk.rs b/src/sdk.rs
new file mode 100644
index 0000000..cc8d430
--- /dev/null
+++ b/src/sdk.rs
@@ -0,0 +1,44 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::path::PathBuf;
+use std::env;
+use utils::is_mac;
+
+pub fn fuchsia_root() -> PathBuf {
+    let fuchsia_root_value = match env::var("FUCHSIA_ROOT") {
+        Ok(val) => val,
+        Err(_) => {
+            panic!("You must set the environmental variable FUCHSIA_ROOT to point to a Fuchsia \
+                    tree with a debug-x86-64 build including the rust module")
+        }
+    };
+    PathBuf::from(fuchsia_root_value)
+}
+
+fn rust_buildtools_path() -> PathBuf {
+    let platform_name = if is_mac() {
+        "rust-x86_64-apple-darwin"
+    } else {
+        "rust-x86_64-unknown-linux-gnu"
+    };
+    fuchsia_root().join("buildtools/rust").join(platform_name)
+}
+
+pub fn rust_c_path() -> PathBuf {
+    rust_buildtools_path().join("bin/rustc")
+}
+
+pub fn rust_linker_path() -> PathBuf {
+    fuchsia_root().join("out/debug-x86-64/host_x64/x86_64-unknown-fuchsia-cc")
+}
+
+pub fn strip_tool_path() -> PathBuf {
+    let platform_name = if is_mac() {
+        "clang+llvm-x86_64-darwin"
+    } else {
+        "clang+llvm-x86_64-linux"
+    };
+    fuchsia_root().join("buildtools/toolchain").join(platform_name).join("bin/strip")
+}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..b303489
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,38 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::path::PathBuf;
+use std::process::Command;
+use std::str;
+use std::time::Duration;
+use uname::uname;
+use sdk::strip_tool_path;
+
+#[allow(dead_code)]
+pub fn duration_as_milliseconds(duration: &Duration) -> u64 {
+    let subsec_ms: u64 = duration.subsec_nanos() as u64 / 1000000;
+    let dur_ms = duration.as_secs() * 1000 + subsec_ms;
+    dur_ms
+}
+
+pub fn is_mac() -> bool {
+    uname().unwrap().sysname == "Darwin"
+}
+
+pub fn strip_binary(binary: &PathBuf) -> PathBuf {
+    let file_name = binary.file_name().unwrap();
+    let new_file_name = file_name.to_string_lossy().into_owned() + "_stripped";
+    let target_path = binary.parent().unwrap().join(new_file_name);
+    let strip_result = Command::new(strip_tool_path())
+        .arg(binary)
+        .arg("-o")
+        .arg(&target_path)
+        .status()
+        .expect("strip command failed to start");
+
+    if !strip_result.success() {
+        panic!("strip failed with error {:?}", strip_result)
+    }
+    target_path
+}