mirror of
				https://github.com/WarrenHood/MCModpackManager.git
				synced 2025-11-04 01:58:41 +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.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<dyn Error>> {
 | 
			
		|||
                    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<dyn Error>> {
 | 
			
		|||
 | 
			
		||||
                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);
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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!(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<FileSource>,
 | 
			
		||||
    /// Version of mod
 | 
			
		||||
    version: semver::Version,
 | 
			
		||||
    pub version: semver::Version,
 | 
			
		||||
    /// Pinned dependencies of a pinned mod
 | 
			
		||||
    deps: Option<Vec<PinnedMod>>
 | 
			
		||||
    pub deps: Option<Vec<ModMeta>>
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,55 +58,40 @@ impl Modrinth {
 | 
			
		|||
        &self,
 | 
			
		||||
        mod_meta: &ModMeta,
 | 
			
		||||
        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?;
 | 
			
		||||
        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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<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 {
 | 
			
		||||
            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<dyn Error>> {
 | 
			
		||||
        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<dyn Error>> {
 | 
			
		||||
        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<dyn Error>> {
 | 
			
		||||
        let modpack_lock_file_path = dir.join(PathBuf::from(MODPACK_LOCK_FILENAME));
 | 
			
		||||
        self.save_to_file(&modpack_lock_file_path)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue