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("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() {
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
}
}