use std::path::Path;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum HostPlatform {
Windows,
MacOs,
Linux,
}
impl HostPlatform {
fn host() -> Self {
if cfg!(target_os = "windows") {
HostPlatform::Windows
} else if cfg!(target_os = "macos") {
HostPlatform::MacOs
} else {
HostPlatform::Linux
}
}
}
const MACOS_DYNAMIC_EXTENSION: &str = "dylib";
const LINUX_DYNAMIC_EXTENSION: &str = "so";
const UNIX_STATIC_EXTENSION: &str = "a";
const WINDOWS_DYNAMIC_EXTENSION: &str = "dll";
const WINDOWS_IMPORT_EXTENSION: &str = "lib";
const WINDOWS_STATIC_EXTENSION: &str = "a";
fn linkable_library_names_for(platform: HostPlatform, lib_name: &str) -> Vec<String> {
match platform {
HostPlatform::Windows => vec![
format!("{lib_name}.{WINDOWS_DYNAMIC_EXTENSION}"),
format!("{lib_name}.{WINDOWS_IMPORT_EXTENSION}"),
format!("lib{lib_name}.{WINDOWS_STATIC_EXTENSION}"),
],
HostPlatform::MacOs => vec![
format!("lib{lib_name}.{MACOS_DYNAMIC_EXTENSION}"),
format!("lib{lib_name}.{UNIX_STATIC_EXTENSION}"),
],
HostPlatform::Linux => vec![
format!("lib{lib_name}.{LINUX_DYNAMIC_EXTENSION}"),
format!("lib{lib_name}.{UNIX_STATIC_EXTENSION}"),
],
}
}
pub(super) fn linkable_library_names(lib_name: &str) -> Vec<String> {
linkable_library_names_for(HostPlatform::host(), lib_name)
}
pub(super) fn directory_has_ffi_library(directory: &Path, lib_name: &str) -> bool {
linkable_library_names(lib_name)
.iter()
.any(|name| directory.join(name).is_file())
}
#[cfg(test)]
mod tests {
use super::*;
fn expected_names(platform: HostPlatform, lib_name: &str) -> Vec<String> {
match platform {
HostPlatform::Windows => vec![
format!("{lib_name}.dll"),
format!("{lib_name}.lib"),
format!("lib{lib_name}.a"),
],
HostPlatform::MacOs => vec![format!("lib{lib_name}.dylib"), format!("lib{lib_name}.a")],
HostPlatform::Linux => vec![format!("lib{lib_name}.so"), format!("lib{lib_name}.a")],
}
}
#[test]
fn windows_candidates_match_the_toolchain_exactly() {
assert_eq!(
linkable_library_names_for(HostPlatform::Windows, "sample_ffi"),
expected_names(HostPlatform::Windows, "sample_ffi")
);
}
#[test]
fn macos_candidates_match_the_toolchain_exactly() {
assert_eq!(
linkable_library_names_for(HostPlatform::MacOs, "sample_ffi"),
expected_names(HostPlatform::MacOs, "sample_ffi")
);
}
#[test]
fn linux_candidates_match_the_toolchain_exactly() {
assert_eq!(
linkable_library_names_for(HostPlatform::Linux, "sample_ffi"),
expected_names(HostPlatform::Linux, "sample_ffi")
);
}
#[test]
fn linux_never_offers_the_macos_dynamic_extension() {
let names = linkable_library_names_for(HostPlatform::Linux, "sample_ffi");
assert!(!names.iter().any(|name| name.ends_with(".dylib")));
assert_eq!(names.first(), Some(&"libsample_ffi.so".to_owned()));
}
#[test]
fn macos_never_offers_the_linux_dynamic_extension() {
let names = linkable_library_names_for(HostPlatform::MacOs, "sample_ffi");
assert!(!names.iter().any(|name| name.ends_with(".so")));
assert_eq!(names.first(), Some(&"libsample_ffi.dylib".to_owned()));
}
#[test]
fn an_ambiguous_directory_resolves_per_platform_and_not_by_iteration_order() {
let directory = tempfile::tempdir().expect("scratch directory");
for extension in ["dylib", "so", "dll", "lib", "a"] {
std::fs::write(directory.path().join(format!("libsample_ffi.{extension}")), "fake").unwrap();
}
std::fs::write(directory.path().join("sample_ffi.dll"), "fake").unwrap();
std::fs::write(directory.path().join("sample_ffi.lib"), "fake").unwrap();
for platform in [HostPlatform::Windows, HostPlatform::MacOs, HostPlatform::Linux] {
let names = linkable_library_names_for(platform, "sample_ffi");
assert_eq!(
names,
expected_names(platform, "sample_ffi"),
"{platform:?} must resolve its own fixed candidate list regardless of what else is on disk"
);
assert!(
names.iter().any(|name| directory.path().join(name).is_file()),
"{platform:?} must still find its own artifact in the mixed directory"
);
}
}
#[test]
fn directory_has_ffi_library_never_credits_a_deps_only_copy() {
let directory = tempfile::tempdir().expect("scratch directory");
std::fs::create_dir_all(directory.path().join("deps")).unwrap();
for name in linkable_library_names_for(HostPlatform::host(), "sample_ffi") {
std::fs::write(directory.path().join("deps").join(name), "fake").unwrap();
}
assert!(!directory_has_ffi_library(directory.path(), "sample_ffi"));
}
}