Implement doc as a fargo command

Also make sure it uses our toolchain’s rustdoc by default.

Also unrelated formatting changes from having run rustup today.

Change-Id: Ia24f8ff6fdd2c87d058f829380b96c1db6b71feb
diff --git a/cratest/src/main.rs b/cratest/src/main.rs
index df5222d..2c72697 100644
--- a/cratest/src/main.rs
+++ b/cratest/src/main.rs
@@ -116,7 +116,8 @@
 
     let tmpdir = TempDir::new("cratest").unwrap();
 
-    let results: Vec<CrateResult> = res.crates
+    let results: Vec<CrateResult> = res
+        .crates
         .par_iter()
         .map(|cr| {
             if excludes.contains(&cr.id) {
diff --git a/src/lib.rs b/src/lib.rs
index 0c3406d..e2b2f3c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,7 +26,8 @@
 pub use sdk::TargetOptions;
 use sdk::{cargo_out_dir, cargo_path, clang_archiver_path, clang_c_compiler_path,
           clang_cpp_compiler_path, clang_linker_path, clang_ranlib_path, fidl2_target_gen_dir,
-          rustc_path, shared_libraries_path, sysroot_path, target_gen_dir, FuchsiaConfig};
+          rustc_path, rustdoc_path, shared_libraries_path, sysroot_path, target_gen_dir,
+          FuchsiaConfig};
 use std::fs;
 use std::path::PathBuf;
 use std::process::Command;
@@ -215,6 +216,19 @@
     Ok(())
 }
 
+fn build_doc(
+    run_cargo_options: RunCargoOptions, target_options: &TargetOptions, no_deps: bool, open: bool
+) -> Result<(), Error> {
+    let mut args = vec![];
+    if no_deps {
+        args.push("--no-deps");
+    }
+    if open {
+        args.push("--open");
+    }
+    run_cargo(run_cargo_options, DOC, &args, &target_options, None, None)
+}
+
 fn load_driver(
     run_cargo_options: RunCargoOptions, target_options: &TargetOptions,
 ) -> Result<(), Error> {
@@ -228,7 +242,8 @@
         None,
     )?;
     let cwd = std::env::current_dir()?;
-    let package = cwd.file_name()
+    let package = cwd
+        .file_name()
         .ok_or(err_msg("No current directory"))?
         .to_str()
         .ok_or(err_msg("Invalid current directory"))?;
@@ -353,9 +368,11 @@
         fs::canonicalize(std::env::current_exe()?)?
     };
 
-    let mut runner_args = vec![fargo_path
-        .to_str()
-        .ok_or_else(|| err_msg("unable to convert path to utf8 encoding"))?];
+    let mut runner_args = vec![
+        fargo_path
+            .to_str()
+            .ok_or_else(|| err_msg("unable to convert path to utf8 encoding"))?,
+    ];
 
     if options.verbose {
         runner_args.push("-v");
@@ -388,7 +405,8 @@
     let sysroot_as_path = sysroot_path(target_options)?;
     let sysroot_as_str = sysroot_as_path.to_str().unwrap();
 
-    let args: Vec<&str> = args.iter()
+    let args: Vec<&str> = args
+        .iter()
         .map(|a| if *a == "++" { "--" } else { *a })
         .collect();
 
@@ -425,6 +443,7 @@
             clang_linker_path(target_options)?.to_str().unwrap(),
         )
         .env("RUSTC", rustc_path(target_options)?.to_str().unwrap())
+        .env("RUSTDOC", rustdoc_path(target_options)?.to_str().unwrap())
         .env("FUCHSIA_GEN_ROOT", target_gen_dir(target_options)?)
         .env("FIDL_GEN_ROOT", fidl2_target_gen_dir(target_options)?)
         .arg(subcommand)
@@ -477,6 +496,10 @@
 static EXAMPLE: &str = "example";
 static EXAMPLES: &str = "examples";
 
