|
8 | 8 | //! - Native: checks patches, vendor/, then ~/.pcb/cache |
9 | 9 | //! - WASM: only checks vendor/ (everything must be pre-vendored) |
10 | 10 |
|
11 | | -use std::collections::{BTreeMap, HashMap}; |
| 11 | +use std::collections::{BTreeMap, HashMap, HashSet}; |
12 | 12 | use std::path::{Path, PathBuf}; |
13 | 13 |
|
14 | 14 | use semver::Version; |
@@ -360,6 +360,92 @@ impl PackagePathResolver for NativePathResolver { |
360 | 360 | } |
361 | 361 | } |
362 | 362 |
|
| 363 | +/// Result of dependency resolution. |
| 364 | +/// |
| 365 | +/// This is a data-only type defined in core so it can be referenced by |
| 366 | +/// `EvalContext` / `EvalOutput`. Construction happens in `pcb-zen` which |
| 367 | +/// performs the actual resolution. |
| 368 | +#[derive(Debug, Clone)] |
| 369 | +pub struct ResolutionResult { |
| 370 | + /// Snapshot of workspace info at the time of resolution |
| 371 | + pub workspace_info: WorkspaceInfo, |
| 372 | + /// Map from Package Root (Absolute Path) -> Import URL -> Resolved Absolute Path |
| 373 | + pub package_resolutions: HashMap<PathBuf, BTreeMap<String, PathBuf>>, |
| 374 | + /// Package dependencies in the build closure: ModuleLine -> Version |
| 375 | + pub closure: HashMap<ModuleLine, Version>, |
| 376 | + /// Asset dependencies: (module_path, ref) -> resolved_path |
| 377 | + pub assets: HashMap<(String, String), PathBuf>, |
| 378 | + /// Whether the lockfile (pcb.sum) was updated during resolution |
| 379 | + pub lockfile_changed: bool, |
| 380 | +} |
| 381 | + |
| 382 | +/// Transitive dependency closure for a package |
| 383 | +#[derive(Debug, Clone, Default)] |
| 384 | +pub struct PackageClosure { |
| 385 | + pub local_packages: HashSet<String>, |
| 386 | + pub remote_packages: HashSet<(String, String)>, |
| 387 | + pub assets: HashSet<(String, String)>, |
| 388 | +} |
| 389 | + |
| 390 | +impl ResolutionResult { |
| 391 | + /// Compute the transitive dependency closure for a package. |
| 392 | + pub fn package_closure(&self, package_url: &str) -> PackageClosure { |
| 393 | + let workspace_info = &self.workspace_info; |
| 394 | + let mut closure = PackageClosure::default(); |
| 395 | + let mut visited: HashSet<String> = HashSet::new(); |
| 396 | + let mut stack: Vec<String> = vec![package_url.to_string()]; |
| 397 | + |
| 398 | + let cache = &workspace_info.cache_dir; |
| 399 | + let vendor_base = workspace_info.root.join("vendor"); |
| 400 | + |
| 401 | + let get_pkg_root = |module_path: &str, version: &str| -> PathBuf { |
| 402 | + let vendor_path = vendor_base.join(module_path).join(version); |
| 403 | + if vendor_path.exists() { |
| 404 | + vendor_path |
| 405 | + } else { |
| 406 | + cache.join(module_path).join(version) |
| 407 | + } |
| 408 | + }; |
| 409 | + |
| 410 | + while let Some(url) = stack.pop() { |
| 411 | + if !visited.insert(url.clone()) { |
| 412 | + continue; |
| 413 | + } |
| 414 | + |
| 415 | + if let Some(pkg) = workspace_info.packages.get(&url) { |
| 416 | + closure.local_packages.insert(url.clone()); |
| 417 | + for dep_url in pkg.config.dependencies.keys() { |
| 418 | + stack.push(dep_url.clone()); |
| 419 | + } |
| 420 | + for (asset_url, asset_spec) in &pkg.config.assets { |
| 421 | + if let Ok(ref_str) = crate::extract_asset_ref_strict(asset_spec) { |
| 422 | + closure.assets.insert((asset_url.clone(), ref_str)); |
| 423 | + } |
| 424 | + } |
| 425 | + } else if let Some((line, version)) = self.closure.iter().find(|(l, _)| l.path == url) { |
| 426 | + let version_str = version.to_string(); |
| 427 | + closure |
| 428 | + .remote_packages |
| 429 | + .insert((url.clone(), version_str.clone())); |
| 430 | + let pkg_root = get_pkg_root(&line.path, &version_str); |
| 431 | + if let Some(deps) = self.package_resolutions.get(&pkg_root) { |
| 432 | + for dep_url in deps.keys() { |
| 433 | + stack.push(dep_url.clone()); |
| 434 | + } |
| 435 | + } |
| 436 | + } |
| 437 | + } |
| 438 | + |
| 439 | + for (asset_path, asset_ref) in self.assets.keys() { |
| 440 | + closure |
| 441 | + .assets |
| 442 | + .insert((asset_path.clone(), asset_ref.clone())); |
| 443 | + } |
| 444 | + |
| 445 | + closure |
| 446 | + } |
| 447 | +} |
| 448 | + |
363 | 449 | #[cfg(test)] |
364 | 450 | mod tests { |
365 | 451 | use super::*; |
@@ -432,6 +518,7 @@ mod tests { |
432 | 518 |
|
433 | 519 | let workspace = WorkspaceInfo { |
434 | 520 | root: workspace_root.clone(), |
| 521 | + cache_dir: PathBuf::new(), |
435 | 522 | config: None, |
436 | 523 | packages, |
437 | 524 | lockfile: None, |
|
0 commit comments