From ec50a380b8bd9805f30e69ca9ac48e0e575417a2 Mon Sep 17 00:00:00 2001 From: Warren Hood Date: Sat, 17 Aug 2024 22:06:31 +0200 Subject: [PATCH] Improved mod resolution --- src/main.rs | 7 +++- src/mod_meta.rs | 2 +- src/modpack.rs | 2 +- src/providers/mod.rs | 8 ++-- src/providers/modrinth.rs | 73 ++++++++++++++---------------------- src/resolver.rs | 79 ++++++++++++++++++++++++++++++--------- 6 files changed, 103 insertions(+), 68 deletions(-) diff --git a/src/main.rs b/src/main.rs index dfb24fa..076d18d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,6 +98,8 @@ async fn main() -> Result<(), Box> { mc_modpack_meta = mc_modpack_meta.provider(provider); } mc_modpack_meta.init_project(&dir)?; + let modpack_lock = resolver::PinnedPackMeta::load_from_directory(&dir).await?; + modpack_lock.save_to_dir(&dir)?; } Commands::New { name, @@ -118,6 +120,9 @@ async fn main() -> Result<(), Box> { mc_modpack_meta = mc_modpack_meta.provider(provider); } mc_modpack_meta.init_project(&dir)?; + + let modpack_lock = resolver::PinnedPackMeta::load_from_directory(&dir).await?; + modpack_lock.save_to_dir(&dir)?; } Commands::Add { name, @@ -147,7 +152,7 @@ async fn main() -> Result<(), Box> { match resolver::PinnedPackMeta::load_from_current_directory().await { Ok(mut modpack_lock) => { - let pin_result = modpack_lock.pin_mod(&mod_meta, &modpack_meta).await; + let pin_result = modpack_lock.pin_mod_and_deps(&mod_meta, &modpack_meta).await; if let Err(e) = pin_result { revert_modpack_meta(e); } diff --git a/src/mod_meta.rs b/src/mod_meta.rs index 621cc6c..46f2f40 100644 --- a/src/mod_meta.rs +++ b/src/mod_meta.rs @@ -25,7 +25,7 @@ impl std::str::FromStr for ModProvider { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct ModMeta { pub name: String, pub version: String, diff --git a/src/modpack.rs b/src/modpack.rs index 55ce528..7618a3c 100644 --- a/src/modpack.rs +++ b/src/modpack.rs @@ -81,7 +81,7 @@ impl ModpackMeta { pub fn add_mod(mut self, mod_meta: &ModMeta) -> Self { if let Some(old_mod_meta) = self.mods.get(&mod_meta.name) { - println!("Updating {} version {}->{}", mod_meta.name, old_mod_meta.version, mod_meta.version); + println!("Updating {} version constraints: {} -> {}", mod_meta.name, old_mod_meta.version, mod_meta.version); } else { println!( diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 61d90e5..01ad441 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -5,18 +5,18 @@ use crate::mod_meta::ModMeta; pub mod modrinth; pub mod raw; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] enum FileSource { Download { url: String, sha1: String, sha512: String}, Local { path: PathBuf, sha1: String, sha512: String }, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct PinnedMod { /// Source of the files for the mod source: Vec, /// Version of mod - version: semver::Version, + pub version: semver::Version, /// Pinned dependencies of a pinned mod - deps: Option> + pub deps: Option> } \ No newline at end of file diff --git a/src/providers/modrinth.rs b/src/providers/modrinth.rs index 5e0b7b1..3d71b81 100644 --- a/src/providers/modrinth.rs +++ b/src/providers/modrinth.rs @@ -58,55 +58,40 @@ impl Modrinth { &self, mod_meta: &ModMeta, pack_meta: &ModpackMeta, - ) -> Result, Box> { + ) -> Result> { let mut versions = self.get_project_versions(&mod_meta.name, pack_meta).await?; versions.sort_by_key(|v| v.version_number.clone()); versions.reverse(); - if mod_meta.version == "*" { - return Ok(versions - .into_iter() - .map(|v| PinnedMod { - source: v - .files - .into_iter() - .map(|f| FileSource::Download { - url: f.url, - sha1: f.hashes.sha1, - sha512: f.hashes.sha512, - }) - .collect(), - version: v.version_number, - deps: None, // TODO: Implement automagic transitive dep installation and pinning + let package = if mod_meta.version == "*" { + versions + .last() + .ok_or(format!("Cannot find package {}", mod_meta.name))? + } else { + let expected_version = semver::Version::parse(&mod_meta.version)?; + versions + .iter() + .filter(|v| v.version_number == expected_version) + .nth(0) + .ok_or(format!( + "Cannot find package {}@{}", + mod_meta.name, mod_meta.version + ))? + }; + + Ok(PinnedMod { + source: package + .files + .iter() + .map(|f| FileSource::Download { + url: f.url.clone(), + sha1: f.hashes.sha1.clone(), + sha512: f.hashes.sha512.clone(), }) - .collect()); - } - - // TODO: Implement more general version constraints - let expected_version = semver::Version::parse(&mod_meta.version)?; - for v in versions.into_iter() { - if v.version_number == expected_version { - return Ok(vec![PinnedMod { - source: v - .files - .into_iter() - .map(|f| FileSource::Download { - url: f.url, - sha1: f.hashes.sha1, - sha512: f.hashes.sha512, - }) - .collect(), - version: v.version_number, - deps: None, - }]); - } - } - - Err(format!( - "Couldn't find a version for mod '{}' that satisfies the version constraint `{}`", - mod_meta.name, mod_meta.version - ) - .into()) + .collect(), + version: package.version_number.clone(), + deps: None, // TODO: Get deps + }) } async fn get_project_versions( diff --git a/src/resolver.rs b/src/resolver.rs index 5717815..ea63005 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; use std::{ - borrow::Borrow, collections::{HashMap, HashSet}, error::Error, path::PathBuf, @@ -29,11 +28,46 @@ impl PinnedPackMeta { } } - pub async fn pin_mod( + pub async fn pin_mod_and_deps( &mut self, mod_metadata: &ModMeta, pack_metadata: &ModpackMeta, ) -> Result<(), Box> { + if let Some(mod_meta) = self.mods.get(&mod_metadata.name) { + if mod_metadata.version != "*" + && semver::Version::parse(&mod_metadata.version)? == mod_meta.version + { + // Skip already pinned mods + // TODO: Replace * with the current mod version in the modpack meta so this doesn't get called twice for the first mod created + return Ok(()); + } + } + let mut deps = + HashSet::from_iter(self.pin_mod(mod_metadata, pack_metadata).await?.into_iter()); + + while !deps.is_empty() { + let mut next_deps = HashSet::new(); + for dep in deps.iter() { + println!( + "Adding mod {}@{} (dependency of {}@{})", + dep.name, dep.version, mod_metadata.name, mod_metadata.version + ); + next_deps.extend(self.pin_mod(dep, &pack_metadata).await?); + } + deps = next_deps; + } + + Ok(()) + } + + /// Pin a mod version + /// + /// A list of dependencies to pin is included + pub async fn pin_mod( + &mut self, + mod_metadata: &ModMeta, + pack_metadata: &ModpackMeta, + ) -> Result, Box> { let mod_providers = if let Some(mod_providers) = &mod_metadata.providers { mod_providers } else { @@ -52,28 +86,35 @@ impl PinnedPackMeta { match mod_provider { crate::mod_meta::ModProvider::CurseForge => unimplemented!(), crate::mod_meta::ModProvider::Modrinth => { - let pinned_mods = self.modrinth.resolve(&mod_metadata, pack_metadata).await; - if let Ok(pinned_mods) = pinned_mods { - pinned_mods.into_iter().for_each(|m| { - self.mods.insert(mod_metadata.name.clone(), m); - }); - } else if let Err(e) = pinned_mods { - return Err(format!( - "Failed to resolve mod '{}' (provider={:#?}) with constraint {}: {}", - mod_metadata.name, mod_provider, mod_metadata.version, e - ) - .into()); + let pinned_mod = self.modrinth.resolve(&mod_metadata, pack_metadata).await; + if let Ok(pinned_mod) = pinned_mod { + self.mods + .insert(mod_metadata.name.clone(), pinned_mod.clone()); + println!("Pinned {}@{}", mod_metadata.name, pinned_mod.version); + if let Some(deps) = &pinned_mod.deps { + return Ok(deps + .iter() + .filter(|d| !self.mods.contains_key(&d.name)) + .cloned() + .collect()); + } + return Ok(vec![]); } } crate::mod_meta::ModProvider::Raw => unimplemented!(), }; } - Ok(()) + + Err(format!( + "Failed to pin mod '{}' (providers={:#?}) with constraint {} and all its deps", + mod_metadata.name, mod_metadata.providers, mod_metadata.version + ) + .into()) } pub async fn init(&mut self, modpack_meta: &ModpackMeta) -> Result<(), Box> { for mod_meta in modpack_meta.iter_mods() { - self.pin_mod(mod_meta, modpack_meta).await?; + self.pin_mod_and_deps(mod_meta, modpack_meta).await?; } Ok(()) } @@ -88,8 +129,12 @@ impl PinnedPackMeta { } pub fn save_current_dir_lock(&self) -> Result<(), Box> { - let modpack_lock_file_path = - std::env::current_dir()?.join(PathBuf::from(MODPACK_LOCK_FILENAME)); + self.save_to_dir(&std::env::current_dir()?)?; + Ok(()) + } + + pub fn save_to_dir(&self, dir: &PathBuf) -> Result<(), Box> { + let modpack_lock_file_path = dir.join(PathBuf::from(MODPACK_LOCK_FILENAME)); self.save_to_file(&modpack_lock_file_path)?; Ok(()) }