cargo-steel-lib 0.8.3

Cargo plugin to build dylib for steel
Documentation
use std::{error::Error, path::PathBuf, process::Command};

use cargo_metadata::{Message, MetadataCommand, Package, TargetKind};
use std::process::Stdio;

fn package_contains_dependency_on_steel(packages: &[Package]) -> Option<&Package> {
    packages.iter().find(|x| x.name.as_ref() == "steel-core")
}

/*
TODO:
- Desired output directory / do not copy to native automatically
- Specify target architecture
*/

pub fn steel_home() -> Option<PathBuf> {
    std::env::var("STEEL_HOME")
        .ok()
        .map(PathBuf::from)
        .or_else(|| {
            let home = env_home::env_home_dir().map(|x| x.join(".steel"));

            if let Some(home) = home {
                if home.exists() {
                    return Some(home);
                }

                #[cfg(target_os = "windows")]
                {
                    if let Err(e) = std::fs::create_dir(&home) {
                        eprintln!("Unable to create steel home directory {:?}: {}", home, e)
                    }

                    return Some(home);
                }
            }
            #[cfg(not(target_os = "windows"))]
            {
                let bd = xdg::BaseDirectories::new();
                let home = bd.data_home;

                home.map(|mut x: PathBuf| {
                    x.push("steel");

                    // Just go ahead and initialize the directory, even though
                    // this is probably not the best place to do this. This almost
                    // assuredly could be lifted out of this check since failing here
                    // could cause some annoyance.
                    if !x.exists() {
                        if let Err(e) = std::fs::create_dir(&x) {
                            eprintln!("Unable to create steel home directory {:?}: {}", x, e)
                        }
                    }

                    x
                })
            }

            #[cfg(target_os = "windows")]
            None
        })
}

pub fn run(args: Vec<String>, env_vars: Vec<(String, String)>) -> Result<bool, Box<dyn Error>> {
    let mut steel_home = steel_home().expect("Unable to find STEEL_HOME");

    steel_home.push("native");

    if !steel_home.exists() {
        std::fs::create_dir(&steel_home)?;
    }

    // --manifest-path
    let mut metadata_command = MetadataCommand::new();

    for pair in args.chunks(2) {
        match &pair {
            &[arg, path] if arg == "--manifest-path" => {
                metadata_command.manifest_path(path);
            }
            _ => {}
        }
    }

    let metadata = metadata_command.exec()?;

    let package = match metadata.root_package() {
        Some(p) => p,
        None => return Err("cargo steel-lib must be run from within a crate".into()),
    };

    println!("Attempting to install: {:#?}", package.name);

    if package_contains_dependency_on_steel(&metadata.packages).is_none() {
        return Err(
            "Cannot install package as a steel dylib - does not contain a dependency on steel!"
                .into(),
        );
    }

    let mut command = Command::new("cargo")
        .args([
            "build",
            "--release",
            "--message-format=json-render-diagnostics",
        ])
        .args(args)
        .envs(env_vars)
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let reader = std::io::BufReader::new(command.stdout.take().unwrap());

    let artifacts = cargo_metadata::Message::parse_stream(reader).filter_map(|x| {
        if let Ok(Message::CompilerArtifact(artifact)) = x {
            Some(artifact)
        } else {
            None
        }
    });

    for last in artifacts {
        if last.target.kind.contains(&TargetKind::CDyLib) {
            for file in last.filenames {
                if file.extension() == Some(std::env::consts::DLL_EXTENSION) {
                    println!("Found a cdylib!");
                    let prefix = if cfg!(target_os = "windows") {
                        "lib"
                    } else {
                        ""
                    };
                    // Replace all hyphens with lowercase in order to match the naming
                    // conventions
                    let filename =
                        format!("{}{}", prefix, file.file_name().unwrap()).replace("-", "_");

                    // If on windows, prefix with "lib" to match unix
                    steel_home.push(filename);

                    println!("Copying {} to {}", file, &steel_home.to_str().unwrap());

                    if steel_home.exists() {
                        std::fs::remove_file(&steel_home)
                            .expect("Unable to delete the existing dylib");
                    }

                    std::fs::copy(file, &steel_home).unwrap();

                    steel_home.pop();
                    break;
                }
            }
        }
    }

    println!("Done!");

    let exit_status = command.wait().expect("Couldn't get cargo's exit status");

    Ok(exit_status.success())
}