use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use serde::Deserialize;
#[derive(Deserialize)]
pub(super) struct Artifact {
target: Target,
executable: Option<PathBuf>,
filenames: Vec<PathBuf>,
}
impl Artifact {
pub(super) fn final_outputs(&self) -> Vec<PathBuf> {
if let Some(executable) = &self.executable {
return vec![executable.clone()];
}
if !self.is_dynamic_library() {
return Vec::new();
}
self.filenames
.iter()
.filter(|filename| is_final_library(filename))
.cloned()
.collect()
}
fn is_dynamic_library(&self) -> bool {
self.target
.crate_types
.iter()
.any(|ty| ty == "cdylib" || ty == "dylib")
}
}
#[derive(Deserialize)]
struct Target {
crate_types: Vec<String>,
}
fn is_build_script_output(path: &Path) -> bool {
path.ancestors()
.any(|a| a.file_name() == Some(OsStr::new("out")))
&& path
.ancestors()
.any(|a| a.file_name() == Some(OsStr::new("build")))
}
fn is_final_library(path: &Path) -> bool {
matches!(
path.extension().and_then(|ext| ext.to_str()),
Some("wasm" | "so" | "dylib" | "dll")
) && path
.parent()
.and_then(Path::file_name)
.is_none_or(|dir| dir != "deps")
&& !is_build_script_output(path)
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
fn artifact(crate_types: &[&str], filenames: &[&str]) -> Artifact {
serde_json::from_value(json!({
"target": { "crate_types": crate_types },
"executable": null,
"filenames": filenames,
}))
.unwrap()
}
#[test]
fn cdylib_output_is_picked_over_its_rlib_companion() {
let artifact = artifact(
&["cdylib", "rlib"],
&["/target/wasm/app.wasm", "/target/wasm/libapp.rlib"],
);
assert_eq!(
artifact.final_outputs(),
vec![PathBuf::from("/target/wasm/app.wasm")]
);
}
#[test]
fn dependency_cdylib_outputs_are_excluded() {
let artifact = artifact(&["cdylib", "rlib"], &["/target/deps/libdep.dylib"]);
assert!(artifact.final_outputs().is_empty());
}
#[test]
fn proc_macro_libraries_are_not_scanned() {
let artifact = artifact(&["proc-macro"], &["/target/libmacros.dylib"]);
assert!(artifact.final_outputs().is_empty());
}
#[test]
fn plain_library_dependencies_are_not_scanned() {
let artifact = artifact(&["lib"], &["/target/deps/libdep.rlib"]);
assert!(artifact.final_outputs().is_empty());
}
#[test]
fn recognizes_final_libraries() {
assert!(is_final_library(Path::new(
"/t/wasm32-unknown-unknown/debug/app.wasm"
)));
assert!(is_final_library(Path::new("/t/debug/libapp.so")));
assert!(is_final_library(Path::new("/t/debug/libapp.dylib")));
assert!(is_final_library(Path::new("/t/debug/app.dll")));
assert!(!is_final_library(Path::new("/t/debug/deps/app.wasm")));
assert!(!is_final_library(Path::new("/t/debug/deps/libapp.dylib")));
assert!(!is_final_library(Path::new("/t/debug/libapp.rlib")));
assert!(!is_final_library(Path::new("/t/debug/app.dll.lib")));
assert!(!is_final_library(Path::new("/t/debug/app.d")));
}
#[test]
fn build_script_library_outputs_are_excluded() {
assert!(!is_final_library(Path::new(
"/t/debug/build/foo/9251c15296da3db3/out/libfoo.so"
)));
assert!(!is_final_library(Path::new(
"/t/debug/build/bar/a3538d1a8934c29c/out/libbar.dylib"
)));
assert!(!is_final_library(Path::new(
"/t/debug/build/foo/extra/nested/path/out/libfoo.so"
)));
}
#[test]
fn final_library_paths_are_not_excluded() {
assert!(is_final_library(Path::new("/t/debug/libfoo.so")));
assert!(is_final_library(Path::new("/t/debug/libfoo.dylib")));
assert!(is_final_library(Path::new("/t/debug/foo.dll")));
assert!(is_final_library(Path::new("/t/debug/foo.wasm")));
}
}