From a1d92063e85855f7a6a5c4bf3a4f235d9b5b7576 Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:32:56 -0400 Subject: [PATCH] Improvements to resolve, zipping and additional data in dependencies --- src/commands/qmod/manifest.rs | 9 ++- src/commands/qmod/zip.rs | 3 + src/commands/restore.rs | 17 +++-- src/models/package.rs | 122 +++++++++++++++++++--------------- src/repository/mod.rs | 25 ++++++- 5 files changed, 112 insertions(+), 64 deletions(-) diff --git a/src/commands/qmod/manifest.rs b/src/commands/qmod/manifest.rs index e53497d3..ebbf034e 100644 --- a/src/commands/qmod/manifest.rs +++ b/src/commands/qmod/manifest.rs @@ -8,6 +8,7 @@ use qpm_qmod::models::mod_json::ModJson; use crate::models::mod_json::{ModJsonExtensions, PreProcessingData}; use crate::models::package::{PackageConfigExtensions, SharedPackageConfigExtensions}; +use crate::repository::{self, Repository}; use qpm_package::models::dependency::SharedPackageConfig; @@ -42,14 +43,16 @@ pub(crate) fn execute_qmod_manifest_operation( ) -> Result<()> { let package = PackageConfig::read(".")?; let shared_package = SharedPackageConfig::read(".")?; + let repo = repository::useful_default_new(build_parameters.offline)?; - let new_json = generate_qmod_manifest(&package, shared_package, build_parameters)?; + let new_json = generate_qmod_manifest(&repo, &package, shared_package, build_parameters)?; // Write mod.json new_json.write(&PathBuf::from(ModJson::get_result_name()))?; Ok(()) } pub(crate) fn generate_qmod_manifest( + repo: &impl Repository, package: &PackageConfig, shared_package: SharedPackageConfig, build_parameters: ManifestQmodOperationArgs, @@ -61,7 +64,7 @@ pub(crate) fn generate_qmod_manifest( let binary = shared_package .config .info - .get_so_name() + .get_so_name2() .file_name() .map(|s| s.to_string_lossy().to_string()); @@ -72,7 +75,7 @@ pub(crate) fn generate_qmod_manifest( binary, }; let mut existing_json = ModJson::read_and_preprocess(preprocess_data)?; - let template_mod_json: ModJson = shared_package.to_mod_json(); + let template_mod_json: ModJson = shared_package.to_mod_json(repo)?; let legacy_0_1_0 = package.matches_version(&VersionReq::parse("^0.1.0")?); existing_json = ModJson::merge_modjson(existing_json, template_mod_json, legacy_0_1_0); if let Some(excluded) = build_parameters.exclude_libs { diff --git a/src/commands/qmod/zip.rs b/src/commands/qmod/zip.rs index 5ac6f15f..9723f15d 100644 --- a/src/commands/qmod/zip.rs +++ b/src/commands/qmod/zip.rs @@ -13,6 +13,7 @@ use crate::commands::qmod::manifest::{generate_qmod_manifest, ManifestQmodOperat use crate::commands::scripts; use crate::models::mod_json::ModJsonExtensions; use crate::models::package::PackageConfigExtensions; +use crate::repository; use crate::terminal::colors::QPMColor; use qpm_package::models::dependency::SharedPackageConfig; @@ -63,8 +64,10 @@ pub(crate) fn execute_qmod_zip_operation(build_parameters: ZipQmodOperationArgs) "No mod.template.json found in the current directory, set it up please :) Hint: use \"qmod create\""); let package = PackageConfig::read(".")?; let shared_package = SharedPackageConfig::read(".")?; + let repo = repository::useful_default_new(build_parameters.offline)?; let new_manifest = generate_qmod_manifest( + &repo, &package, shared_package, ManifestQmodOperationArgs { diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 5a8d384d..cee01f5c 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -3,7 +3,7 @@ use std::{env, fs::File, io::Read, path::Path}; use clap::Args; use color_eyre::{ - eyre::{bail, eyre, ContextCompat, Result}, + eyre::{bail, eyre, Context, ContextCompat, Result}, Section, }; use itertools::Itertools; @@ -82,21 +82,28 @@ impl Command for RestoreCommand { // update config shared_package.config = package; - // make additional data use cached data + + // https://discord.com/channels/994470435100033074/994630741235347566/1265083186157715538 + // make additional data update (for local installs) shared_package .restored_dependencies .iter_mut() .try_for_each(|d| -> color_eyre::Result<()> { - let package = repo + let package: SharedPackageConfig = repo .get_package(&d.dependency.id, &d.version) - .ok() - .flatten() .with_context(|| { format!( "Unable to fetch {}:{}", d.dependency.id.dependency_id_color(), d.version.version_id_color() ) + })? + .wrap_err_with(|| { + format!( + "Package {}:{} does not exist", + d.dependency.id.dependency_id_color(), + d.version.version_id_color() + ) })?; d.dependency.additional_data = package.config.info.additional_data; Ok(()) diff --git a/src/models/package.rs b/src/models/package.rs index ecf067a9..4448f569 100644 --- a/src/models/package.rs +++ b/src/models/package.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, fs::File, io::BufReader, path::Path}; +use std::{collections::HashMap, fs::File, io::BufReader, path::Path}; use color_eyre::{eyre::Context, owo_colors::OwoColorize, Result, Section}; use itertools::Itertools; @@ -6,7 +6,7 @@ use qpm_package::{ extensions::package_metadata::PackageMetadataExtensions, models::{ dependency::{Dependency, SharedDependency, SharedPackageConfig}, - package::PackageConfig, + package::{PackageConfig, PackageDependency}, }, }; use qpm_qmod::models::mod_json::{ModDependency, ModJson}; @@ -40,7 +40,7 @@ pub trait SharedPackageConfigExtensions: Sized { repository: &impl Repository, ) -> Result<(Self, Vec)>; - fn to_mod_json(self) -> ModJson; + fn to_mod_json(self, repo: &impl Repository) -> color_eyre::Result; fn try_write_toolchain(&self, repo: &impl Repository) -> Result<()>; } @@ -167,109 +167,115 @@ impl SharedPackageConfigExtensions for SharedPackageConfig { )) } - fn to_mod_json(self) -> ModJson { + fn to_mod_json(self, repo: &impl Repository) -> color_eyre::Result { // Self { // id: dep.id, // version_range: dep.version_range, // mod_link: dep.additional_data.mod_link, // } - let local_deps = &self.config.dependencies; + // List of dependencies we are directly referencing in qpm.json + let direct_dependencies: HashMap = self + .config + .dependencies + .iter() + .map(|f| (f.id.clone(), f)) + .collect(); - // Only bundle mods that are not specifically excluded in qpm.json or if they're not header-only - let restored_deps: Vec<_> = self + let restored_dependencies_inflated: HashMap = self .restored_dependencies - .iter() - .filter(|dep| { - let local_dep_opt = local_deps - .iter() - .find(|local_dep| local_dep.id == dep.dependency.id); + .into_iter() + .map(|shared_dep| -> color_eyre::Result<_> { + let dep_package = + repo.get_package_checked(&shared_dep.dependency.id, &shared_dep.version)?; + Ok((shared_dep.dependency.id.clone(), dep_package)) + }) + .try_collect()?; - if let Some(local_dep) = local_dep_opt { + // Only bundle mods that are not specifically excluded in qpm.json or if they're not header-only + let restored_deps: Vec<_> = restored_dependencies_inflated + .values() + .filter(|dep_package| { + if let Some(local_dep) = direct_dependencies.get(&dep_package.config.info.id) { // if force included/excluded, return early if let Some(force_included) = local_dep.additional_data.include_qmod { return force_included; } } - // or if header only is false - dep.dependency.additional_data.mod_link.is_some() - || !dep.dependency.additional_data.headers_only.unwrap_or(false) + // if a qmod, we need to depend on it + dep_package.config.info.additional_data.mod_link.is_some() + // if not header only, we link to it and bundle it later + || !dep_package + .config + .info + .additional_data + .headers_only + .unwrap_or(false) }) .collect(); - // List of dependencies we are directly referencing in qpm.json - let direct_dependencies: HashSet = self - .config - .dependencies - .iter() - .map(|f| f.id.clone()) - .collect(); - // downloadable mods links n stuff // mods that are header-only but provide qmods can be added as deps // Must be directly referenced in qpm.json - let mods: Vec = local_deps - .iter() + let mods: Vec = direct_dependencies + .values() // Removes any dependency without a qmod link .filter_map(|dep| { - let shared_dep = restored_deps.iter().find(|d| d.dependency.id == dep.id)?; - if shared_dep.dependency.additional_data.mod_link.is_some() { - return Some((shared_dep, dep)); + let shared_dep = restored_dependencies_inflated.get(&dep.id)?; + if shared_dep.config.info.additional_data.mod_link.is_some() { + let mod_dependency = ModDependency { + version_range: dep.version_range.clone(), + id: dep.id.clone(), + mod_link: shared_dep.config.info.additional_data.mod_link.clone(), + required: dep.additional_data.required, + }; + return Some(mod_dependency); } None }) - .map(|(shared_dep, dep)| ModDependency { - version_range: dep.version_range.clone(), - id: dep.id.clone(), - mod_link: shared_dep.dependency.additional_data.mod_link.clone(), - required: dep.additional_data.required, - }) + .sorted_by(|a, b| a.id.cmp(&b.id)) .collect(); // The rest of the mods to handle are not qmods, they are .so or .a mods // actual direct lib deps - let libs: Vec = self - .restored_dependencies + let libs: Vec = restored_deps .iter() // We could just query the bmbf core mods list on GH? // https://github.com/BMBF/resources/blob/master/com.beatgames.beatsaber/core-mods.json // but really the only lib that never is copied over is the modloader, the rest is either a downloaded qmod or just a copied lib // even core mods should technically be added via download .filter(|lib| { - // find the actual dependency for the include qmod value - let local_dep_opt = local_deps - .iter() - .find(|local_dep| local_dep.id == lib.dependency.id); - // if set, use it later - let include_qmod = local_dep_opt - .and_then(|local_dep| local_dep.additional_data.include_qmod.as_ref()); + let include_qmod = direct_dependencies + .get(&lib.config.info.id) + .and_then(|dep| dep.additional_data.include_qmod); // Must be directly referenced in qpm.json - direct_dependencies.contains(&lib.dependency.id) && + direct_dependencies.contains_key(&lib.config.info.id) && // keep if header only is false, or if not defined - !lib.dependency.additional_data.headers_only.unwrap_or(false) && + !lib.config.info.additional_data.headers_only.unwrap_or(false) && // Modloader should never be included - lib.dependency.id != "modloader" && + lib.config.info.id != "modloader" && // don't include static deps - !lib.dependency.additional_data.static_linking.unwrap_or(false) && + !lib.config.info.additional_data.static_linking.unwrap_or(false) && // it's marked to be included, defaults to including ( same as dependencies with qmods ) - include_qmod.copied().unwrap_or(true) && + include_qmod.unwrap_or(true) && // Only keep libs that aren't downloadable - !mods.iter().any(|dep| lib.dependency.id == dep.id) + !mods.iter().any(|dep| lib.config.info.id == dep.id) }) - .map(|dep| dep.get_so_name().to_str().unwrap().to_string()) + .map(|dep| dep.config.info.get_so_name2().to_str().unwrap().to_string()) + .sorted() .collect(); - ModJson { + let json = ModJson { name: self.config.info.name.clone(), id: self.config.info.id.clone(), porter: None, @@ -280,11 +286,17 @@ impl SharedPackageConfigExtensions for SharedPackageConfig { cover_image: None, is_library: None, dependencies: mods, - // TODO: Change - late_mod_files: vec![self.config.info.get_so_name().to_str().unwrap().to_string()], + late_mod_files: vec![self + .config + .info + .get_so_name2() + .to_str() + .unwrap() + .to_string()], library_files: libs, ..Default::default() - } + }; + Ok(json) } fn try_write_toolchain(&self, repo: &impl Repository) -> Result<()> { diff --git a/src/repository/mod.rs b/src/repository/mod.rs index ea1bf880..56a9a303 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -1,4 +1,7 @@ -use color_eyre::Result; +use color_eyre::{ + eyre::{Context, ContextCompat}, + Result, +}; use itertools::Itertools; use semver::Version; @@ -6,6 +9,8 @@ use qpm_package::models::{ backend::PackageVersion, dependency::SharedPackageConfig, package::PackageConfig, }; +use crate::terminal::colors::QPMColor; + use self::{ local::FileRepository, memcached::MemcachedRepository, multi::MultiDependencyRepository, qpackages::QPMRepository, @@ -20,6 +25,24 @@ pub trait Repository { fn get_package_names(&self) -> Result>; fn get_package_versions(&self, id: &str) -> Result>>; fn get_package(&self, id: &str, version: &Version) -> Result>; + + fn get_package_checked(&self, id: &str, version: &Version) -> Result { + self.get_package(id, version) + .with_context(|| { + format!( + "Unable to fetch {}:{}", + id.dependency_id_color(), + version.version_id_color() + ) + })? + .wrap_err_with(|| { + format!( + "Package not found: {}:{}", + id.dependency_id_color(), + version.version_id_color() + ) + }) + } // add to the db cache // this just stores the shared config itself, not the package fn add_to_db_cache(&mut self, config: SharedPackageConfig, permanent: bool) -> Result<()>;