use std::path::Path;
use crate::cli::{CliError, Result};
use crate::target::JavaHostTarget;
pub(crate) fn remove_file_if_exists(path: &Path) -> Result<()> {
match std::fs::remove_file(path) {
Ok(()) => Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(source) => Err(CliError::WriteFailed {
path: path.to_path_buf(),
source,
}),
}
}
pub(crate) fn remove_stale_flat_jvm_outputs_if_current_host_unrequested(
java_output: &Path,
current_host: Option<JavaHostTarget>,
requested_host_targets: &[JavaHostTarget],
artifact_name: &str,
) -> Result<()> {
let Some(current_host) = current_host else {
return Ok(());
};
if requested_host_targets.contains(¤t_host) {
return Ok(());
}
remove_file_if_exists(&java_output.join(current_host.jni_library_filename(artifact_name)))?;
remove_file_if_exists(&java_output.join(current_host.shared_library_filename(artifact_name)))?;
Ok(())
}
pub(crate) fn remove_stale_requested_jvm_shared_library_copies_after_success(
java_output: &Path,
packaged_outputs: &[super::link::JvmPackagedNativeOutput],
artifact_name: &str,
) -> Result<()> {
let current_host = JavaHostTarget::current();
for packaged_output in packaged_outputs {
if packaged_output.has_shared_library_copy {
continue;
}
let stale_shared_library_name = packaged_output
.host_target
.shared_library_filename(artifact_name);
let structured_copy = java_output
.join("native")
.join(packaged_output.host_target.canonical_name())
.join(&stale_shared_library_name);
remove_file_if_exists(&structured_copy)?;
if current_host == Some(packaged_output.host_target) {
remove_file_if_exists(&java_output.join(stale_shared_library_name))?;
}
}
Ok(())
}
pub(crate) fn remove_stale_structured_jvm_outputs(
native_output_root: &Path,
requested_host_targets: &[JavaHostTarget],
) -> Result<()> {
let requested_host_directories = requested_host_targets
.iter()
.map(|host_target| host_target.canonical_name())
.collect::<std::collections::HashSet<_>>();
for host_target in [
JavaHostTarget::DarwinArm64,
JavaHostTarget::DarwinX86_64,
JavaHostTarget::LinuxX86_64,
JavaHostTarget::LinuxAarch64,
JavaHostTarget::WindowsX86_64,
] {
if requested_host_directories.contains(host_target.canonical_name()) {
continue;
}
remove_directory_if_exists(&native_output_root.join(host_target.canonical_name()))?;
}
Ok(())
}
fn remove_directory_if_exists(path: &Path) -> Result<()> {
match std::fs::remove_dir_all(path) {
Ok(()) => Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(source) => Err(CliError::WriteFailed {
path: path.to_path_buf(),
source,
}),
}
}
#[cfg(test)]
mod tests {
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};
use super::{
remove_file_if_exists, remove_stale_flat_jvm_outputs_if_current_host_unrequested,
remove_stale_requested_jvm_shared_library_copies_after_success,
remove_stale_structured_jvm_outputs,
};
use crate::pack::java::link::JvmPackagedNativeOutput;
use crate::target::JavaHostTarget;
fn temporary_directory(prefix: &str) -> std::path::PathBuf {
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time went backwards")
.as_nanos();
std::env::temp_dir().join(format!("{prefix}-{unique}"))
}
#[test]
fn remove_file_if_exists_deletes_existing_file() {
let temp_root = temporary_directory("boltffi-remove-file-test");
fs::create_dir_all(&temp_root).expect("create temp dir");
let file_path = temp_root.join("stale.dylib");
fs::write(&file_path, []).expect("write temp file");
remove_file_if_exists(&file_path).expect("remove stale file");
assert!(!file_path.exists());
fs::remove_dir_all(&temp_root).expect("cleanup temp dir");
}
#[test]
fn removes_stale_requested_shared_library_copies_only_after_success() {
let current_host = JavaHostTarget::current();
let requested_host = current_host.unwrap_or(JavaHostTarget::DarwinArm64);
let temp_root = temporary_directory("boltffi-java-requested-shared-cleanup");
let native_output = temp_root
.join("native")
.join(requested_host.canonical_name());
fs::create_dir_all(&native_output).expect("create structured output dir");
let structured_shared = native_output.join(requested_host.shared_library_filename("demo"));
fs::write(&structured_shared, []).expect("write structured shared copy");
let flat_shared = temp_root.join(requested_host.shared_library_filename("demo"));
if current_host == Some(requested_host) {
fs::write(&flat_shared, []).expect("write flat shared copy");
}
remove_stale_requested_jvm_shared_library_copies_after_success(
&temp_root,
&[JvmPackagedNativeOutput {
host_target: requested_host,
has_shared_library_copy: false,
jni_library_path: temp_root.join(requested_host.jni_library_filename("demo")),
shared_library_path: None,
debug_info_sidecars: Vec::new(),
}],
"demo",
)
.expect("cleanup stale requested shared copies");
assert!(!structured_shared.exists());
if current_host == Some(requested_host) {
assert!(!flat_shared.exists());
}
fs::remove_dir_all(&temp_root).expect("cleanup temp dir");
}
#[test]
fn removes_stale_flat_jvm_outputs_when_current_host_is_not_requested() {
let current_host = JavaHostTarget::current().expect("supported test host");
let requested_other_host = [
JavaHostTarget::DarwinArm64,
JavaHostTarget::DarwinX86_64,
JavaHostTarget::LinuxX86_64,
JavaHostTarget::LinuxAarch64,
JavaHostTarget::WindowsX86_64,
]
.into_iter()
.find(|target| *target != current_host)
.expect("alternate host");
let temp_root = temporary_directory("boltffi-java-flat-cleanup");
fs::create_dir_all(&temp_root).expect("create temp dir");
let jni_copy = temp_root.join(current_host.jni_library_filename("demo"));
let shared_copy = temp_root.join(current_host.shared_library_filename("demo"));
fs::write(&jni_copy, []).expect("write stale jni");
fs::write(&shared_copy, []).expect("write stale shared");
remove_stale_flat_jvm_outputs_if_current_host_unrequested(
&temp_root,
Some(current_host),
&[requested_other_host],
"demo",
)
.expect("cleanup stale outputs");
assert!(!jni_copy.exists());
assert!(!shared_copy.exists());
fs::remove_dir_all(&temp_root).expect("cleanup temp dir");
}
#[test]
fn removes_stale_structured_jvm_outputs_when_host_matrix_is_narrowed() {
let temp_root = temporary_directory("boltffi-java-structured-cleanup");
let darwin_dir = temp_root.join(JavaHostTarget::DarwinArm64.canonical_name());
let linux_dir = temp_root.join(JavaHostTarget::LinuxX86_64.canonical_name());
fs::create_dir_all(&darwin_dir).expect("create darwin dir");
fs::create_dir_all(&linux_dir).expect("create linux dir");
remove_stale_structured_jvm_outputs(&temp_root, &[JavaHostTarget::DarwinArm64])
.expect("cleanup stale structured outputs");
assert!(darwin_dir.exists());
assert!(!linux_dir.exists());
fs::remove_dir_all(&temp_root).expect("cleanup temp dir");
}
#[test]
fn preserves_requested_structured_jvm_outputs() {
let temp_root = temporary_directory("boltffi-java-structured-preserve");
let darwin_dir = temp_root.join(JavaHostTarget::DarwinArm64.canonical_name());
let linux_dir = temp_root.join(JavaHostTarget::LinuxX86_64.canonical_name());
fs::create_dir_all(&darwin_dir).expect("create darwin dir");
fs::create_dir_all(&linux_dir).expect("create linux dir");
remove_stale_structured_jvm_outputs(
&temp_root,
&[JavaHostTarget::DarwinArm64, JavaHostTarget::LinuxX86_64],
)
.expect("preserve structured outputs");
assert!(darwin_dir.exists());
assert!(linux_dir.exists());
fs::remove_dir_all(&temp_root).expect("cleanup temp dir");
}
#[test]
fn preserves_flat_jvm_outputs_when_current_host_is_requested() {
let current_host = JavaHostTarget::current().expect("supported test host");
let temp_root = temporary_directory("boltffi-java-flat-preserve");
fs::create_dir_all(&temp_root).expect("create temp dir");
let jni_copy = temp_root.join(current_host.jni_library_filename("demo"));
let shared_copy = temp_root.join(current_host.shared_library_filename("demo"));
fs::write(&jni_copy, []).expect("write current jni");
fs::write(&shared_copy, []).expect("write current shared");
remove_stale_flat_jvm_outputs_if_current_host_unrequested(
&temp_root,
Some(current_host),
&[current_host],
"demo",
)
.expect("preserve current-host outputs");
assert!(jni_copy.exists());
assert!(shared_copy.exists());
fs::remove_dir_all(&temp_root).expect("cleanup temp dir");
}
}