myd 0.1.1

An implementation of the rust module system
Documentation
//! Manages information about rustlib crates, `core`, `std`, `alloc`, &c, &c

use ego_tree::Tree;
use phf::phf_map;

use crate::{
    parse::{self, resolve_package, Error},
    platforms::SystemInfo,
    Features, Module,
};
use std::{
    env, fs,
    path::{Path, PathBuf},
    process::Command,
    string::FromUtf8Error,
};

use super::compat_pkg::ManualPackage;

/// A list of sysroot crates by path and dependencies
pub const SYSROOT_CRATES: phf::Map<&str, (&str, &[&str])> = phf_map! {
    "alloc" =>             ("alloc",                     &["core"]),
    "backtrace" =>         ("backtrace",                 &[]),
    "core" =>              ("core",                      &[]),
    "panic_abort" =>       ("panic_abort",               &[]),
    "panic_unwind" =>      ("panic_unwind",              &[]),
    "proc_macro" =>        ("proc_macro",                &["std"]),
    "profiler_builtins" => ("profiler_builtins",         &[]),
    "std" =>               ("std",                       &["alloc", "panic_unwind", "panic_abort", "core", "profiler_builtins", "unwind", "std_detect", "test"]),
    "std_detect" =>        ("stdarch/crates/std_detect", &[]),
    "test" =>              ("test",                      &[]),
    "unwind" =>            ("unwind",                    &[]),
};

/// Adds a sysroot crate and all of its dependencies
/// to the tree.
///
/// It will not add a crate twice.
pub(crate) fn add_sysroot_crate(
    tree: &mut Tree<Module>,
    features: &Features,
    platform: &SystemInfo,
    crat: &str,
) -> parse::Result<()> {
    if let Some((path, deps)) = SYSROOT_CRATES.get(crat) {
        // Check to see if we have already added this,
        // as not to add it for a second time
        for toplevel in tree.root().children() {
            if toplevel.value().ident == crat {
                return Ok(());
            }
        }

        // Get the path of the lib.rs
        let sysroot = get_sysroot()?;
        let mut path = sysroot.join(path);
        path.push("src");
        path.push("lib.rs");

        // Resolve the actual source code for the package.
        resolve_package(
            features,
            platform,
            tree,
            ManualPackage::new_rl(crat, &path),
            &mut false,
        )?;

        // Recurse (the base case is when we run out of
        // dependencies)
        //
        // This shouldn't be cyclic as compared to normal dependencies.
        for dep in *deps {
            add_sysroot_crate(tree, features, platform, dep)?;
        }

        Ok(())
    } else {
        Err(Error::InvalidSysrootCrate(crat.to_string()))
    }
}

/// Gets the path to the sysroot
fn get_sysroot() -> parse::Result<PathBuf> {
    let sysroot_dir = discover_sysroot_dir()?;
    let sysroot_src_dir = discover_sysroot_src_dir_or_add_component(&sysroot_dir)?;
    Ok(sysroot_src_dir)
}

/// Finds the sysroot dir using rustc
fn discover_sysroot_dir() -> parse::Result<PathBuf> {
    let stdout = Command::new("rustc")
        .args(["--print", "sysroot"])
        .output()?
        .stdout;
    Ok(stdout_to_path(stdout)?)
}

/// Same as `discover_sysroot_src_dir` but will install `rust-src` from `rustup` if needed
fn discover_sysroot_src_dir_or_add_component(sysroot_path: &Path) -> parse::Result<PathBuf> {
    discover_sysroot_src_dir(sysroot_path)
        .or_else(|| {
            // Try and add the rust source component
            Command::new("rustup")
                .args(["component", "add", "rust-src"])
                .output()
                .ok();
            get_rust_src(sysroot_path)
        })
        .ok_or(Error::Sysroot)
}

/// Gets the src by checking the environment variables,
fn discover_sysroot_src_dir(sysroot_path: &Path) -> Option<PathBuf> {
    if let Ok(path) = env::var("RUST_SRC_PATH") {
        let path = PathBuf::from(path.as_str());
        let core = path.join("core");
        if fs::metadata(core).is_ok() {
            return Some(path);
        }
    }

    get_rust_src(sysroot_path)
}

/// Gets the rust src from the sysroot
fn get_rust_src(sysroot_path: &Path) -> Option<PathBuf> {
    let rust_src = sysroot_path.join("lib/rustlib/src/rust/library");
    if fs::metadata(&rust_src).is_ok() {
        Some(rust_src)
    } else {
        None
    }
}

/// Converts a `stdout` (from `rustc`) to a `PathBuf`
fn stdout_to_path(mut stdout: Vec<u8>) -> Result<PathBuf, FromUtf8Error> {
    // Remove a \n
    if stdout.last() == Some(&b'\n') {
        stdout.pop();
    }
    let path = String::from_utf8(stdout)?;
    Ok(PathBuf::from(path))
}