mirror of
https://github.com/WarrenHood/MCModpackManager.git
synced 2025-04-30 00:04:59 +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 = 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
|
let expected_version = semver::Version::parse(&mod_meta.version)?;
|
||||||
.into_iter()
|
versions
|
||||||
.map(|f| FileSource::Download {
|
.iter()
|
||||||
url: f.url,
|
.filter(|v| v.version_number == expected_version)
|
||||||
sha1: f.hashes.sha1,
|
.nth(0)
|
||||||
sha512: f.hashes.sha512,
|
.ok_or(format!(
|
||||||
})
|
"Cannot find package {}@{}",
|
||||||
.collect(),
|
mod_meta.name, mod_meta.version
|
||||||
version: v.version_number,
|
))?
|
||||||
deps: None, // TODO: Implement automagic transitive dep installation and pinning
|
};
|
||||||
|
|
||||||
|
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());
|
.collect(),
|
||||||
}
|
version: package.version_number.clone(),
|
||||||
|
deps: None, // TODO: Get deps
|
||||||
// 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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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