Reapply "Support plugin and archive symlinks"

This reverts commit da0e35bb2b.

The minimum version of libloadorder has been increased because 18.5.0
included some changes to support symlinked plugins, without which
symlink support is incomplete.
This commit is contained in:
Oliver Hamlet
2025-09-30 19:01:08 +01:00
parent 36fc177e76
commit bc89403134
3 changed files with 71 additions and 5 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ license.workspace = true
[dependencies]
crc32fast = "1"
esplugin = "6.1"
libloadorder = "18.4"
libloadorder = "18.5"
log = { version = "0.4.6", features = ["std"] }
loot-condition-interpreter = "5"
petgraph = ">= 0.6, < 0.9"
+70 -3
View File
@@ -625,14 +625,15 @@ fn find_archives(
data_path: &Path,
) -> std::io::Result<Vec<PathBuf>> {
let extension = archive_file_extension(game_type);
let allow_symlinks = allow_archive_symlinks(game_type);
let mut archive_paths = Vec::new();
for path in additional_data_paths {
let paths = find_archives_in_path(path, extension)?;
let paths = find_archives_in_path(path, extension, allow_symlinks)?;
archive_paths.extend(paths);
}
let paths = find_archives_in_path(data_path, extension)?;
let paths = find_archives_in_path(data_path, extension, allow_symlinks)?;
archive_paths.extend(paths);
Ok(archive_paths)
@@ -645,18 +646,29 @@ fn archive_file_extension(game_type: GameType) -> &'static str {
}
}
fn allow_archive_symlinks(game_type: GameType) -> bool {
// Assuming support for archive symlinks matches support for plugin symlinks.
!cfg!(windows) || matches!(game_type, GameType::OblivionRemastered | GameType::OpenMW)
}
fn find_archives_in_path(
parent_path: &Path,
archive_file_extension: &str,
allow_symlinks: bool,
) -> std::io::Result<Vec<PathBuf>> {
if !parent_path.exists() {
return Ok(Vec::new());
}
#[expect(
clippy::filetype_is_file,
reason = "Only files are supported except in specific cases"
)]
let paths = std::fs::read_dir(parent_path)?
.filter_map(Result::ok)
.filter(|e| {
e.file_type().map(|f| f.is_file()).unwrap_or(false)
e.file_type()
.is_ok_and(|f| f.is_file() || (allow_symlinks && f.is_symlink()))
&& iends_with_ascii(&e.file_name().to_string_lossy(), archive_file_extension)
})
.map(|e| e.path())
@@ -2051,6 +2063,61 @@ mod tests {
}
}
mod find_archives {
use tempfile::tempdir;
use super::*;
#[cfg(windows)]
pub(crate) fn symlink_file(original: &Path, link: &Path) {
std::os::windows::fs::symlink_file(original, link).unwrap();
}
#[cfg(unix)]
pub(crate) fn symlink_file(original: &Path, link: &Path) {
std::os::unix::fs::symlink(original, link).unwrap();
}
#[cfg(unix)]
#[parameterized_test(ALL_GAME_TYPES)]
fn should_find_symlinked_archives_on_linux(game_type: GameType) {
test_find_symlinked_archives(game_type, true);
}
#[cfg(windows)]
#[parameterized_test(ALL_GAME_TYPES)]
fn should_find_symlinked_archives_for_only_openmw_and_oblivion_remastered_on_windows(
game_type: GameType,
) {
test_find_symlinked_archives(
game_type,
matches!(game_type, GameType::OpenMW | GameType::OblivionRemastered),
);
}
fn test_find_symlinked_archives(game_type: GameType, should_include_symlinks: bool) {
let tmp_dir = tempdir().unwrap();
let extension = archive_file_extension(game_type);
let archive_path = tmp_dir.path().join(format!("archive{extension}"));
let symlink_path = tmp_dir.path().join(format!("archive.symlink{extension}"));
std::fs::File::create(&archive_path).unwrap();
symlink_file(&archive_path, &symlink_path);
let archives = find_archives(game_type, &[], tmp_dir.path()).unwrap();
assert!(archives.contains(&archive_path));
if should_include_symlinks {
assert!(archives.contains(&symlink_path));
} else {
assert!(!archives.contains(&symlink_path));
}
}
}
#[test]
fn to_plugin_sorting_data_should_filter_out_files_with_false_constraints() {
let game_type = GameType::Oblivion;
-1
View File
@@ -1,6 +1,5 @@
// Allow some lints that are denied at the workspace level.
#![allow(
clippy::filetype_is_file,
clippy::must_use_candidate,
clippy::missing_errors_doc,
clippy::wildcard_enum_match_arm