Add support for ignoring transitive dep versions

This commit is contained in:
Warren Hood 2024-08-18 01:45:14 +02:00
parent 6a18ac39bb
commit 86fa34d9a2
4 changed files with 77 additions and 44 deletions

View file

@ -59,6 +59,9 @@ enum Commands {
/// URL to download the mod from /// URL to download the mod from
#[arg(long)] #[arg(long)]
url: Option<String>, url: Option<String>,
/// Whether to ignore exact transitive mod versions
#[arg(long, short, action)]
ignore_transitive_versions: bool,
}, },
/// Remove a mod from the modpack /// Remove a mod from the modpack
Remove { Remove {
@ -103,7 +106,7 @@ 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?; let modpack_lock = resolver::PinnedPackMeta::load_from_directory(&dir, false).await?;
modpack_lock.save_to_dir(&dir)?; modpack_lock.save_to_dir(&dir)?;
} }
Commands::New { Commands::New {
@ -126,13 +129,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
} }
mc_modpack_meta.init_project(&dir)?; mc_modpack_meta.init_project(&dir)?;
let modpack_lock = resolver::PinnedPackMeta::load_from_directory(&dir).await?; let modpack_lock = resolver::PinnedPackMeta::load_from_directory(&dir, false).await?;
modpack_lock.save_to_dir(&dir)?; modpack_lock.save_to_dir(&dir)?;
} }
Commands::Add { Commands::Add {
name, name,
providers, providers,
url, url,
ignore_transitive_versions,
} => { } => {
let mut modpack_meta = ModpackMeta::load_from_current_directory()?; let mut modpack_meta = ModpackMeta::load_from_current_directory()?;
let old_modpack_meta = modpack_meta.clone(); let old_modpack_meta = modpack_meta.clone();
@ -155,10 +159,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
panic!("Reverted modpack meta:\n{}", e); panic!("Reverted modpack meta:\n{}", e);
}; };
match resolver::PinnedPackMeta::load_from_current_directory().await { match resolver::PinnedPackMeta::load_from_current_directory(ignore_transitive_versions).await {
Ok(mut modpack_lock) => { Ok(mut modpack_lock) => {
let pin_result = modpack_lock let pin_result = modpack_lock
.pin_mod_and_deps(&mod_meta, &modpack_meta) .pin_mod_and_deps(&mod_meta, &modpack_meta, ignore_transitive_versions)
.await; .await;
if let Err(e) = pin_result { if let Err(e) = pin_result {
revert_modpack_meta(e); revert_modpack_meta(e);
@ -188,7 +192,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
panic!("Reverted modpack meta:\n{}", e); panic!("Reverted modpack meta:\n{}", e);
}; };
match resolver::PinnedPackMeta::load_from_current_directory().await { match resolver::PinnedPackMeta::load_from_current_directory(false).await {
Ok(mut modpack_lock) => { Ok(mut modpack_lock) => {
let remove_result = modpack_lock.remove_mod(&name, &modpack_meta); let remove_result = modpack_lock.remove_mod(&name, &modpack_meta);
if let Err(e) = remove_result { if let Err(e) = remove_result {

View file

@ -16,7 +16,7 @@ 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
pub version: semver::Version, pub version: String,
/// Pinned dependencies of a pinned mod /// Pinned dependencies of a pinned mod
pub deps: Option<HashSet<ModMeta>> pub deps: Option<HashSet<ModMeta>>
} }

View file

@ -64,16 +64,16 @@ struct VersionFiles {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct ModrinthProjectVersion { struct ModrinthProjectVersion {
author_id: String, // author_id: String,
date_published: String, // date_published: String,
dependencies: Vec<VersionDeps>, dependencies: Option<Vec<VersionDeps>>,
// downloads: i64, // downloads: i64,
files: Vec<VersionFiles>, files: Vec<VersionFiles>,
// loaders: Vec<String>, // loaders: Vec<String>,
name: String, // name: String,
project_id: String, // project_id: String,
id: String, id: String,
version_number: semver::Version, version_number: String,
// version_type: String, // version_type: String,
} }
@ -102,10 +102,12 @@ impl Modrinth {
project_version: Option<&str>, project_version: Option<&str>,
pack_meta: &ModpackMeta, pack_meta: &ModpackMeta,
) -> Result<ModMeta, Box<dyn Error>> { ) -> Result<ModMeta, Box<dyn Error>> {
let project_versions = self.get_project_versions(project_id, pack_meta).await?; let project_versions = self
.get_project_versions(project_id, pack_meta, true)
.await?;
let project_slug = self.get_project_slug(project_id).await?; let project_slug = self.get_project_slug(project_id).await?;
for version in project_versions.into_iter() { for version in project_versions.iter() {
if project_version.is_none() || project_version.unwrap_or("*") == version.id { if project_version.is_none() || project_version.unwrap_or("*") == version.id {
return Ok(ModMeta::new(&project_slug)? return Ok(ModMeta::new(&project_slug)?
.provider(ModProvider::Modrinth) .provider(ModProvider::Modrinth)
@ -126,20 +128,21 @@ impl Modrinth {
mod_meta: &ModMeta, mod_meta: &ModMeta,
pack_meta: &ModpackMeta, pack_meta: &ModpackMeta,
) -> Result<PinnedMod, Box<dyn Error>> { ) -> Result<PinnedMod, Box<dyn Error>> {
let versions = self.get_project_versions(&mod_meta.name, pack_meta).await?; let versions = self
.get_project_versions(&mod_meta.name, pack_meta, false)
.await?;
let package = if mod_meta.version == "*" { let package = if mod_meta.version == "*" {
versions.last().ok_or(format!( versions.first().ok_or(format!(
"Cannot find package {} for loader={} and mc version={}", "Cannot find package {} for loader={} and mc version={}",
mod_meta.name, mod_meta.name,
pack_meta.modloader.to_string().to_lowercase(), pack_meta.modloader.to_string().to_lowercase(),
pack_meta.mc_version pack_meta.mc_version
))? ))?
} else { } else {
let expected_version = semver::Version::parse(&mod_meta.version)?;
versions versions
.iter() .iter()
.filter(|v| v.version_number == expected_version) .filter(|v| v.version_number == mod_meta.version)
.nth(0) .nth(0)
.ok_or(format!( .ok_or(format!(
"Cannot find package {}@{}", "Cannot find package {}@{}",
@ -148,15 +151,13 @@ impl Modrinth {
}; };
let mut deps_meta = HashSet::new(); let mut deps_meta = HashSet::new();
for dep in package if let Some(deps) = &package.dependencies {
.dependencies for dep in deps.iter().filter(|dep| dep.dependency_type == "required") {
.iter() deps_meta.insert(
.filter(|dep| dep.dependency_type == "required") self.get_mod_meta(&dep.project_id, dep.version_id.as_deref(), pack_meta)
{ .await?,
deps_meta.insert( );
self.get_mod_meta(&dep.project_id, dep.version_id.as_deref(), pack_meta) }
.await?,
);
} }
Ok(PinnedMod { Ok(PinnedMod {
@ -170,7 +171,11 @@ impl Modrinth {
}) })
.collect(), .collect(),
version: package.version_number.clone(), version: package.version_number.clone(),
deps: if package.dependencies.len() > 0 { deps: if package
.dependencies
.as_ref()
.is_some_and(|deps| deps.len() > 0)
{
Some(deps_meta) Some(deps_meta)
} else { } else {
None None
@ -182,19 +187,26 @@ impl Modrinth {
&self, &self,
mod_id: &str, mod_id: &str,
pack_meta: &ModpackMeta, pack_meta: &ModpackMeta,
ignore_game_version_and_loader: bool, // For deps we might as well let them use anything
) -> Result<Vec<ModrinthProjectVersion>, Box<dyn Error>> { ) -> Result<Vec<ModrinthProjectVersion>, Box<dyn Error>> {
let mut project_versions: Vec<ModrinthProjectVersion> = self let query_vec = if ignore_game_version_and_loader {
.client &vec![]
.get(format!( } else {
"https://api.modrinth.com/v2/project/{mod_id}/version" &vec![
))
.query(&[
( (
"loaders", "loaders",
format!("[\"{}\"]", pack_meta.modloader.to_string().to_lowercase()), format!("[\"{}\"]", pack_meta.modloader.to_string().to_lowercase()),
), ),
("game_versions", format!("[\"{}\"]", pack_meta.mc_version)), ("game_versions", format!("[\"{}\"]", pack_meta.mc_version)),
]) ]
};
let mut project_versions: Vec<ModrinthProjectVersion> = self
.client
.get(format!(
"https://api.modrinth.com/v2/project/{mod_id}/version"
))
.query(query_vec)
.send() .send()
.await? .await?
.json() .json()

View file

@ -32,11 +32,10 @@ impl PinnedPackMeta {
&mut self, &mut self,
mod_metadata: &ModMeta, mod_metadata: &ModMeta,
pack_metadata: &ModpackMeta, pack_metadata: &ModpackMeta,
ignore_transitive_versions: bool,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
if let Some(mod_meta) = self.mods.get(&mod_metadata.name) { if let Some(mod_meta) = self.mods.get(&mod_metadata.name) {
if mod_metadata.version != "*" if mod_metadata.version != "*" && mod_metadata.version == mod_meta.version {
&& semver::Version::parse(&mod_metadata.version)? == mod_meta.version
{
// Skip already pinned mods // 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 // 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(()); return Ok(());
@ -45,6 +44,11 @@ impl PinnedPackMeta {
let mut deps = let mut deps =
HashSet::from_iter(self.pin_mod(mod_metadata, pack_metadata).await?.into_iter()); HashSet::from_iter(self.pin_mod(mod_metadata, pack_metadata).await?.into_iter());
if ignore_transitive_versions {
// Ignore transitive dep versions
deps = deps.iter().map(|d| d.clone().version("*")).collect();
}
let pinned_version = self let pinned_version = self
.mods .mods
.get(&mod_metadata.name) .get(&mod_metadata.name)
@ -183,9 +187,14 @@ impl PinnedPackMeta {
Ok(()) Ok(())
} }
pub async fn init(&mut self, modpack_meta: &ModpackMeta) -> Result<(), Box<dyn Error>> { pub async fn init(
&mut self,
modpack_meta: &ModpackMeta,
ignore_transitive_versions: bool,
) -> Result<(), Box<dyn Error>> {
for mod_meta in modpack_meta.iter_mods() { for mod_meta in modpack_meta.iter_mods() {
self.pin_mod_and_deps(mod_meta, modpack_meta).await?; self.pin_mod_and_deps(mod_meta, modpack_meta, ignore_transitive_versions)
.await?;
} }
Ok(()) Ok(())
} }
@ -210,12 +219,18 @@ impl PinnedPackMeta {
Ok(()) Ok(())
} }
pub async fn load_from_directory(directory: &PathBuf) -> Result<Self, Box<dyn Error>> { pub async fn load_from_directory(
directory: &PathBuf,
ignore_transitive_versions: bool,
) -> Result<Self, Box<dyn Error>> {
let modpack_lock_file_path = directory.clone().join(PathBuf::from(MODPACK_LOCK_FILENAME)); let modpack_lock_file_path = directory.clone().join(PathBuf::from(MODPACK_LOCK_FILENAME));
if !modpack_lock_file_path.exists() { if !modpack_lock_file_path.exists() {
let mut new_modpack_lock = Self::new(); let mut new_modpack_lock = Self::new();
new_modpack_lock new_modpack_lock
.init(&ModpackMeta::load_from_directory(directory)?) .init(
&ModpackMeta::load_from_directory(directory)?,
ignore_transitive_versions,
)
.await?; .await?;
return Ok(new_modpack_lock); return Ok(new_modpack_lock);
}; };
@ -223,7 +238,9 @@ impl PinnedPackMeta {
Ok(toml::from_str(&modpack_lock_contents)?) Ok(toml::from_str(&modpack_lock_contents)?)
} }
pub async fn load_from_current_directory() -> Result<Self, Box<dyn Error>> { pub async fn load_from_current_directory(
Self::load_from_directory(&std::env::current_dir()?).await ignore_transitive_versions: bool,
) -> Result<Self, Box<dyn Error>> {
Self::load_from_directory(&std::env::current_dir()?, ignore_transitive_versions).await
} }
} }