+static DOC: &str = "doc";
+static DOC_OPEN: &str = "open";
+static DOC_NO_DEPS: &str = "no-deps";
+
 static TARGET_CPU: &str = "target-cpu";
 static X64: &str = "x64";
 static ARM64: &str = "arm64";
@@ -488,6 +511,8 @@
 static NO_NET: &str = "no-net";
 static FX_RUN_PARAMS: &str = "fx-run-params";
 
+static RELEASE_HELP: &str = "Build artifacts in release mode, with optimizations";
+
 #[doc(hidden)]
 pub fn run() -> Result<(), Error> {
     let matches = App::new("fargo")
@@ -548,7 +573,7 @@
         .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(RELEASE).long(RELEASE).help(RELEASE_HELP))
                 .arg(
                     Arg::with_name("test")
                         .long("test")
@@ -566,7 +591,7 @@
         .subcommand(
             SubCommand::with_name("build")
                 .about("Build binary targeting Fuchsia device or emulator")
-                .arg(Arg::with_name(RELEASE).long(RELEASE).help("Build release"))
+                .arg(Arg::with_name(RELEASE).long(RELEASE).help(RELEASE_HELP))
                 .arg(
                     Arg::with_name("example")
                         .long("example")
@@ -582,7 +607,7 @@
         .subcommand(
             SubCommand::with_name(CHECK)
                 .about("Check binary targeting Fuchsia device or emulator")
-                .arg(Arg::with_name(RELEASE).long(RELEASE).help("Check release"))
+                .arg(Arg::with_name(RELEASE).long(RELEASE).help(RELEASE_HELP))
                 .arg(
                     Arg::with_name(EXAMPLE)
                         .long(EXAMPLE)
@@ -596,9 +621,24 @@
                 ),
         )
         .subcommand(
+            SubCommand::with_name(DOC)
+                .about("Build a package's documentation")
+                .arg(Arg::with_name(RELEASE).long(RELEASE).help(RELEASE_HELP))
+                .arg(
+                    Arg::with_name(DOC_NO_DEPS)
+                        .long(DOC_NO_DEPS)
+                        .help("Don't build documentation for dependencies"),
+                )
+                .arg(
+                    Arg::with_name(DOC_OPEN)
+                        .long(DOC_OPEN)
+                        .help("Opens the docs in a browser after the operation"),
+                ),
+        )
+        .subcommand(
             SubCommand::with_name("run")
                 .about("Run binary on Fuchsia device or emulator")
-                .arg(Arg::with_name(RELEASE).long(RELEASE).help("Build release"))
+                .arg(Arg::with_name(RELEASE).long(RELEASE).help(RELEASE_HELP))
                 .arg(
                     Arg::with_name(SET_ROOT_VIEW)
                         .long(SET_ROOT_VIEW)
@@ -822,6 +862,15 @@
         return Ok(());
     }
 
+    if let Some(doc_matches) = matches.subcommand_matches(DOC) {
+        return build_doc(
+            run_cargo_options.release(doc_matches.is_present(RELEASE)),
+            &target_options,
+            doc_matches.is_present(DOC_NO_DEPS),
+            doc_matches.is_present(DOC_OPEN),
+        );
+    }
+
     if matches.subcommand_matches("list-devices").is_some() {
         return netls(verbose, &target_options);
     }
diff --git a/src/sdk.rs b/src/sdk.rs
index 314aa88..cf1aebe 100644
--- a/src/sdk.rs
+++ b/src/sdk.rs
@@ -179,6 +179,14 @@
     }
 }
 
+pub fn rustdoc_path(target_options: &TargetOptions) -> Result<PathBuf, Error> {
+    if let Some(rustdoc_path) = get_path_from_env("FARGO_RUSTDOC", false)? {
+        Ok(rustdoc_path)
+    } else {
+        Ok(buildtools_path(target_options)?.join("rust/bin/rustdoc"))
+    }
+}
+
 pub fn toolchain_path(target_options: &TargetOptions) -> Result<PathBuf, Error> {
     Ok(buildtools_path(target_options)?.join("clang"))
 }