Skip to main content

rez_next_solver/
resolution.rs

1//! Resolution result and related types
2
3use rez_next_package::Package;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Result of a dependency resolution
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ResolutionResult {
10    /// Resolved packages in dependency order
11    pub packages: Vec<Package>,
12    /// Whether conflicts were resolved during resolution
13    pub conflicts_resolved: bool,
14    /// Resolution time in milliseconds
15    pub resolution_time_ms: u64,
16    /// Additional metadata
17    pub metadata: HashMap<String, String>,
18}
19
20impl ResolutionResult {
21    /// Create a new resolution result
22    pub fn new(packages: Vec<Package>) -> Self {
23        Self {
24            packages,
25            conflicts_resolved: false,
26            resolution_time_ms: 0,
27            metadata: HashMap::new(),
28        }
29    }
30
31    /// Create a successful resolution with conflicts resolved
32    pub fn with_conflicts_resolved(packages: Vec<Package>, resolution_time_ms: u64) -> Self {
33        Self {
34            packages,
35            conflicts_resolved: true,
36            resolution_time_ms,
37            metadata: HashMap::new(),
38        }
39    }
40
41    /// Add metadata to the resolution result
42    pub fn with_metadata(mut self, key: String, value: String) -> Self {
43        self.metadata.insert(key, value);
44        self
45    }
46
47    /// Get the number of resolved packages
48    pub fn package_count(&self) -> usize {
49        self.packages.len()
50    }
51
52    /// Get package by name
53    pub fn get_package(&self, name: &str) -> Option<&Package> {
54        self.packages.iter().find(|p| p.name == name)
55    }
56
57    /// Get all package names
58    pub fn get_package_names(&self) -> Vec<String> {
59        self.packages.iter().map(|p| p.name.clone()).collect()
60    }
61
62    /// Check if a package is included in the resolution
63    pub fn contains_package(&self, name: &str) -> bool {
64        self.packages.iter().any(|p| p.name == name)
65    }
66
67    /// Get packages that match a pattern
68    pub fn find_packages(&self, pattern: &str) -> Vec<&Package> {
69        self.packages
70            .iter()
71            .filter(|p| self.matches_pattern(&p.name, pattern))
72            .collect()
73    }
74
75    /// Simple pattern matching (supports * wildcard)
76    fn matches_pattern(&self, text: &str, pattern: &str) -> bool {
77        if pattern == "*" {
78            return true;
79        }
80
81        if pattern.contains('*') {
82            // Convert to regex pattern
83            let regex_pattern = pattern.replace("*", ".*");
84            if let Ok(regex) = regex::Regex::new(&format!("^{}$", regex_pattern)) {
85                return regex.is_match(text);
86            }
87        }
88
89        text == pattern
90    }
91
92    /// Get resolution summary
93    pub fn get_summary(&self) -> ResolutionSummary {
94        let mut package_versions = HashMap::new();
95        let mut total_size = 0u64;
96
97        for package in &self.packages {
98            if let Some(ref version) = package.version {
99                package_versions.insert(package.name.clone(), version.as_str().to_string());
100            } else {
101                package_versions.insert(package.name.clone(), "latest".to_string());
102            }
103
104            // Estimate package size (this would be more accurate with actual file sizes)
105            total_size += 1024 * 1024; // 1MB per package as estimate
106        }
107
108        ResolutionSummary {
109            package_count: self.packages.len(),
110            conflicts_resolved: self.conflicts_resolved,
111            resolution_time_ms: self.resolution_time_ms,
112            estimated_size_bytes: total_size,
113            package_versions,
114        }
115    }
116
117    /// Validate the resolution result
118    pub fn validate(&self) -> Result<(), String> {
119        // Check for duplicate packages
120        let mut seen_packages = std::collections::HashSet::new();
121        for package in &self.packages {
122            let key = match &package.version {
123                Some(version) => format!("{}-{}", package.name, version.as_str()),
124                None => package.name.clone(),
125            };
126
127            if seen_packages.contains(&key) {
128                return Err(format!("Duplicate package in resolution: {}", key));
129            }
130            seen_packages.insert(key);
131        }
132
133        // Validate package definitions
134        for package in &self.packages {
135            if let Err(e) = package.validate() {
136                return Err(format!("Invalid package {}: {}", package.name, e));
137            }
138        }
139
140        Ok(())
141    }
142
143    /// Convert to a format suitable for environment generation
144    pub fn to_environment_spec(&self) -> EnvironmentSpec {
145        let mut packages = Vec::new();
146        let mut environment_vars = HashMap::new();
147
148        for package in &self.packages {
149            let package_spec = PackageSpec {
150                name: package.name.clone(),
151                version: package.version.as_ref().map(|v| v.as_str().to_string()),
152                requirements: package.requires.clone(),
153                tools: package.tools.clone(),
154            };
155            packages.push(package_spec);
156
157            // Add package-specific environment variables
158            if let Some(ref commands) = package.commands {
159                environment_vars.insert(
160                    format!("{}_COMMANDS", package.name.to_uppercase()),
161                    commands.clone(),
162                );
163            }
164        }
165
166        EnvironmentSpec {
167            packages,
168            environment_vars,
169            metadata: self.metadata.clone(),
170        }
171    }
172}
173
174/// Summary of a resolution result
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct ResolutionSummary {
177    /// Number of packages in the resolution
178    pub package_count: usize,
179    /// Whether conflicts were resolved
180    pub conflicts_resolved: bool,
181    /// Resolution time in milliseconds
182    pub resolution_time_ms: u64,
183    /// Estimated total size in bytes
184    pub estimated_size_bytes: u64,
185    /// Package versions included
186    pub package_versions: HashMap<String, String>,
187}
188
189/// Environment specification generated from resolution
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct EnvironmentSpec {
192    /// Packages in the environment
193    pub packages: Vec<PackageSpec>,
194    /// Environment variables to set
195    pub environment_vars: HashMap<String, String>,
196    /// Additional metadata
197    pub metadata: HashMap<String, String>,
198}
199
200/// Package specification for environment
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct PackageSpec {
203    /// Package name
204    pub name: String,
205    /// Package version
206    pub version: Option<String>,
207    /// Package requirements
208    pub requirements: Vec<String>,
209    /// Package tools
210    pub tools: Vec<String>,
211}
212
213impl EnvironmentSpec {
214    /// Get all package names
215    pub fn get_package_names(&self) -> Vec<String> {
216        self.packages.iter().map(|p| p.name.clone()).collect()
217    }
218
219    /// Get environment variable by name
220    pub fn get_env_var(&self, name: &str) -> Option<&String> {
221        self.environment_vars.get(name)
222    }
223
224    /// Add an environment variable
225    pub fn add_env_var(&mut self, name: String, value: String) {
226        self.environment_vars.insert(name, value);
227    }
228
229    /// Get all tools from all packages
230    pub fn get_all_tools(&self) -> Vec<String> {
231        let mut all_tools = Vec::new();
232        for package in &self.packages {
233            all_tools.extend(package.tools.iter().cloned());
234        }
235        all_tools.sort();
236        all_tools.dedup();
237        all_tools
238    }
239
240    /// Generate shell script for environment setup
241    pub fn generate_shell_script(&self, shell: ShellType) -> String {
242        let mut script = String::new();
243
244        match shell {
245            ShellType::Bash => {
246                script.push_str("#!/bin/bash\n");
247                script.push_str("# Generated by rez-core\n\n");
248
249                for (name, value) in &self.environment_vars {
250                    script.push_str(&format!("export {}=\"{}\"\n", name, value));
251                }
252
253                // Add tools to PATH
254                let tools = self.get_all_tools();
255                if !tools.is_empty() {
256                    script.push_str("\n# Add tools to PATH\n");
257                    for tool in tools {
258                        script.push_str(&format!("export PATH=\"$PATH:/path/to/{}\"\n", tool));
259                    }
260                }
261            }
262            ShellType::Cmd => {
263                script.push_str("@echo off\n");
264                script.push_str("REM Generated by rez-core\n\n");
265
266                for (name, value) in &self.environment_vars {
267                    script.push_str(&format!("set {}={}\n", name, value));
268                }
269
270                // Add tools to PATH
271                let tools = self.get_all_tools();
272                if !tools.is_empty() {
273                    script.push_str("\nREM Add tools to PATH\n");
274                    for tool in tools {
275                        script.push_str(&format!("set PATH=%PATH%;C:\\path\\to\\{}\n", tool));
276                    }
277                }
278            }
279            ShellType::PowerShell => {
280                script.push_str("# Generated by rez-core\n\n");
281
282                for (name, value) in &self.environment_vars {
283                    script.push_str(&format!("$env:{} = \"{}\"\n", name, value));
284                }
285
286                // Add tools to PATH
287                let tools = self.get_all_tools();
288                if !tools.is_empty() {
289                    script.push_str("\n# Add tools to PATH\n");
290                    for tool in tools {
291                        script.push_str(&format!("$env:PATH += \";C:\\path\\to\\{}\"\n", tool));
292                    }
293                }
294            }
295        }
296
297        script
298    }
299}
300
301/// Supported shell types
302#[derive(Debug, Clone, PartialEq)]
303pub enum ShellType {
304    /// Bash shell
305    Bash,
306    /// Windows Command Prompt
307    Cmd,
308    /// PowerShell
309    PowerShell,
310}
311
312impl Default for ResolutionResult {
313    fn default() -> Self {
314        Self::new(Vec::new())
315    }
316}