Add a command to build a Fuchsia targeting rustc

Change-Id: I838e5e30edf2ce4d75bffae1dfae3787a8ed4500
Testing: manual
diff --git a/src/build_rustc.rs b/src/build_rustc.rs
new file mode 100644
index 0000000..76b6f93
--- /dev/null
+++ b/src/build_rustc.rs
@@ -0,0 +1,124 @@
+use crate::sdk::{clang_base_path, clang_resource_dir, shared_libraries_path, sysroot_path, TargetOptions};
+use crate::X64;
+use crate::{get_target_triple, get_triple_cpu};
+
+use failure::Error;
+use std::fs::create_dir_all;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+use std::process::Command;
+
+pub fn build_rustc(rust_root: &Path, target_options: &TargetOptions<'_, '_>) -> Result<(), Error> {
+    let target_triple = get_target_triple(target_options);
+    let triple_cpu = get_triple_cpu(target_options);
+    let targets_name = if target_options.config.fuchsia_arch == X64 {
+        "X86"
+    } else {
+        "AArch64"
+    };
+    let clang_base_path = clang_base_path()?;
+    let clang_bin_path = clang_base_path.join("bin");
+    let staging = rust_root.join("fuchsia_staging");
+    create_dir_all(&staging)?;
+    let build = staging.join("build");
+    create_dir_all(&build)?;
+    let config_path = staging.join("config.toml");
+
+    let mut config = File::create(&config_path)?;
+    writeln!(
+        config,
+        r#"
+[llvm]
+optimize = true
+static-libstdcpp = true
+ninja = true
+targets = "{targets}"
+
+[build]
+target = ["{target}-fuchsia"]
+docs = false
+extended = true
+cargo-native-static = true
+
+[install]
+prefix = "{prefix}"
+sysconfdir = "etc"
+
+[rust]
+optimize = true
+channel = "nightly"
+lld = true
+
+[target.{target}-fuchsia]
+cc = "{cc}"
+cxx = "{cxx}"
+ar = "{ar}"
+linker = "{linker}"
+
+[dist]
+    "#,
+        prefix = staging.to_string_lossy(),
+        cc = clang_bin_path.join("clang").to_string_lossy(),
+        cxx = clang_bin_path.join("clang++").to_string_lossy(),
+        ar = clang_bin_path.join("llvm-ar").to_string_lossy(),
+        linker = clang_bin_path.join("ld.lld").to_string_lossy(),
+        target = triple_cpu,
+        targets = targets_name,
+    )?;
+
+    let sysroot_path = sysroot_path(target_options)?;
+    let sysroot_lib_path = sysroot_path.join("lib");
+    let clang_resource_lib = clang_resource_dir(&target_triple)?
+        .join(&target_triple)
+        .join("lib");
+
+    let cargo_dir = staging.join(".cargo");
+    create_dir_all(&cargo_dir)?;
+    let cargo_config_path = cargo_dir.join("config");
+
+    let mut cargo_config = File::create(&cargo_config_path)?;
+    writeln!(
+        cargo_config,
+        r#"
+[target.{target}-fuchsia]
+ar = "{ar}"
+rustflags = [
+  "-C", "link-arg=--sysroot={sysroot}",
+  "-C", "link-arg=-L{sysroot}/lib",
+  "-C", "link-arg=-L{target_lib}",
+  "-C", "link-arg=-L{clang_resource_lib}",
+]
+    "#,
+        target = triple_cpu,
+        sysroot = sysroot_path.to_string_lossy(),
+        ar = clang_bin_path.join("llvm-ar").to_string_lossy(),
+        target_lib = shared_libraries_path(target_options)?.to_string_lossy(),
+        clang_resource_lib = clang_resource_lib.to_string_lossy(),
+    )?;
+    let x_py = rust_root.join("x.py");
+    let common_c_flags = format!(
+        "--sysroot={} --target={}-fuchsia",
+        sysroot_path.to_str().unwrap(),
+        triple_cpu
+    );
+    let ld_flags = format!(
+        "--sysroot={} --target={}-fuchsia -L{}",
+        sysroot_path.to_str().unwrap(),
+        triple_cpu,
+        sysroot_lib_path.to_string_lossy()
+    );
+    let mut cmd = Command::new(x_py);
+    cmd.current_dir(build)
+        .env(format!("CFLAGS_{}-fuchsia", triple_cpu), &common_c_flags)
+        .env(format!("LDFLAGS_{}-fuchsia", triple_cpu), &ld_flags)
+        .arg("build")
+        .arg("--config")
+        .arg(&config_path)
+        .arg("--target")
+        .arg(format!("{}-fuchsia", triple_cpu));
+
+    println!("cmd = {:#?}", cmd);
+    cmd.status().expect("x_py command failed to start");
+    Ok(())
+}
diff --git a/src/lib.rs b/src/lib.rs
index 5e86567..7b707f2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,6 +8,7 @@
 
 #![recursion_limit = "1024"]
 
+mod build_rustc;
 mod cross;
 mod device;
 mod sdk;
@@ -15,6 +16,7 @@
 
 pub use crate::sdk::{FuchsiaConfig, TargetOptions};
 
+use crate::build_rustc::build_rustc;
 use crate::cross::{pkg_config_path, run_configure, run_pkg_config};
 use crate::device::{enable_networking, netaddr, netls, scp_to_device, ssh, start_emulator,
                     stop_emulator, StartEmulatorOptions};
@@ -775,6 +777,9 @@
 
 static WRITE_CONFIG: &str = "write-config";
 
+static BUILD_RUSTC: &str = "build-rustc";
+static RUST_ROOT: &str = "rust-root";
+
 static RUN_ON_TARGET: &str = "run-on-target";
 
 #[doc(hidden)]
@@ -1070,6 +1075,17 @@
             .subcommand(SubCommand::with_name(WRITE_CONFIG).about(
                 "Write a .cargo/config file to allow cargo to operate correctly for Fuchsia",
             ))
+            .subcommand(
+                SubCommand::with_name(BUILD_RUSTC)
+                    .arg(
+                        Arg::with_name(RUST_ROOT)
+                            .long(RUST_ROOT)
+                            .value_name(RUST_ROOT)
+                            .required(true)
+                            .help("Path to rust checkout"),
+                    )
+                    .about("Build rustc targeting Fuchsia"),
+            )
             .get_matches();
 
     let verbose = matches.is_present("verbose");
@@ -1368,5 +1384,10 @@
         return write_config(&run_cargo_options, &target_options);
     }
 
+    if let Some(build_rustc_matches) = matches.subcommand_matches(BUILD_RUSTC) {
+        let rust_root = PathBuf::from(build_rustc_matches.value_of(RUST_ROOT).unwrap());
+        return build_rustc(&rust_root, &target_options);
+    }
+
     Ok(())
 }