mirror of
				https://github.com/WarrenHood/MCModpackManager.git
				synced 2025-11-04 08:18:40 +00:00 
			
		
		
		
	Improved mod resolution
This commit is contained in:
		
							parent
							
								
									09e3c066b6
								
							
						
					
					
						commit
						ec50a380b8
					
				| 
						 | 
					@ -98,6 +98,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
                    mc_modpack_meta = mc_modpack_meta.provider(provider);
 | 
					                    mc_modpack_meta = mc_modpack_meta.provider(provider);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                mc_modpack_meta.init_project(&dir)?;
 | 
					                mc_modpack_meta.init_project(&dir)?;
 | 
				
			||||||
 | 
					                let modpack_lock = resolver::PinnedPackMeta::load_from_directory(&dir).await?;
 | 
				
			||||||
 | 
					                modpack_lock.save_to_dir(&dir)?;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Commands::New {
 | 
					            Commands::New {
 | 
				
			||||||
                name,
 | 
					                name,
 | 
				
			||||||
| 
						 | 
					@ -118,6 +120,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
                    mc_modpack_meta = mc_modpack_meta.provider(provider);
 | 
					                    mc_modpack_meta = mc_modpack_meta.provider(provider);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                mc_modpack_meta.init_project(&dir)?;
 | 
					                mc_modpack_meta.init_project(&dir)?;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                let modpack_lock = resolver::PinnedPackMeta::load_from_directory(&dir).await?;
 | 
				
			||||||
 | 
					                modpack_lock.save_to_dir(&dir)?;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Commands::Add {
 | 
					            Commands::Add {
 | 
				
			||||||
                name,
 | 
					                name,
 | 
				
			||||||
| 
						 | 
					@ -147,7 +152,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match resolver::PinnedPackMeta::load_from_current_directory().await {
 | 
					                match resolver::PinnedPackMeta::load_from_current_directory().await {
 | 
				
			||||||
                    Ok(mut modpack_lock) => {
 | 
					                    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 {
 | 
					                        if let Err(e) = pin_result {
 | 
				
			||||||
                            revert_modpack_meta(e);
 | 
					                            revert_modpack_meta(e);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 struct ModMeta {
 | 
				
			||||||
    pub name: String,
 | 
					    pub name: String,
 | 
				
			||||||
    pub version: String,
 | 
					    pub version: String,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ impl ModpackMeta {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn add_mod(mut self, mod_meta: &ModMeta) -> Self {
 | 
					    pub fn add_mod(mut self, mod_meta: &ModMeta) -> Self {
 | 
				
			||||||
        if let Some(old_mod_meta) = self.mods.get(&mod_meta.name) {
 | 
					        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 {
 | 
					        else {
 | 
				
			||||||
            println!(
 | 
					            println!(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,18 +5,18 @@ use crate::mod_meta::ModMeta;
 | 
				
			||||||
pub mod modrinth;
 | 
					pub mod modrinth;
 | 
				
			||||||
pub mod raw;
 | 
					pub mod raw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					#[derive(Serialize, Deserialize, Clone)]
 | 
				
			||||||
enum FileSource {
 | 
					enum FileSource {
 | 
				
			||||||
    Download { url: String, sha1: String, sha512: String},
 | 
					    Download { url: String, sha1: String, sha512: String},
 | 
				
			||||||
    Local { path: PathBuf, sha1: String, sha512: String },
 | 
					    Local { path: PathBuf, sha1: String, sha512: String },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					#[derive(Serialize, Deserialize, Clone)]
 | 
				
			||||||
pub struct PinnedMod {
 | 
					pub struct PinnedMod {
 | 
				
			||||||
    /// Source of the files for the mod
 | 
					    /// Source of the files for the mod
 | 
				
			||||||
    source: Vec<FileSource>,
 | 
					    source: Vec<FileSource>,
 | 
				
			||||||
    /// Version of mod
 | 
					    /// Version of mod
 | 
				
			||||||
    version: semver::Version,
 | 
					    pub version: semver::Version,
 | 
				
			||||||
    /// Pinned dependencies of a pinned mod
 | 
					    /// Pinned dependencies of a pinned mod
 | 
				
			||||||
    deps: Option<Vec<PinnedMod>>
 | 
					    pub deps: Option<Vec<ModMeta>>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -58,55 +58,40 @@ impl Modrinth {
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        mod_meta: &ModMeta,
 | 
					        mod_meta: &ModMeta,
 | 
				
			||||||
        pack_meta: &ModpackMeta,
 | 
					        pack_meta: &ModpackMeta,
 | 
				
			||||||
    ) -> Result<Vec<PinnedMod>, Box<dyn Error>> {
 | 
					    ) -> Result<PinnedMod, Box<dyn Error>> {
 | 
				
			||||||
        let mut versions = self.get_project_versions(&mod_meta.name, pack_meta).await?;
 | 
					        let mut versions = self.get_project_versions(&mod_meta.name, pack_meta).await?;
 | 
				
			||||||
        versions.sort_by_key(|v| v.version_number.clone());
 | 
					        versions.sort_by_key(|v| v.version_number.clone());
 | 
				
			||||||
        versions.reverse();
 | 
					        versions.reverse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if mod_meta.version == "*" {
 | 
					        let package = if mod_meta.version == "*" {
 | 
				
			||||||
            return Ok(versions
 | 
					            versions
 | 
				
			||||||
                .into_iter()
 | 
					                .last()
 | 
				
			||||||
                .map(|v| PinnedMod {
 | 
					                .ok_or(format!("Cannot find package {}", mod_meta.name))?
 | 
				
			||||||
                    source: v
 | 
					        } else {
 | 
				
			||||||
                        .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
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .collect());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // TODO: Implement more general version constraints
 | 
					 | 
				
			||||||
            let expected_version = semver::Version::parse(&mod_meta.version)?;
 | 
					            let expected_version = semver::Version::parse(&mod_meta.version)?;
 | 
				
			||||||
        for v in versions.into_iter() {
 | 
					            versions
 | 
				
			||||||
            if v.version_number == expected_version {
 | 
					                .iter()
 | 
				
			||||||
                return Ok(vec![PinnedMod {
 | 
					                .filter(|v| v.version_number == expected_version)
 | 
				
			||||||
                    source: v
 | 
					                .nth(0)
 | 
				
			||||||
 | 
					                .ok_or(format!(
 | 
				
			||||||
 | 
					                    "Cannot find package {}@{}",
 | 
				
			||||||
 | 
					                    mod_meta.name, mod_meta.version
 | 
				
			||||||
 | 
					                ))?
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(PinnedMod {
 | 
				
			||||||
 | 
					            source: package
 | 
				
			||||||
                .files
 | 
					                .files
 | 
				
			||||||
                        .into_iter()
 | 
					                .iter()
 | 
				
			||||||
                .map(|f| FileSource::Download {
 | 
					                .map(|f| FileSource::Download {
 | 
				
			||||||
                            url: f.url,
 | 
					                    url: f.url.clone(),
 | 
				
			||||||
                            sha1: f.hashes.sha1,
 | 
					                    sha1: f.hashes.sha1.clone(),
 | 
				
			||||||
                            sha512: f.hashes.sha512,
 | 
					                    sha512: f.hashes.sha512.clone(),
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                .collect(),
 | 
					                .collect(),
 | 
				
			||||||
                    version: v.version_number,
 | 
					            version: package.version_number.clone(),
 | 
				
			||||||
                    deps: None,
 | 
					            deps: None, // TODO: Get deps
 | 
				
			||||||
                }]);
 | 
					        })
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Err(format!(
 | 
					 | 
				
			||||||
            "Couldn't find a version for mod '{}' that satisfies the version constraint `{}`",
 | 
					 | 
				
			||||||
            mod_meta.name, mod_meta.version
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .into())
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn get_project_versions(
 | 
					    async fn get_project_versions(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    borrow::Borrow,
 | 
					 | 
				
			||||||
    collections::{HashMap, HashSet},
 | 
					    collections::{HashMap, HashSet},
 | 
				
			||||||
    error::Error,
 | 
					    error::Error,
 | 
				
			||||||
    path::PathBuf,
 | 
					    path::PathBuf,
 | 
				
			||||||
| 
						 | 
					@ -29,11 +28,46 @@ impl PinnedPackMeta {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn pin_mod(
 | 
					    pub async fn pin_mod_and_deps(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
        mod_metadata: &ModMeta,
 | 
					        mod_metadata: &ModMeta,
 | 
				
			||||||
        pack_metadata: &ModpackMeta,
 | 
					        pack_metadata: &ModpackMeta,
 | 
				
			||||||
    ) -> Result<(), Box<dyn Error>> {
 | 
					    ) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					        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<Vec<ModMeta>, Box<dyn Error>> {
 | 
				
			||||||
        let mod_providers = if let Some(mod_providers) = &mod_metadata.providers {
 | 
					        let mod_providers = if let Some(mod_providers) = &mod_metadata.providers {
 | 
				
			||||||
            mod_providers
 | 
					            mod_providers
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
| 
						 | 
					@ -52,28 +86,35 @@ impl PinnedPackMeta {
 | 
				
			||||||
            match mod_provider {
 | 
					            match mod_provider {
 | 
				
			||||||
                crate::mod_meta::ModProvider::CurseForge => unimplemented!(),
 | 
					                crate::mod_meta::ModProvider::CurseForge => unimplemented!(),
 | 
				
			||||||
                crate::mod_meta::ModProvider::Modrinth => {
 | 
					                crate::mod_meta::ModProvider::Modrinth => {
 | 
				
			||||||
                    let pinned_mods = self.modrinth.resolve(&mod_metadata, pack_metadata).await;
 | 
					                    let pinned_mod = self.modrinth.resolve(&mod_metadata, pack_metadata).await;
 | 
				
			||||||
                    if let Ok(pinned_mods) = pinned_mods {
 | 
					                    if let Ok(pinned_mod) = pinned_mod {
 | 
				
			||||||
                        pinned_mods.into_iter().for_each(|m| {
 | 
					                        self.mods
 | 
				
			||||||
                            self.mods.insert(mod_metadata.name.clone(), m);
 | 
					                            .insert(mod_metadata.name.clone(), pinned_mod.clone());
 | 
				
			||||||
                        });
 | 
					                        println!("Pinned {}@{}", mod_metadata.name, pinned_mod.version);
 | 
				
			||||||
                    } else if let Err(e) = pinned_mods {
 | 
					                        if let Some(deps) = &pinned_mod.deps {
 | 
				
			||||||
                        return Err(format!(
 | 
					                            return Ok(deps
 | 
				
			||||||
                            "Failed to resolve mod '{}' (provider={:#?}) with constraint {}: {}",
 | 
					                                .iter()
 | 
				
			||||||
                            mod_metadata.name, mod_provider, mod_metadata.version, e
 | 
					                                .filter(|d| !self.mods.contains_key(&d.name))
 | 
				
			||||||
                        )
 | 
					                                .cloned()
 | 
				
			||||||
                        .into());
 | 
					                                .collect());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        return Ok(vec![]);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                crate::mod_meta::ModProvider::Raw => unimplemented!(),
 | 
					                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<dyn Error>> {
 | 
					    pub async fn init(&mut self, modpack_meta: &ModpackMeta) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
        for mod_meta in modpack_meta.iter_mods() {
 | 
					        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(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -88,8 +129,12 @@ impl PinnedPackMeta {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn save_current_dir_lock(&self) -> Result<(), Box<dyn Error>> {
 | 
					    pub fn save_current_dir_lock(&self) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
        let modpack_lock_file_path =
 | 
					        self.save_to_dir(&std::env::current_dir()?)?;
 | 
				
			||||||
            std::env::current_dir()?.join(PathBuf::from(MODPACK_LOCK_FILENAME));
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save_to_dir(&self, dir: &PathBuf) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					        let modpack_lock_file_path = dir.join(PathBuf::from(MODPACK_LOCK_FILENAME));
 | 
				
			||||||
        self.save_to_file(&modpack_lock_file_path)?;
 | 
					        self.save_to_file(&modpack_lock_file_path)?;
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue