rustc-llvm-proxy 0.1.2

Dynamically proxy LLVM calls into Rust own shared library
use std::env;
use std::path::PathBuf;
use std::process::Command;

use failure::Error;

pub fn find_lib_path() -> Result<PathBuf, Error> {
    let paths = collect_possible_paths()?;

    if paths.len() == 0 {
        bail!("Unable to find possible LLVM shared lib locations.");
    }

    for path in &paths {
        if path.join("librustc_codegen_llvm-llvm.so").exists() {
            return Ok(path.join("librustc_codegen_llvm-llvm.so"));
        }

        if path.join("librustc_codegen_llvm-llvm.dylib").exists() {
            return Ok(path.join("librustc_codegen_llvm-llvm.dylib"));
        }

        if path.join("rustc_codegen_llvm-llvm.dll").exists() {
            return Ok(path.join("rustc_codegen_llvm-llvm.dll"));
        }
    }

    bail!(
        "Unable to find LLVM shared lib in possible locations:\n- {}",
        paths
            .into_iter()
            .map(|item| item.to_str().unwrap().to_owned())
            .collect::<Vec<_>>()
            .join("\n- ")
    );
}

fn collect_possible_paths() -> Result<Vec<PathBuf>, Error> {
    let mut paths = vec![];

    if let Ok(rustup_home) = env::var("RUSTUP_HOME") {
        let rustup_home = PathBuf::from(rustup_home);
        let rustup_toolchain = env::var("RUSTUP_TOOLCHAIN")?;
        let rustup_arch = extract_arch(&rustup_toolchain);

        paths.push(
            rustup_home
                .join("toolchains")
                .join(&rustup_toolchain)
                .join("lib")
                .join("rustlib")
                .join(rustup_arch)
                .join("codegen-backends"),
        );
    }

    if let Ok(lib_paths) = env::var("LD_LIBRARY_PATH") {
        for item in lib_paths.split(":") {
            let mut possible_path = PathBuf::from(item);
            possible_path.pop();

            if let Some(possible_toolchain) = possible_path.file_name() {
                let possible_arch = extract_arch(possible_toolchain.to_str().unwrap());

                paths.push(
                    possible_path
                        .join("lib")
                        .join("rustlib")
                        .join(possible_arch)
                        .join("codegen-backends"),
                );
            }
        }
    }

    if let Ok(cargo) = env::var("CARGO") {
        let mut cargo_path = PathBuf::from(cargo);
        cargo_path.pop();
        cargo_path.pop();

        if let Some(toolchain) = cargo_path.file_name() {
            let arch = extract_arch(toolchain.to_str().unwrap());

            paths.push(
                cargo_path
                    .join("lib")
                    .join("rustlib")
                    .join(arch)
                    .join("codegen-backends"),
            );
        }
    }

    if let Ok(output) = Command::new("rustup").args(&["which", "rustc"]).output() {
        let mut rustc_path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
        rustc_path.pop();
        rustc_path.pop();

        if let Some(toolchain) = rustc_path.file_name() {
            let arch = extract_arch(toolchain.to_str().unwrap());

            paths.push(
                rustc_path
                    .join("lib")
                    .join("rustlib")
                    .join(arch)
                    .join("codegen-backends"),
            );
        }
    }

    Ok(paths)
}

fn extract_arch(toolchain: &str) -> String {
    toolchain.split('-').skip(1).collect::<Vec<_>>().join("-")
}