aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs
blob: 37cbbfee6fbb2dc259660253ccd3eafe20ac7dc9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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<Problem>,
    pub(crate) learned_pool: Vec<Vec<Rule>>,
}

impl SolverProblemsException {
    pub const ERROR_DEPENDENCY_RESOLUTION_FAILED: i64 = 2;

    pub fn new(problems: Vec<Problem>, learned_pool: Vec<Vec<Rule>>) -> 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<String> = Vec::new();
        let mut is_caused_by_lock = false;

        let mut problems: Vec<String> = 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<String> = 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 <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> 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("<warning>ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.</warning>\nIf you can not upgrade PHP you can require <info>composer/package-versions-deprecated</info> 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<Problem> {
        &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<String> = 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::<Vec<_>>()
            .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<Rule>>) -> Vec<String> {
        let mut missing_extensions: indexmap::IndexMap<String, i64> = 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 {}