mirror of
https://github.com/WarrenHood/MCModpackManager.git
synced 2025-04-29 19:04:57 +01: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