Add command to create a Fuchsia-compatible .cargo/config

This should provide a better experience with things like vscode.

Change-Id: Ib2ee3a023f32560e9fc482759d19d54a606d71ca
diff --git a/README.md b/README.md
index 7ac9eac..f4fcb62 100644
--- a/README.md
+++ b/README.md
@@ -8,16 +8,15 @@
 
     FLAGS:
             --debug-os             Use debug user.bootfs and ssh keys
-            --disable-cross-env    Disable the setting of CC, AR and such environmental
-                                   variables.
+            --disable-cross-env    Disable the setting of CC, AR and such environmental variables.
         -h, --help                 Prints help information
         -V, --version              Prints version information
         -v, --verbose              Print verbose output while performing commands
 
     OPTIONS:
         -N, --device-name <device-name>
-                Name of device to target, needed if there are multiple devices visible on
-                the network
+                Name of device to target, needed if there are multiple devices visible on the
+                network
         -T, --target-cpu <target-cpu>
                 Architecture of target device [default: x64]  [values: x64, arm64]
 
@@ -26,11 +25,11 @@
         autotest             Auto build and test in Fuchsia device or emulator
         build                Build binary targeting Fuchsia device or emulator
         build-tests          Build tests for Fuchsia device or emulator
-        cargo                Run a cargo command for Fuchsia. Use -- to indicate that
-                             all following arguments should be passed to cargo.
+        cargo                Run a cargo command for Fuchsia. Use -- to indicate that all
+                             following arguments should be passed to cargo.
         check                Check binary targeting Fuchsia device or emulator
-        configure            Run a configure script for the cross compilation
-                             environment
+        configure            Run a configure script for the cross compilation environment
+        doc                  Build a package's documentation
         enable-networking    Enable networking for a running emulator
         help                 Prints this message or the help of the given subcommand(s)
         list-devices         List visible Fuchsia devices
@@ -42,6 +41,8 @@
         start                Start a Fuchsia emulator
         stop                 Stop all Fuchsia emulators
         test                 Run unit tests on Fuchsia device or emulator
+        write-config         Write a .cargo/config file to allow cargo to operate correctly
+                             for Fuchsia
 
 The `fargo-test` directory contains something one can use to test-drive.
 
@@ -113,6 +114,17 @@
 
 will get cargo to cause rustc to emil llvm ir files.
 
+## Creating a .cargo/config
+
+`fargo --write-config` will create a .cargo directory with a config file that tells cargo
+how to compile artifacts for Fuchsia and how to run them. Creating such a config file
+might allow some tools to work that otherwise would not be able to compile artifacts
+for Fuchsia.
+
+The config file created will be for the architecture and debug/release options that are
+passed to fargo with the `write-config` command. If you wish to switch to a different
+architecture or build, re-run `write-config`.
+
 ## Getting help
 
 For problems getting the Fuchsia build to complete, the #fuchsia IRC channel on
diff --git a/rustfmt.toml b/rustfmt.toml
index 692e394..b3dcc90 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,5 +1,5 @@
 # This file was originally created by running the command
-# rustfmt --dump-default-config rustfmt.toml && sort -u rustfmt.toml -o rustfmt.toml.
+# rustfmt --print-config default rustfmt.toml && sort -u rustfmt.toml -o rustfmt.toml.
 # Changes from the defaults are marked with comments.
 binop_separator = "Front"
 blank_lines_lower_bound = 0
@@ -11,8 +11,9 @@
 condense_wildcard_suffixes = false
 control_brace_style = "AlwaysSameLine"
 disable_all_formatting = false
+emit_mode = "Files"
 empty_item_single_line = true
-error_on_line_overflow = false # Comments with urls are allways too long
+error_on_line_overflow = false
 error_on_unformatted = false
 fn_args_density = "Compressed" # Fuchsia prefers compressed
 fn_single_line = false
@@ -28,22 +29,19 @@
 match_block_trailing_comma = false
 max_width = 100
 merge_derives = true
+merge_imports = false
 newline_style = "Unix"
-normalize_comments = true # Seems useful
-remove_blank_lines_at_start_or_end_of_block = true
-reorder_extern_crates = true
-reorder_extern_crates_in_group = true
-reorder_imported_names = true
-reorder_imports = true # This sort of ordering is useful and should be done by a tool
-reorder_imports_in_group = true # This sort of ordering is useful and should be done by a tool
-reorder_modules = true # This sort of ordering is useful and should be done by a tool
+normalize_comments = false
+remove_nested_parens = true
+reorder_impl_items = false
+reorder_imports = true
+reorder_modules = true
 report_fixme = "Never"
 report_todo = "Never"
 skip_children = false
-spaces_around_ranges = false
-spaces_within_parens_and_brackets = false
 space_after_colon = true
 space_before_colon = false
+spaces_around_ranges = false
 struct_field_align_threshold = 0
 struct_lit_single_line = true
 tab_spaces = 4
@@ -52,7 +50,7 @@
 type_punctuation_density = "Wide"
 unstable_features = false
 use_field_init_shorthand = false
-use_small_heuristics = true
+use_small_heuristics = "Default"
 use_try_shorthand = true # Fuchsia prefers the shortcut
 where_single_line = false
 wrap_comments = true # otherwise comments will violate max_width
diff --git a/src/lib.rs b/src/lib.rs
index 1d7302e..257c461 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,7 +29,9 @@
           clang_cpp_compiler_path, clang_linker_path, clang_ranlib_path, rustc_path, rustdoc_path,
           shared_libraries_path, sysroot_path, zircon_build_path, FuchsiaConfig};
 use std::fs;
-use std::path::PathBuf;
+use std::fs::File;
+use std::io::Write;
+use std::path::{Path, PathBuf};
 use std::process::Command;
 use utils::strip_binary;
 
@@ -303,6 +305,67 @@
     }
 }
 
+fn get_target_triple(target_options: &TargetOptions) -> String {
+    let triple_cpu = if (target_options.target_cpu) == X64 {
+        "x86_64"
+    } else {
+        "aarch64"
+    };
+
+    format!("{}-unknown-fuchsia", triple_cpu)
+}
+
+fn get_rustflags(
+    target_options: &TargetOptions, sysroot_as_path: &PathBuf,
+) -> Result<String, Error> {
+    Ok(format!(
+        "-C link-arg=--target={}-unknown-fuchsia -C link-arg=--sysroot={} -Lnative={}",
+        get_target_triple(target_options),
+        sysroot_as_path.to_str().unwrap(),
+        shared_libraries_path(target_options)?.to_str().unwrap(),
+    ))
+}
+
+fn make_fargo_command(
+    runner: Option<PathBuf>, options: &RunCargoOptions, target_options: &TargetOptions,
+    additional_target_args: Option<&str>,
+) -> Result<String, Error> {
+    let set_root_view_arg = format!("--{}", SET_ROOT_VIEW);
+
+    let fargo_path = if runner.is_some() {
+        runner.unwrap()
+    } else {
+        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"))?,
+    ];
+
+    if options.verbose {
+        runner_args.push("-v");
+    }
+
+    if let Some(device_name) = target_options.device_name {
+        runner_args.push("--device-name");
+        runner_args.push(device_name);
+    }
+
+    runner_args.push("run-on-target");
+
+    if options.set_root_view {
+        runner_args.push(&set_root_view_arg);
+    }
+
+    if let Some(args_for_target) = additional_target_args {
+        runner_args.push(&args_for_target);
+    }
+
+    Ok(runner_args.join(" "))
+}
+
 /// Runs the cargo tool configured to target Fuchsia. When used as a library,
 /// the runner options must contain the path to fargo or some other program
 /// that implements the `run-on-target` subcommand in a way compatible with
@@ -336,13 +399,7 @@
         println!("target_options = {:?}", target_options);
     }
 
-    let set_root_view_arg = format!("--{}", SET_ROOT_VIEW);
-
-    let triple_cpu = if target_options.target_cpu == X64 {
-        "x86_64"
-    } else {
-        "aarch64"
-    };
+    let triple_cpu = get_target_triple(target_options);
     let target_triple = format!("{}-unknown-fuchsia", triple_cpu);
     let mut target_args = vec!["--target", &target_triple];
 
@@ -362,39 +419,8 @@
 
     let target_triple_uc = format!("{}_unknown_fuchsia", triple_cpu).to_uppercase();
 
-    let fargo_path = if runner.is_some() {
-        runner.unwrap()
-    } else {
-        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"))?,
-    ];
-
-    if options.verbose {
-        runner_args.push("-v");
-        target_args.push("-v");
-    }
-
-    if let Some(device_name) = target_options.device_name {
-        runner_args.push("--device-name");
-        runner_args.push(device_name);
-    }
-
-    runner_args.push("run-on-target");
-
-    if options.set_root_view {
-        runner_args.push(&set_root_view_arg);
-    }
-
-    if let Some(args_for_target) = additional_target_args {
-        runner_args.push(&args_for_target);
-    }
-
-    let fargo_command = runner_args.join(" ");
+    let fargo_command =
+        make_fargo_command(runner, &options, target_options, additional_target_args)?;
 
     if options.verbose {
         println!("fargo_command: {:?}", fargo_command);
@@ -431,12 +457,7 @@
     cmd.env(runner_env_name, fargo_command)
         .env(
             rustflags_env_name,
-            format!(
-                "-C link-arg=--target={}-unknown-fuchsia -C link-arg=--sysroot={} -Lnative={}",
-                triple_cpu,
-                sysroot_as_str,
-                shared_libraries_path(target_options)?.to_str().unwrap(),
-            ),
+            get_rustflags(target_options, &sysroot_as_path)?,
         )
         .env(
             linker_env_name,
@@ -492,6 +513,54 @@
     Ok(())
 }
 
+fn write_config(options: &RunCargoOptions, target_options: &TargetOptions) -> Result<(), Error> {
+    let cargo_dir_path = Path::new(".cargo");
+    if cargo_dir_path.exists() {
+        if !cargo_dir_path.is_dir() {
+            bail!(
+                "fargo wants to create a directory {:#?}, but there is an existing file in the way",
+                cargo_dir_path
+            );
+        }
+    } else {
+        fs::create_dir(cargo_dir_path)?;
+    }
+
+    let mut config = File::create(".cargo/config")?;
+
+    let sysroot_as_path = sysroot_path(target_options)?;
+    writeln!(config, "[target.{}]", get_target_triple(target_options))?;
+    writeln!(
+        config,
+        "rustflags = \"{}\"",
+        get_rustflags(target_options, &sysroot_as_path)?
+    )?;
+    writeln!(
+        config,
+        "linker = \"{}\"",
+        clang_linker_path(target_options)?.to_str().unwrap()
+    )?;
+    writeln!(
+        config,
+        "runner = \"{}\"",
+        make_fargo_command(None, options, target_options, None)?
+    )?;
+    writeln!(config, "")?;
+    writeln!(config, "[build]")?;
+    writeln!(
+        config,
+        "rustc = \"{}\"",
+        rustc_path(target_options)?.to_str().unwrap()
+    )?;
+    writeln!(
+        config,
+        "rustdoc = \"{}\"",
+        rustdoc_path(target_options)?.to_str().unwrap()
+    )?;
+    writeln!(config, "target = \"{}\"", get_target_triple(target_options))?;
+    Ok(())
+}
+
 static SET_ROOT_VIEW: &str = "set-root-view";
 
 static CHECK: &str = "check";
@@ -521,6 +590,8 @@
 static GRAPHICS: &str = "graphics";
 static DISABLE_VIRTCON: &str = "disable-virtcon";
 
+static WRITE_CONFIG: &str = "write-config";
+
 #[doc(hidden)]
 pub fn run() -> Result<(), Error> {
     let matches =
@@ -755,6 +826,9 @@
                             .help("Don't pass --host to configure"),
                     ),
             )
+            .subcommand(SubCommand::with_name(WRITE_CONFIG).about(
+                "Write a .cargo/config file to allow cargo to operate correctly for Fuchsia",
+            ))
             .get_matches();
 
     let verbose = matches.is_present("verbose");
@@ -1011,5 +1085,9 @@
         return Ok(());
     }
 
+    if let Some(_write_config_matches) = matches.subcommand_matches(WRITE_CONFIG) {
+        return write_config(&run_cargo_options, &target_options);
+    }
+
     Ok(())
 }