dodo-bindgen-cli 2.0.0

A CLI tool for generating TypeScript bindings for libraries built with the `dodo_bindgen` crate
use core::panic;
use std::path::PathBuf;
use std::process::{Command, Stdio};

use cargo_metadata::{Message, MetadataCommand};

#[derive(Clone, Debug)]
pub struct MetaData {
    pub pkg_name: String,
    pub lib_name: String,
    pub pkg_path: PathBuf,
}

const DYLIB_EXTENSIONS: &[&str] = &["so", "dylib", "dll"];

pub struct Cargo;

impl Cargo {
    pub fn get_metadata() -> MetaData {
        let cmd = MetadataCommand::new();

        let metadata = cmd.exec().expect("failed to execute `cargo metadata`");
        let root_pkg = metadata
            .root_package()
            .expect("failed to retrieve root package name");

        let dylib_target = root_pkg.targets.iter().find(|target| {
            if let Some(kind) = target.kind.first()
                && kind.as_str() == "cdylib"
                && let Some(crate_type) = target.crate_types.first()
                && crate_type.as_str() == "cdylib"
            {
                return true;
            }
            false
        });

        let pkg_name;
        let lib_name;

        if let Some(dylib_target) = dylib_target {
            pkg_name = root_pkg.name.clone();
            lib_name = dylib_target.name.clone();
        } else {
            panic!(
                "no `cdylib` library target found in package `{}`",
                root_pkg.name
            );
        }

        let mut pkg_path = PathBuf::from(root_pkg.manifest_path.clone());
        pkg_path.pop();

        MetaData {
            pkg_name,
            lib_name,
            pkg_path,
        }
    }

    pub fn host_target() -> String {
        let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
        let output = Command::new(&rustc)
            .arg("-vV")
            .output()
            .unwrap_or_else(|err| panic!("failed to run `{rustc} -vV`: {err}"));
        String::from_utf8_lossy(&output.stdout)
            .lines()
            .find_map(|line| line.strip_prefix("host: "))
            .map(str::to_string)
            .unwrap_or_else(|| panic!("`{rustc} -vV` did not report a host target"))
    }

    pub fn build(pkg_name: &str, profile: &str, target: Option<&str>) -> PathBuf {
        let mut cmd = Command::new("cargo");
        cmd
            //.arg("+nightly")
            .arg("build")
            .arg("--package")
            .arg(pkg_name)
            .arg("--lib")
            .arg("--message-format=json")
            .arg("--profile")
            .arg(profile)
            .stderr(Stdio::inherit())
            .stdout(Stdio::piped());

        if let Some(target) = target {
            cmd.arg("--target").arg(target);
        }

        let output = cmd.output().expect("failed to start `cargo build` process");

        let dylib_path;

        if !output.status.success() {
            panic!(
                "failed to execute `cargo`: process exited with {}\nfull command: {:?}",
                output.status, cmd
            )
        } else {
            let cargo_out = std::io::BufReader::new(output.stdout.as_slice());
            let mut artifact_paths = Vec::new();

            for msg in Message::parse_stream(cargo_out) {
                if let Err(err) = msg {
                    panic!("failed to parse cargo output: {:?}", err);
                }

                if let Message::CompilerArtifact(artifact) = msg.unwrap() {
                    // check to ensure the library is set to type `cdylib`
                    if artifact.target.kind.contains(&"cdylib".to_string()) {
                        artifact_paths.extend(
                            artifact
                                .filenames
                                .iter()
                                .filter(|path| {
                                    path.extension().is_some_and(|extension| {
                                        DYLIB_EXTENSIONS.contains(&extension)
                                    })
                                })
                                .map(|path| PathBuf::from(path.as_std_path())),
                        );
                    }
                }
            }

            if let Some(path) = artifact_paths.pop() {
                dylib_path = path;
            } else {
                panic!(
                    "failed to retrieve cargo build artifacts\nis your library set to the `cdylib` type?"
                )
            }
        }

        dylib_path
    }
}