From d05d73dd8039b2858c5f715eec2d28c58b2a33d4 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Fri, 15 May 2026 23:25:44 +0900 Subject: feat(port): port SolverProblemsException.php --- .../solver_problems_exception.rs | 150 +++++++++++++++++++++ 1 file changed, 150 insertions(+) (limited to 'crates/shirabe/src') diff --git a/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs b/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs index e882e1f..37cbbfe 100644 --- a/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs +++ b/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs @@ -1 +1,151 @@ //! ref: composer/src/Composer/DependencyResolver/SolverProblemsException.php + +use shirabe_php_shim::RuntimeException; + +use crate::dependency_resolver::pool::Pool; +use crate::dependency_resolver::problem::Problem; +use crate::dependency_resolver::request::Request; +use crate::dependency_resolver::rule::Rule; +use crate::repository::repository_set::RepositorySet; +use crate::util::ini_helper::IniHelper; + +#[derive(Debug)] +pub struct SolverProblemsException { + inner: RuntimeException, + pub(crate) problems: Vec, + pub(crate) learned_pool: Vec>, +} + +impl SolverProblemsException { + pub const ERROR_DEPENDENCY_RESOLUTION_FAILED: i64 = 2; + + pub fn new(problems: Vec, learned_pool: Vec>) -> Self { + let message = format!( + "Failed resolving dependencies with {} problems, call getPrettyString to get formatted details", + problems.len() + ); + Self { + inner: RuntimeException { + message, + code: Self::ERROR_DEPENDENCY_RESOLUTION_FAILED, + }, + problems, + learned_pool, + } + } + + pub fn get_pretty_string( + &self, + repository_set: &RepositorySet, + request: &Request, + pool: &Pool, + is_verbose: bool, + is_dev_extraction: bool, + ) -> String { + let installed_map = request.get_present_map(true); + let mut missing_extensions: Vec = Vec::new(); + let mut is_caused_by_lock = false; + + let mut problems: Vec = Vec::new(); + for problem in &self.problems { + problems.push(format!( + "{}\n", + problem.get_pretty_string(repository_set, request, pool, is_verbose, &installed_map, &self.learned_pool) + )); + missing_extensions.extend(self.get_extension_problems(problem.get_reasons())); + is_caused_by_lock = is_caused_by_lock || problem.is_caused_by_lock(repository_set, request, pool); + } + + let mut i = 1; + let mut text = "\n".to_string(); + let mut unique_problems = problems.clone(); + unique_problems.dedup(); + for problem in &unique_problems { + text.push_str(&format!(" Problem {}{}", i, problem)); + i += 1; + } + + let mut hints: Vec = Vec::new(); + if !is_dev_extraction && (text.contains("could not be found") || text.contains("no matching package found")) { + hints.push("Potential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead for further common problems.".to_string()); + } + + if !missing_extensions.is_empty() { + hints.push(self.create_extension_hint(&missing_extensions)); + } + + if is_caused_by_lock && !is_dev_extraction && !request.get_update_allow_transitive_root_dependencies() { + hints.push("Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.".to_string()); + } + + if text.contains("found composer-plugin-api[2.0.0] but it does not match") && text.contains("- ocramius/package-versions") { + hints.push("ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.\nIf you can not upgrade PHP you can require composer/package-versions-deprecated to resolve this with PHP 7.0+.".to_string()); + } + + // class_exists('PHPUnit\Framework\TestCase', false) is always false at runtime + if text.contains("found composer-plugin-api[2.0.0] but it does not match") { + hints.push("You are using Composer 2, which some of your plugins seem to be incompatible with. Make sure you update your plugins or report a plugin-issue to ask them to support Composer 2.".to_string()); + } + + if !hints.is_empty() { + text.push('\n'); + text.push_str(&hints.join("\n\n")); + } + + text + } + + pub fn get_problems(&self) -> &Vec { + &self.problems + } + + fn create_extension_hint(&self, missing_extensions: &[String]) -> String { + let mut paths = IniHelper::get_all(); + + if paths.first().map_or(false, |s| s.is_empty()) { + if paths.len() == 1 { + return String::new(); + } + paths.remove(0); + } + + let mut unique_extensions: Vec = missing_extensions.to_vec(); + unique_extensions.sort(); + unique_extensions.dedup(); + let ignore_extensions_arguments: String = unique_extensions + .iter() + .map(|ext| format!("--ignore-platform-req={}", ext)) + .collect::>() + .join(" "); + + let mut text = "To enable extensions, verify that they are enabled in your .ini files:\n - ".to_string(); + text.push_str(&paths.join("\n - ")); + text.push_str("\nYou can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode."); + text.push_str(&format!("\nAlternatively, you can run Composer with `{}` to temporarily ignore these required extensions.", ignore_extensions_arguments)); + + text + } + + fn get_extension_problems(&self, reason_sets: Vec>) -> Vec { + let mut missing_extensions: indexmap::IndexMap = indexmap::IndexMap::new(); + for reason_set in reason_sets { + for rule in reason_set { + let required = rule.get_required_package(); + if let Some(req) = required { + if req.starts_with("ext-") { + missing_extensions.insert(req.to_string(), 1); + } + } + } + } + missing_extensions.into_keys().collect() + } +} + +impl std::fmt::Display for SolverProblemsException { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.inner.message) + } +} + +impl std::error::Error for SolverProblemsException {} -- cgit v1.3.1