1use crate::constraint::{Constraint, Op};
7use crate::intervals;
8use crate::package::{Origin, Package};
9use crate::platform::is_platform_package;
10use crate::repository::{is_package_acceptable, ComposerRepository, RepoError};
11use crate::root::RootAlias;
12use crate::version::{regex, stability_rank};
13use pcre2::bytes::Regex;
14use std::collections::{BTreeMap, BTreeSet, HashMap};
15use std::sync::OnceLock;
16
17#[derive(Debug, thiserror::Error)]
18#[error("{0}")]
19pub struct PoolError(pub String, pub crate::repository::RepoErrorKind);
20
21impl PoolError {
22 pub fn data(message: impl Into<String>) -> PoolError {
24 PoolError(message.into(), crate::repository::RepoErrorKind::Data)
25 }
26 pub fn is_transport(&self) -> bool {
27 self.1 == crate::repository::RepoErrorKind::Transport
28 }
29}
30
31impl From<RepoError> for PoolError {
35 fn from(e: RepoError) -> PoolError {
36 let kind = e.1;
37 PoolError(e.0, kind)
38 }
39}
40
41#[derive(Debug, Clone)]
43pub struct OrderedMap<V>(pub Vec<(String, V)>);
44
45impl<V> Default for OrderedMap<V> {
46 fn default() -> OrderedMap<V> {
47 OrderedMap(Vec::new())
48 }
49}
50
51impl<V> OrderedMap<V> {
52 pub fn get(&self, key: &str) -> Option<&V> {
53 self.0.iter().find(|(k, _)| k == key).map(|(_, v)| v)
54 }
55 pub fn contains(&self, key: &str) -> bool {
56 self.0.iter().any(|(k, _)| k == key)
57 }
58 pub fn insert(&mut self, key: &str, value: V) {
59 if let Some(slot) = self.0.iter_mut().find(|(k, _)| k == key) {
60 slot.1 = value;
61 } else {
62 self.0.push((key.to_owned(), value));
63 }
64 }
65 pub fn remove(&mut self, key: &str) -> Option<V> {
66 let pos = self.0.iter().position(|(k, _)| k == key)?;
67 Some(self.0.remove(pos).1)
68 }
69 pub fn len(&self) -> usize {
70 self.0.len()
71 }
72 pub fn is_empty(&self) -> bool {
73 self.0.is_empty()
74 }
75 pub fn iter(&self) -> impl Iterator<Item = (&String, &V)> {
76 self.0.iter().map(|(k, v)| (k, v))
77 }
78 pub fn keys(&self) -> impl Iterator<Item = &String> {
79 self.0.iter().map(|(k, _)| k)
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum UpdateMode {
86 OnlyListed,
87 ListedWithTransitiveDepsNoRootRequire,
88 ListedWithTransitiveDeps,
89}
90
91#[derive(Debug, Clone, Default)]
95pub struct Request {
96 pub locked_repository: Option<Vec<usize>>,
97 pub requires: OrderedMap<Constraint>,
98 pub pretty_requires: OrderedMap<String>,
100 pub fixed_packages: Vec<usize>,
101 pub locked_packages: Vec<usize>,
102 pub fixed_locked_packages: Vec<usize>,
103 pub update_allow_list: Vec<String>,
104 pub update_mode: Option<UpdateMode>,
105 pub restricted_packages: Option<Vec<String>>,
106}
107
108fn push_unique(list: &mut Vec<usize>, idx: usize) {
109 if !list.contains(&idx) {
110 list.push(idx);
111 }
112}
113
114impl Request {
115 pub fn new(locked_repository: Option<Vec<usize>>) -> Request {
116 Request {
117 locked_repository,
118 ..Request::default()
119 }
120 }
121
122 pub fn require_name(
123 &mut self,
124 name: &str,
125 constraint: Option<Constraint>,
126 ) -> Result<(), PoolError> {
127 let pretty = constraint
128 .as_ref()
129 .map_or_else(|| "*".to_owned(), |c| c.to_string());
130 self.require_name_pretty(name, constraint, &pretty)
131 }
132
133 pub fn require_name_pretty(
136 &mut self,
137 name: &str,
138 constraint: Option<Constraint>,
139 pretty: &str,
140 ) -> Result<(), PoolError> {
141 let name = name.to_lowercase();
142 let constraint = constraint.unwrap_or(Constraint::MatchAll);
143 if let Some(existing) = self.requires.get(&name) {
144 return Err(PoolError::data(format!(
145 "Overwriting requires seems like a bug ({name} {existing} => {constraint}, check why it is happening, might be a root alias"
146 )));
147 }
148 self.requires.insert(&name, constraint);
149 self.pretty_requires.insert(&name, pretty.to_owned());
150 Ok(())
151 }
152
153 pub fn pretty_require(&self, name: &str, filtered: &Constraint) -> String {
158 match (self.requires.get(name), self.pretty_requires.get(name)) {
159 (Some(original), Some(pretty)) if original == filtered => pretty.clone(),
160 _ => filtered.to_string(),
161 }
162 }
163
164 pub fn fix_package(&mut self, idx: usize) {
165 push_unique(&mut self.fixed_packages, idx);
166 }
167
168 pub fn lock_package(&mut self, idx: usize) {
169 push_unique(&mut self.locked_packages, idx);
170 }
171
172 pub fn fix_locked_package(&mut self, idx: usize) {
173 push_unique(&mut self.fixed_packages, idx);
174 push_unique(&mut self.fixed_locked_packages, idx);
175 }
176
177 pub fn unlock_package(&mut self, idx: usize) {
178 self.locked_packages.retain(|i| *i != idx);
179 }
180
181 pub fn set_update_allow_list(&mut self, list: Vec<String>, mode: UpdateMode) {
182 self.update_allow_list = list;
183 self.update_mode = Some(mode);
184 }
185
186 pub fn update_allow_transitive_dependencies(&self) -> bool {
187 self.update_mode != Some(UpdateMode::OnlyListed)
190 }
191
192 pub fn update_allow_transitive_root_dependencies(&self) -> bool {
193 self.update_mode == Some(UpdateMode::ListedWithTransitiveDeps)
194 }
195
196 pub fn is_fixed_package(&self, idx: usize) -> bool {
197 self.fixed_packages.contains(&idx)
198 }
199
200 pub fn is_locked_package(&self, idx: usize) -> bool {
201 self.locked_packages.contains(&idx) || self.fixed_locked_packages.contains(&idx)
202 }
203
204 pub fn locked_packages_all(&self) -> Vec<usize> {
206 let mut out = self.locked_packages.clone();
207 out.extend(self.fixed_locked_packages.iter().copied());
208 out
209 }
210
211 pub fn fixed_or_locked_packages(&self) -> Vec<usize> {
213 let mut out = self.fixed_packages.clone();
214 out.extend(self.locked_packages.iter().copied());
215 out
216 }
217}
218
219pub enum Repository {
221 Root(Vec<usize>),
223 Platform(Vec<usize>),
225 Composer(Box<ComposerRepository>),
226 Path(crate::path_repo::PathRepository),
228 Locked(Vec<usize>),
230}
231
232pub const INSTALLER_IGNORED_TYPES: &[&str] = &["php-ext", "php-ext-zend"];
237
238pub struct RepositorySet {
240 pub root_aliases: BTreeMap<String, BTreeMap<String, (String, String)>>,
242 pub root_references: BTreeMap<String, String>,
243 pub acceptable_stabilities: BTreeMap<String, i32>,
244 pub stability_flags: BTreeMap<String, i32>,
245 pub root_requires: OrderedMap<Constraint>,
246 pub temporary_constraints: BTreeMap<String, TemporaryConstraint>,
247 pub repositories: Vec<Repository>,
248}
249
250impl RepositorySet {
251 pub fn new(
252 minimum_stability: &str,
253 stability_flags: BTreeMap<String, i32>,
254 root_aliases: &[RootAlias],
255 root_references: BTreeMap<String, String>,
256 root_requires: OrderedMap<Constraint>,
257 temporary_constraints: BTreeMap<String, TemporaryConstraint>,
258 ) -> RepositorySet {
259 let mut aliases: BTreeMap<String, BTreeMap<String, (String, String)>> = BTreeMap::new();
260 for a in root_aliases {
261 aliases.entry(a.package.clone()).or_default().insert(
262 a.version.clone(),
263 (a.alias.clone(), a.alias_normalized.clone()),
264 );
265 }
266 let min = stability_rank(minimum_stability);
267 let mut acceptable = BTreeMap::new();
268 for s in ["stable", "RC", "beta", "alpha", "dev"] {
269 let rank = stability_rank(s);
270 if rank <= min {
271 acceptable.insert(s.to_owned(), rank);
272 }
273 }
274 let mut requires = OrderedMap::default();
275 for (name, c) in root_requires.iter() {
276 if !is_platform_package(name) {
277 requires.insert(name, c.clone());
278 }
279 }
280 RepositorySet {
281 root_aliases: aliases,
282 root_references,
283 acceptable_stabilities: acceptable,
284 stability_flags,
285 root_requires: requires,
286 temporary_constraints,
287 repositories: Vec::new(),
288 }
289 }
290
291 pub fn add_repository(&mut self, repo: Repository) {
292 self.repositories.push(repo);
293 }
294
295 pub fn create_pool(
299 &self,
300 request: &mut Request,
301 arena: &mut Vec<Package>,
302 pre_pool: Option<&PrePoolFilter>,
303 ignored_types: &[&str],
304 ) -> Result<Pool, PoolError> {
305 let mut builder = PoolBuilder::new(self);
306 builder.ignored_types = ignored_types.iter().map(|t| (*t).to_owned()).collect();
307 builder.build_pool(&self.repositories, request, arena, pre_pool)
308 }
309}
310
311#[derive(Debug, Clone)]
316pub struct PrePoolFilter {
317 pub symfony_require: String,
319 pub symfony: Constraint,
320 pub root_constraints: BTreeMap<String, Constraint>,
322 pub versions: crate::flex_filter::FlexVersions,
323}
324
325#[derive(Debug, Clone)]
329pub struct TemporaryConstraint {
330 pub pretty: String,
331 pub constraint: Constraint,
332}
333
334impl std::fmt::Display for TemporaryConstraint {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 f.write_str(&self.pretty)
337 }
338}
339
340pub type FilterListRemoved = BTreeMap<String, Vec<(String, Vec<crate::repository::FilterEntry>)>>;
343
344#[derive(Debug, Clone, Default)]
345pub struct Pool {
346 pub identity: u64,
349 pub packages: Vec<usize>,
351 id_of: HashMap<usize, usize>,
352 package_by_name: HashMap<String, Vec<usize>>,
353 pub unacceptable_fixed_or_locked: Vec<usize>,
354 pub warnings: Vec<String>,
355 pub flex_notice: Option<String>,
358 pub filter_list_removed: FilterListRemoved,
361 pub security_removed: BTreeMap<String, Vec<(String, Vec<String>)>>,
364 pub abandoned_removed: BTreeMap<String, BTreeMap<String, String>>,
366 pub removed_versions: BTreeMap<String, BTreeMap<String, String>>,
369 pub removed_versions_by_package: BTreeMap<usize, BTreeMap<String, String>>,
372}
373
374impl Pool {
375 pub fn new(packages: Vec<usize>, unacceptable: Vec<usize>, arena: &[Package]) -> Pool {
376 static NEXT_IDENTITY: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
377 let mut pool = Pool {
378 flex_notice: None,
379 identity: NEXT_IDENTITY.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
380 packages: Vec::with_capacity(packages.len()),
381 id_of: HashMap::new(),
382 package_by_name: HashMap::new(),
383 unacceptable_fixed_or_locked: unacceptable,
384 warnings: Vec::new(),
385 filter_list_removed: BTreeMap::new(),
386 security_removed: BTreeMap::new(),
387 abandoned_removed: BTreeMap::new(),
388 removed_versions: BTreeMap::new(),
389 removed_versions_by_package: BTreeMap::new(),
390 };
391 for idx in packages {
392 pool.packages.push(idx);
393 let id = pool.packages.len();
394 pool.id_of.insert(idx, id);
395 for name in arena[idx].names(true) {
396 pool.package_by_name.entry(name).or_default().push(id);
397 }
398 }
399 pool
400 }
401
402 pub fn len(&self) -> usize {
403 self.packages.len()
404 }
405
406 pub fn is_empty(&self) -> bool {
407 self.packages.is_empty()
408 }
409
410 pub fn package_by_id(&self, id: usize) -> usize {
412 self.packages[id - 1]
413 }
414
415 pub fn id_of(&self, arena_idx: usize) -> Option<usize> {
416 self.id_of.get(&arena_idx).copied()
417 }
418
419 pub fn literal_to_package(&self, literal: i64) -> usize {
420 self.package_by_id(literal.unsigned_abs() as usize)
421 }
422
423 pub fn what_provides(
425 &self,
426 arena: &[Package],
427 name: &str,
428 constraint: Option<&Constraint>,
429 ) -> Vec<usize> {
430 let Some(candidates) = self.package_by_name.get(name) else {
431 return Vec::new();
432 };
433 candidates
434 .iter()
435 .copied()
436 .filter(|id| Self::matches(&arena[self.packages[id - 1]], name, constraint))
437 .collect()
438 }
439
440 pub fn matches(candidate: &Package, name: &str, constraint: Option<&Constraint>) -> bool {
442 if candidate.name == name {
443 return match constraint {
444 None => true,
445 Some(c) => c.matches_version(&candidate.version),
446 };
447 }
448 let provides = &candidate.provides;
449 let replaces = &candidate.replaces;
450 if replaces.has_numeric_keys() || provides.has_numeric_keys() {
455 for link in provides.iter().chain(replaces.iter()) {
456 if link.target == name && constraint.is_none_or(|c| c.matches(&link.constraint)) {
457 return true;
458 }
459 }
460 return false;
461 }
462 if let Some(link) = provides.get(name) {
463 if constraint.is_none_or(|c| c.matches(&link.constraint)) {
464 return true;
465 }
466 }
467 if let Some(link) = replaces.get(name) {
468 if constraint.is_none_or(|c| c.matches(&link.constraint)) {
469 return true;
470 }
471 }
472 false
473 }
474
475 pub fn is_filter_list_removed(&self, name: &str, version: &str) -> bool {
477 self.filter_list_removed
478 .get(name)
479 .is_some_and(|versions| versions.iter().any(|(v, _)| v == version))
480 }
481
482 pub fn with_packages(&self, kept: Vec<usize>, arena: &[Package]) -> Pool {
485 let mut pool = Pool::new(kept, self.unacceptable_fixed_or_locked.clone(), arena);
486 pool.warnings = self.warnings.clone();
487 pool.filter_list_removed = self.filter_list_removed.clone();
488 pool.security_removed = self.security_removed.clone();
489 pool.abandoned_removed = self.abandoned_removed.clone();
490 pool.removed_versions = self.removed_versions.clone();
491 pool.removed_versions_by_package = self.removed_versions_by_package.clone();
492 pool
493 }
494
495 pub fn is_security_removed(&self, name: &str, constraint: &Constraint) -> bool {
497 !self.security_advisory_ids(name, constraint).is_empty()
498 }
499
500 pub fn security_advisory_ids(&self, name: &str, constraint: &Constraint) -> Vec<String> {
503 self.security_removed
504 .get(name)
505 .and_then(|m| {
506 m.iter()
507 .find(|(v, _)| constraint.matches(&Constraint::new(Op::Eq, v.clone())))
508 .map(|(_, ids)| ids.clone())
509 })
510 .unwrap_or_default()
511 }
512
513 pub fn is_abandoned_removed(&self, name: &str, constraint: &Constraint) -> bool {
515 self.abandoned_removed.get(name).is_some_and(|m| {
516 m.keys()
517 .any(|v| constraint.matches(&Constraint::new(Op::Eq, v.clone())))
518 })
519 }
520
521 pub fn is_filter_list_removed_version(&self, name: &str, constraint: &Constraint) -> bool {
523 self.filter_list_removed.get(name).is_some_and(|versions| {
524 versions
525 .iter()
526 .any(|(v, _)| constraint.matches(&Constraint::new(Op::Eq, v.clone())))
527 })
528 }
529
530 pub fn filter_list_entries_text(
533 &self,
534 name: &str,
535 constraint: &Constraint,
536 ) -> Vec<(String, String)> {
537 let mut lists: Vec<(String, Vec<String>)> = Vec::new();
538 let mut seen: Vec<&crate::repository::FilterEntry> = Vec::new();
541 if let Some(versions) = self.filter_list_removed.get(name) {
542 for (v, entries) in versions {
543 if !constraint.matches(&Constraint::new(Op::Eq, v.clone())) {
544 continue;
545 }
546 for e in entries {
547 if seen.iter().any(|s| s.same_entry(e)) {
548 continue;
549 }
550 seen.push(e);
551 let source = e
552 .source
553 .as_deref()
554 .filter(|s| !s.is_empty() && *s != "0")
555 .map(|s| format!(" reported by {s}"))
556 .unwrap_or_default();
557 let url = e
558 .url
559 .as_deref()
560 .filter(|s| !s.is_empty() && *s != "0")
561 .map(|s| format!(" (see {s})"))
562 .unwrap_or_default();
563 let reason = e
564 .reason
565 .as_deref()
566 .filter(|s| !s.is_empty() && *s != "0")
567 .map(|s| format!(" reason: {s}"))
568 .unwrap_or_default();
569 let text = format!("{source}{url}{reason}");
570 match lists.iter_mut().find(|(l, _)| *l == e.list_name) {
571 Some((_, texts)) => texts.push(text),
572 None => lists.push((e.list_name.clone(), vec![text])),
573 }
574 }
575 }
576 }
577 lists
578 .into_iter()
579 .map(|(list, texts)| {
580 let action = if list == "malware" {
581 "flagged as "
582 } else {
583 "filtered by "
584 };
585 (list.clone(), format!("{action}{list}{}", texts.join(", ")))
586 })
587 .collect()
588 }
589
590 pub fn is_unacceptable_fixed_or_locked(&self, arena_idx: usize) -> bool {
591 self.unacceptable_fixed_or_locked.contains(&arena_idx)
592 }
593
594 pub fn removed_versions(&self, name: &str, constraint: &Constraint) -> Vec<(String, String)> {
597 let Some(versions) = self.removed_versions.get(name) else {
598 return Vec::new();
599 };
600 versions
601 .iter()
602 .filter(|(v, _)| constraint.matches(&Constraint::new(Op::Eq, (*v).clone())))
603 .map(|(v, p)| (v.clone(), p.clone()))
604 .collect()
605 }
606
607 pub fn removed_versions_by_package(&self, arena_idx: usize) -> Vec<(String, String)> {
609 self.removed_versions_by_package
610 .get(&arena_idx)
611 .map(|m| m.iter().map(|(v, p)| (v.clone(), p.clone())).collect())
612 .unwrap_or_default()
613 }
614}
615
616const LOAD_BATCH_SIZE: usize = 50;
617
618pub fn package_name_regexp(pattern: &str) -> Regex {
620 let quoted = crate::version::preg_quote(pattern).replace("\\*", ".*");
621 pcre2::bytes::RegexBuilder::new()
622 .caseless(true)
623 .build(&format!("^{quoted}$"))
624 .unwrap_or_else(|e| panic!("pattern {pattern}: {e}"))
625}
626
627struct PoolBuilder<'a> {
628 set: &'a RepositorySet,
629 alias_map: HashMap<usize, Vec<(usize, usize)>>,
631 packages_to_load: OrderedMap<Constraint>,
632 loaded_packages: BTreeMap<String, Constraint>,
633 loaded_per_repo: BTreeMap<usize, BTreeMap<String, BTreeSet<String>>>,
634 packages: Vec<Option<usize>>,
636 unacceptable: Vec<usize>,
637 update_allow_list: Vec<String>,
638 update_allow_patterns: Vec<Regex>,
639 skipped_load: BTreeMap<String, Vec<usize>>,
640 ignored_types: Vec<String>,
641 allowed_types: Option<Vec<String>>,
642 restricted: Option<BTreeSet<String>>,
643 path_repo_unlocked: BTreeSet<String>,
644 max_extended_reqs: BTreeSet<String>,
645 update_allow_warned: BTreeSet<String>,
646 warnings: Vec<String>,
647}
648
649impl<'a> PoolBuilder<'a> {
650 fn new(set: &'a RepositorySet) -> PoolBuilder<'a> {
651 PoolBuilder {
652 set,
653 alias_map: HashMap::new(),
654 packages_to_load: OrderedMap::default(),
655 loaded_packages: BTreeMap::new(),
656 loaded_per_repo: BTreeMap::new(),
657 packages: Vec::new(),
658 unacceptable: Vec::new(),
659 update_allow_list: Vec::new(),
660 update_allow_patterns: Vec::new(),
661 skipped_load: BTreeMap::new(),
662 ignored_types: Vec::new(),
663 allowed_types: None,
664 restricted: None,
665 path_repo_unlocked: BTreeSet::new(),
666 max_extended_reqs: BTreeSet::new(),
667 update_allow_warned: BTreeSet::new(),
668 warnings: Vec::new(),
669 }
670 }
671
672 fn loaded_packages_in_pool(&self) -> Vec<usize> {
673 self.packages.iter().flatten().copied().collect()
674 }
675
676 fn build_pool(
677 &mut self,
678 repositories: &[Repository],
679 request: &mut Request,
680 arena: &mut Vec<Package>,
681 pre_pool: Option<&PrePoolFilter>,
682 ) -> Result<Pool, PoolError> {
683 self.restricted = request
684 .restricted_packages
685 .as_ref()
686 .map(|l| l.iter().cloned().collect());
687
688 if !request.update_allow_list.is_empty() {
689 self.update_allow_list = request.update_allow_list.clone();
690 self.update_allow_patterns = self
691 .update_allow_list
692 .iter()
693 .map(|p| package_name_regexp(p))
694 .collect();
695 self.warn_about_non_matching_update_allow_list(request, arena)?;
696 let Some(locked) = request.locked_repository.clone() else {
697 return Err(PoolError::data(
698 "No lock repo present and yet a partial update was requested.",
699 ));
700 };
701 for locked_idx in locked {
702 if !self.is_update_allowed(&arena[locked_idx]) {
703 let p = &arena[locked_idx];
704 self.skipped_load
705 .entry(p.name.clone())
706 .or_default()
707 .push(locked_idx);
708 for link in p.replaces.iter() {
709 self.skipped_load
710 .entry(link.target.clone())
711 .or_default()
712 .push(locked_idx);
713 }
714 if p.dist.as_ref().is_some_and(|d| d.kind == "path") {
715 let symlink = p
716 .raw
717 .get("transport-options")
718 .and_then(|t| t.get("symlink"))
719 .cloned();
720 if symlink != Some(serde_json::Value::Bool(false)) {
721 self.path_repo_unlocked.insert(p.name.clone());
722 continue;
723 }
724 }
725 request.lock_package(locked_idx);
726 }
727 }
728 }
729
730 for idx in request.fixed_or_locked_packages() {
731 let (name, replaces, names, stability, origin) = {
732 let p = &arena[idx];
733 (
734 p.name.clone(),
735 p.replaces
736 .iter()
737 .map(|l| l.target.clone())
738 .collect::<Vec<_>>(),
739 p.names(true),
740 p.stability,
741 p.origin,
742 )
743 };
744 self.loaded_packages.insert(name, Constraint::MatchAll);
745 for target in replaces {
746 self.loaded_packages.insert(target, Constraint::MatchAll);
747 }
748 if matches!(origin, Origin::Root | Origin::Platform)
749 || is_package_acceptable(
750 &self.set.acceptable_stabilities,
751 &self.set.stability_flags,
752 &names,
753 stability,
754 )
755 {
756 self.load_package(request, idx, false, arena);
757 } else {
758 self.unacceptable.push(idx);
759 }
760 }
761
762 let requires: Vec<(String, Constraint)> = request.requires.0.clone();
763 for (name, constraint) in &requires {
764 if self.loaded_packages.contains_key(name) {
765 continue;
766 }
767 self.packages_to_load.insert(name, constraint.clone());
768 self.max_extended_reqs.insert(name.clone());
769 }
770 let already: Vec<String> = self
771 .packages_to_load
772 .keys()
773 .filter(|n| self.loaded_packages.contains_key(*n))
774 .cloned()
775 .collect();
776 for name in already {
777 self.packages_to_load.remove(&name);
778 }
779
780 while !self.packages_to_load.is_empty() {
781 self.load_packages_marked_for_loading(request, repositories, arena)?;
782 }
783
784 if !self.set.temporary_constraints.is_empty() {
785 let entries: Vec<(usize, usize)> = self
786 .packages
787 .iter()
788 .enumerate()
789 .filter_map(|(i, p)| p.map(|idx| (i, idx)))
790 .collect();
791 for (i, idx) in entries {
792 if arena[idx].is_alias() {
793 continue;
794 }
795 for name in arena[idx].names(true) {
796 let Some(constraint) = self.set.temporary_constraints.get(&name) else {
797 continue;
798 };
799 let mut package_and_aliases: Vec<(usize, usize)> = vec![(i, idx)];
800 if let Some(aliases) = self.alias_map.get(&idx) {
801 package_and_aliases.extend(aliases.iter().copied());
802 }
803 let found = package_and_aliases
804 .iter()
805 .any(|(_, p)| constraint.constraint.matches_version(&arena[*p].version));
806 if !found {
807 for (pool_index, _) in package_and_aliases {
808 self.packages[pool_index] = None;
809 }
810 }
811 }
812 }
813 }
814
815 let mut packages = self.loaded_packages_in_pool();
816 let mut flex_notice = None;
819 if let Some(f) = pre_pool {
820 let mut locked: BTreeMap<String, Vec<String>> = BTreeMap::new();
821 for idx in request.fixed_or_locked_packages() {
822 let p = &arena[idx];
823 let entry = locked.entry(p.name.clone()).or_default();
824 entry.push(p.version.clone());
825 if let Some(base) = p.alias_of {
826 entry.push(arena[base].version.clone());
827 }
828 }
829 let (kept, restricting) = crate::flex_filter::remove_legacy_packages(
830 &packages,
831 arena,
832 &f.root_constraints,
833 &locked,
834 &f.symfony,
835 &f.versions,
836 );
837 if std::env::var_os("VIVACITY_TRACE").is_some() {
838 eprintln!(
839 "trace: flex filter {} → {} package versions (require {}, {} splits)",
840 packages.len(),
841 kept.len(),
842 f.symfony_require,
843 f.versions.splits.len()
844 );
845 }
846 packages = kept;
847 if restricting {
848 flex_notice = Some(format!(
849 "Restricting packages listed in \"symfony/symfony\" to \"{}\"",
850 f.symfony_require
851 ));
852 }
853 }
854 let mut pool = Pool::new(packages, std::mem::take(&mut self.unacceptable), arena);
855 pool.warnings = std::mem::take(&mut self.warnings);
856 pool.flex_notice = flex_notice;
857 Ok(pool)
858 }
859
860 fn mark_package_name_for_loading(
862 &mut self,
863 request: &Request,
864 name: &str,
865 constraint: &Constraint,
866 ) {
867 if is_platform_package(name) {
868 return;
869 }
870 if self.max_extended_reqs.contains(name) {
871 return;
872 }
873 let mut constraint = constraint.clone();
874 if let Some(root) = request.requires.get(name) {
875 if !intervals::is_subset_of(&constraint, root) {
876 constraint = root.clone();
877 }
878 }
879 if !self.loaded_packages.contains_key(name) {
880 if let Some(pending) = self.packages_to_load.get(name) {
881 if intervals::is_subset_of(&constraint, pending) {
882 return;
883 }
884 constraint = intervals::compact_constraint(&Constraint::create(
885 vec![pending.clone(), constraint],
886 false,
887 ));
888 }
889 self.packages_to_load.insert(name, constraint);
890 return;
891 }
892 let loaded = &self.loaded_packages[name];
893 if intervals::is_subset_of(&constraint, loaded) {
894 return;
895 }
896 let merged = intervals::compact_constraint(&Constraint::create(
897 vec![loaded.clone(), constraint],
898 false,
899 ));
900 self.packages_to_load.insert(name, merged);
901 self.loaded_packages.remove(name);
902 }
903
904 fn load_packages_marked_for_loading(
906 &mut self,
907 request: &mut Request,
908 repositories: &[Repository],
909 arena: &mut Vec<Package>,
910 ) -> Result<(), PoolError> {
911 let names: Vec<String> = self.packages_to_load.keys().cloned().collect();
912 for name in names {
913 if let Some(restricted) = &self.restricted {
914 if !restricted.contains(&name) {
915 self.packages_to_load.remove(&name);
916 continue;
917 }
918 }
919 let c = self
920 .packages_to_load
921 .get(&name)
922 .cloned()
923 .unwrap_or(Constraint::MatchAll);
924 self.loaded_packages.insert(name, c);
925 }
926 let mut remaining: Vec<(String, Constraint)> = std::mem::take(&mut self.packages_to_load).0;
927 for (repo_index, repository) in repositories.iter().enumerate() {
928 if matches!(repository, Repository::Platform(_) | Repository::Locked(_)) {
929 continue;
930 }
931 if remaining.is_empty() {
932 break;
933 }
934 let batches: Vec<Vec<(String, Constraint)>> = remaining
935 .chunks(LOAD_BATCH_SIZE)
936 .map(|c| c.to_vec())
937 .collect();
938 let mut kept: Vec<Vec<(String, Constraint)>> = Vec::new();
939 for batch in batches {
940 let empty = BTreeMap::new();
941 let already = self.loaded_per_repo.get(&repo_index).unwrap_or(&empty);
942 let (names_found, ids) = match repository {
943 Repository::Composer(repo) => repo.load_packages(
944 &batch,
945 &self.set.acceptable_stabilities,
946 &self.set.stability_flags,
947 already,
948 Origin::Repository(repo_index),
949 arena,
950 )?,
951 Repository::Root(members) => array_repository_load_packages(
952 members,
953 &batch,
954 &self.set.acceptable_stabilities,
955 &self.set.stability_flags,
956 already,
957 arena,
958 ),
959 Repository::Path(repo) => array_repository_load_packages(
960 &repo.members,
961 &batch,
962 &self.set.acceptable_stabilities,
963 &self.set.stability_flags,
964 already,
965 arena,
966 ),
967 Repository::Platform(_) | Repository::Locked(_) => unreachable!(),
968 };
969 let mut batch = batch;
970 batch.retain(|(n, _)| !names_found.contains(n));
971 kept.push(batch);
972 for idx in ids {
973 let (name, version, package_type) = {
974 let p = &arena[idx];
975 (p.name.clone(), p.version.clone(), p.package_type.clone())
976 };
977 self.loaded_per_repo
978 .entry(repo_index)
979 .or_default()
980 .entry(name.clone())
981 .or_default()
982 .insert(version);
983 if self.ignored_types.contains(&package_type)
984 || self
985 .allowed_types
986 .as_ref()
987 .is_some_and(|a| !a.contains(&package_type))
988 {
989 continue;
990 }
991 let propagate = !self.path_repo_unlocked.contains(&name);
992 self.load_package(request, idx, propagate, arena);
993 }
994 }
995 remaining = kept.into_iter().flatten().collect();
996 }
997 Ok(())
998 }
999
1000 fn load_package(
1002 &mut self,
1003 request: &mut Request,
1004 idx: usize,
1005 propagate_update: bool,
1006 arena: &mut Vec<Package>,
1007 ) {
1008 let index = self.packages.len();
1009 self.packages.push(Some(idx));
1010 if let Some(base) = arena[idx].alias_of {
1011 self.alias_map.entry(base).or_default().push((index, idx));
1012 }
1013 let name = arena[idx].name.clone();
1014 if let Some(reference) = self.set.root_references.get(&name) {
1015 if !request.is_locked_package(idx) && !request.is_fixed_package(idx) {
1016 set_source_dist_references(arena, idx, reference);
1017 }
1018 }
1019 if propagate_update || self.path_repo_unlocked.contains(&name) {
1020 let version = arena[idx].version.clone();
1021 if let Some((alias, alias_normalized)) = self
1022 .set
1023 .root_aliases
1024 .get(&name)
1025 .and_then(|m| m.get(&version))
1026 {
1027 let base = arena[idx].alias_of.unwrap_or(idx);
1028 let mut alias_package = arena[base].alias(base, alias_normalized, alias);
1029 alias_package.root_package_alias = true;
1030 alias_package.origin = Origin::Detached;
1031 arena.push(alias_package);
1032 let alias_idx = arena.len() - 1;
1033 let new_index = self.packages.len();
1034 self.packages.push(Some(alias_idx));
1035 self.alias_map
1036 .entry(base)
1037 .or_default()
1038 .push((new_index, alias_idx));
1039 }
1040 }
1041 let requires: Vec<(String, Constraint)> = arena[idx]
1042 .requires
1043 .iter()
1044 .map(|l| (l.target.clone(), l.constraint.clone()))
1045 .collect();
1046 for (require, link_constraint) in requires {
1047 if self.skipped_load.contains_key(&require) {
1048 if propagate_update && request.update_allow_transitive_dependencies() {
1049 let skipped_root_requires =
1050 self.skipped_root_requires(request, &require, arena);
1051 if request.update_allow_transitive_root_dependencies()
1052 || skipped_root_requires.is_empty()
1053 {
1054 self.unlock_package(request, &require, arena);
1055 self.mark_package_name_for_loading(request, &require, &link_constraint);
1056 } else {
1057 self.warn_root_requires(&skipped_root_requires);
1058 }
1059 } else if self.path_repo_unlocked.contains(&require)
1060 && !self.loaded_packages.contains_key(&require)
1061 {
1062 self.mark_package_name_for_loading(request, &require, &link_constraint);
1063 }
1064 } else {
1065 self.mark_package_name_for_loading(request, &require, &link_constraint);
1066 }
1067 }
1068 if propagate_update && request.update_allow_transitive_dependencies() {
1069 let replaces: Vec<String> = arena[idx]
1070 .replaces
1071 .iter()
1072 .map(|l| l.target.clone())
1073 .collect();
1074 for replace in replaces {
1075 if self.loaded_packages.contains_key(&replace)
1076 && self.skipped_load.contains_key(&replace)
1077 {
1078 let skipped_root_requires =
1079 self.skipped_root_requires(request, &replace, arena);
1080 if request.update_allow_transitive_root_dependencies()
1081 || skipped_root_requires.is_empty()
1082 {
1083 self.unlock_package(request, &replace, arena);
1084 self.mark_package_name_for_loading_if_required(request, &replace, arena);
1085 } else {
1086 self.warn_root_requires(&skipped_root_requires);
1087 }
1088 }
1089 }
1090 }
1091 }
1092
1093 fn warn_root_requires(&mut self, root_requires: &[String]) {
1094 for root_require in root_requires {
1095 if self.update_allow_warned.insert(root_require.clone()) {
1096 self.warnings.push(format!(
1097 "Dependency {root_require} is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies."
1098 ));
1099 }
1100 }
1101 }
1102
1103 fn is_root_require(request: &Request, name: &str) -> bool {
1104 request.requires.contains(name)
1105 }
1106
1107 fn skipped_root_requires(
1109 &self,
1110 request: &Request,
1111 name: &str,
1112 arena: &[Package],
1113 ) -> Vec<String> {
1114 let Some(skipped) = self.skipped_load.get(name) else {
1115 return Vec::new();
1116 };
1117 if request.requires.contains(name) {
1118 return skipped
1119 .iter()
1120 .map(|idx| {
1121 let p = &arena[*idx];
1122 if p.name != name {
1123 format!("{} (via replace of {name})", p.name)
1124 } else {
1125 p.name.clone()
1126 }
1127 })
1128 .collect();
1129 }
1130 let mut matches = Vec::new();
1131 for idx in skipped {
1132 let p = &arena[*idx];
1133 if request.requires.contains(&p.name) {
1134 matches.push(p.name.clone());
1135 }
1136 for link in p.replaces.iter() {
1137 if request.requires.contains(&link.target) {
1138 if p.name != name {
1139 matches.push(format!("{} (via replace of {name})", p.name));
1140 } else {
1141 matches.push(p.name.clone());
1142 }
1143 break;
1144 }
1145 }
1146 }
1147 matches
1148 }
1149
1150 fn is_update_allowed(&self, package: &Package) -> bool {
1152 self.update_allow_patterns
1153 .iter()
1154 .any(|re| re.is_match(package.name.as_bytes()).unwrap_or(false))
1155 }
1156
1157 fn warn_about_non_matching_update_allow_list(
1159 &mut self,
1160 request: &Request,
1161 arena: &[Package],
1162 ) -> Result<(), PoolError> {
1163 let Some(locked) = &request.locked_repository else {
1164 return Err(PoolError::data(
1165 "No lock repo present and yet a partial update was requested.",
1166 ));
1167 };
1168 'patterns: for pattern in &self.update_allow_list.clone() {
1169 let mut matched_platform = false;
1170 let re = package_name_regexp(pattern);
1171 for idx in locked {
1172 if re.is_match(arena[*idx].name.as_bytes()).unwrap_or(false) {
1173 continue 'patterns;
1174 }
1175 }
1176 for name in request.requires.keys() {
1177 if re.is_match(name.as_bytes()).unwrap_or(false) {
1178 if is_platform_package(name) {
1179 matched_platform = true;
1180 continue;
1181 }
1182 continue 'patterns;
1183 }
1184 }
1185 if matched_platform {
1186 self.warnings.push(format!(
1187 "Pattern \"{pattern}\" listed for update matches platform packages, but these cannot be updated by Composer."
1188 ));
1189 } else if pattern.contains('*') {
1190 self.warnings.push(format!(
1191 "Pattern \"{pattern}\" listed for update does not match any locked packages."
1192 ));
1193 } else {
1194 self.warnings.push(format!(
1195 "Package \"{pattern}\" listed for update is not locked."
1196 ));
1197 }
1198 }
1199 Ok(())
1200 }
1201
1202 fn unlock_package(&mut self, request: &mut Request, name: &str, arena: &mut Vec<Package>) {
1204 let skipped: Vec<usize> = self.skipped_load.get(name).cloned().unwrap_or_default();
1205 for idx in skipped {
1206 let replacer_name = arena[idx].name.clone();
1207 if replacer_name != name
1208 && self.skipped_load.contains_key(&replacer_name)
1209 && (request.update_allow_transitive_root_dependencies()
1210 || (!Self::is_root_require(request, name)
1211 && !Self::is_root_require(request, &replacer_name)))
1212 {
1213 self.unlock_package(request, &replacer_name, arena);
1214 if Self::is_root_require(request, &replacer_name) {
1215 self.mark_package_name_for_loading(
1216 request,
1217 &replacer_name,
1218 &Constraint::MatchAll,
1219 );
1220 } else {
1221 for loaded in self.loaded_packages_in_pool() {
1222 let c = arena[loaded]
1223 .requires
1224 .get(&replacer_name)
1225 .map(|l| l.constraint.clone());
1226 if let Some(c) = c {
1227 self.mark_package_name_for_loading(request, &replacer_name, &c);
1228 }
1229 }
1230 }
1231 }
1232 }
1233 if self.path_repo_unlocked.contains(name) {
1234 let entries: Vec<(usize, usize)> = self
1235 .packages
1236 .iter()
1237 .enumerate()
1238 .filter_map(|(i, p)| p.map(|idx| (i, idx)))
1239 .filter(|(_, idx)| arena[*idx].name == name)
1240 .collect();
1241 for (index, idx) in entries {
1242 self.remove_loaded_package(idx, index, arena);
1243 }
1244 }
1245 self.skipped_load.remove(name);
1246 self.loaded_packages.remove(name);
1247 self.max_extended_reqs.remove(name);
1248 self.path_repo_unlocked.remove(name);
1249 for locked_idx in request.locked_packages_all() {
1250 if !arena[locked_idx].is_alias() && arena[locked_idx].name == name {
1251 if let Some(index) = self.packages.iter().position(|p| *p == Some(locked_idx)) {
1252 request.unlock_package(locked_idx);
1253 self.remove_loaded_package(locked_idx, index, arena);
1254 for fixed_or_locked in request.fixed_or_locked_packages() {
1255 if fixed_or_locked == locked_idx {
1256 continue;
1257 }
1258 if self.skipped_load.contains_key(&arena[fixed_or_locked].name) {
1259 let locked_name = arena[locked_idx].name.clone();
1260 let requires = arena[fixed_or_locked].requires.clone();
1261 if let Some(link) = requires.get(&locked_name) {
1262 let c = link.constraint.clone();
1263 self.mark_package_name_for_loading(request, &locked_name, &c);
1264 }
1265 let replaces = arena[locked_idx].replaces.clone();
1266 for replace in replaces.iter() {
1267 if requires.get(&replace.target).is_some()
1268 && self.skipped_load.contains_key(&replace.target)
1269 {
1270 self.unlock_package(request, &replace.target, arena);
1271 self.mark_package_name_for_loading(
1272 request,
1273 &replace.target,
1274 &replace.constraint,
1275 );
1276 }
1277 }
1278 }
1279 }
1280 }
1281 }
1282 }
1283 }
1284
1285 fn mark_package_name_for_loading_if_required(
1287 &mut self,
1288 request: &Request,
1289 name: &str,
1290 arena: &[Package],
1291 ) {
1292 if let Some(c) = request.requires.get(name).cloned() {
1293 self.mark_package_name_for_loading(request, name, &c);
1294 }
1295 for loaded in self.loaded_packages_in_pool() {
1296 let links: Vec<Constraint> = arena[loaded]
1297 .requires
1298 .iter()
1299 .filter(|l| l.target == name)
1300 .map(|l| l.constraint.clone())
1301 .collect();
1302 for c in links {
1303 self.mark_package_name_for_loading(request, name, &c);
1304 }
1305 }
1306 }
1307
1308 fn remove_loaded_package(&mut self, idx: usize, index: usize, arena: &[Package]) {
1310 let repo_index = match arena[idx].origin {
1311 Origin::Repository(i) => Some(i),
1312 _ => None,
1313 };
1314 let mut forget = |p: &Package| {
1315 if let Some(r) = repo_index {
1316 if let Some(by_name) = self
1317 .loaded_per_repo
1318 .get_mut(&r)
1319 .and_then(|m| m.get_mut(&p.name))
1320 {
1321 by_name.remove(&p.version);
1322 }
1323 }
1324 };
1325 forget(&arena[idx]);
1326 self.packages[index] = None;
1327 if let Some(aliases) = self.alias_map.remove(&idx) {
1328 for (alias_index, alias_idx) in aliases {
1329 forget(&arena[alias_idx]);
1330 self.packages[alias_index] = None;
1331 }
1332 }
1333 }
1334}
1335
1336pub fn array_repository_load_packages(
1338 members: &[usize],
1339 package_name_map: &[(String, Constraint)],
1340 acceptable: &BTreeMap<String, i32>,
1341 flags: &BTreeMap<String, i32>,
1342 already_loaded: &BTreeMap<String, BTreeSet<String>>,
1343 arena: &[Package],
1344) -> (Vec<String>, Vec<usize>) {
1345 let mut result: Vec<usize> = Vec::new();
1346 let mut names_found: Vec<String> = Vec::new();
1347 for idx in members {
1348 let p = &arena[*idx];
1349 let Some((_, constraint)) = package_name_map.iter().find(|(n, _)| *n == p.name) else {
1350 continue;
1351 };
1352 let matches =
1353 matches!(constraint, Constraint::MatchAll) || constraint.matches_version(&p.version);
1354 if matches
1355 && is_package_acceptable(acceptable, flags, &p.names(true), p.stability)
1356 && !already_loaded
1357 .get(&p.name)
1358 .is_some_and(|s| s.contains(&p.version))
1359 {
1360 push_unique(&mut result, *idx);
1361 if let Some(base) = p.alias_of {
1362 push_unique(&mut result, base);
1363 }
1364 }
1365 if !names_found.contains(&p.name) {
1366 names_found.push(p.name.clone());
1367 }
1368 }
1369 for idx in members {
1370 if let Some(base) = arena[*idx].alias_of {
1371 if result.contains(&base) {
1372 push_unique(&mut result, *idx);
1373 }
1374 }
1375 }
1376 (names_found, result)
1377}
1378
1379pub fn set_source_dist_references(arena: &mut [Package], idx: usize, reference: &str) {
1383 static HOSTS: OnceLock<Regex> = OnceLock::new();
1384 static SHA: OnceLock<Regex> = OnceLock::new();
1385 let base = arena[idx].alias_of.unwrap_or(idx);
1386 let targets: Vec<usize> = (0..arena.len())
1387 .filter(|i| *i == base || arena[*i].alias_of == Some(base))
1388 .collect();
1389 for t in targets {
1390 let p = &mut arena[t];
1391 if let Some(s) = &mut p.source {
1392 s.reference = Some(reference.to_owned());
1393 }
1394 let dist_url = p.dist.as_ref().map(|d| d.url.clone());
1395 let hosts = regex(
1396 &HOSTS,
1397 r"^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/",
1398 true,
1399 );
1400 match dist_url {
1401 Some(url) if hosts.is_match(url.as_bytes()).unwrap_or(false) => {
1402 let sha = regex(&SHA, r"(?<=/|sha=)[a-f0-9]{40}(?=/|$)", true);
1403 let replaced = replace_all(sha, &url, reference);
1404 if let Some(d) = &mut p.dist {
1405 d.reference = Some(reference.to_owned());
1406 d.url = replaced;
1407 }
1408 }
1409 _ => {
1410 if let Some(d) = &mut p.dist {
1411 if d.reference
1412 .as_deref()
1413 .is_some_and(|r| !r.is_empty() && r != "0")
1414 {
1415 d.reference = Some(reference.to_owned());
1416 }
1417 }
1418 }
1419 }
1420 }
1421}
1422
1423fn replace_all(re: &Regex, subject: &str, replacement: &str) -> String {
1424 let mut out = String::new();
1425 let mut last = 0;
1426 for m in re.find_iter(subject.as_bytes()).flatten() {
1427 out.push_str(&subject[last..m.start()]);
1428 out.push_str(replacement);
1429 last = m.end();
1430 }
1431 out.push_str(&subject[last..]);
1432 out
1433}
1434
1435#[cfg(test)]
1436mod tests {
1437 use super::*;
1438 use crate::package::{Link, LinkType};
1439 use serde_json::json;
1440
1441 #[test]
1442 fn match_looks_up_links_by_php_key() {
1443 let mut lib = Package::new("lib-libxslt", "1.1.35.0", "1.1.35", Origin::Platform);
1446 lib.replaces.insert(Link {
1447 key: Some("xsl".into()),
1448 source: "lib-libxslt".into(),
1449 target: "lib-xsl".into(),
1450 constraint: Constraint::new(crate::constraint::Op::Eq, "1.1.35.0"),
1451 pretty_constraint: "1.1.35".into(),
1452 kind: LinkType::Replace,
1453 });
1454 assert!(!Pool::matches(&lib, "lib-xsl", None));
1455 assert!(Pool::matches(&lib, "xsl", None));
1458 let pool = Pool::new(vec![0], Vec::new(), std::slice::from_ref(&lib));
1459 assert_eq!(
1460 pool.what_provides(std::slice::from_ref(&lib), "xsl", None),
1461 Vec::<usize>::new()
1462 );
1463 assert_eq!(
1464 pool.what_provides(std::slice::from_ref(&lib), "lib-xsl", None),
1465 Vec::<usize>::new()
1466 );
1467 let cfg = json!({"name": "acme/lib", "version": "dev-main", "default-branch": true,
1469 "replace": {"acme/old": "self.version"}});
1470 let mut arena = Vec::new();
1471 let ids =
1472 crate::loader::load_packages(&[cfg], Origin::Repository(0), &mut arena, false).unwrap();
1473 let alias = &arena[ids[0]];
1474 assert!(alias.replaces.has_numeric_keys());
1475 assert!(Pool::matches(
1476 alias,
1477 "acme/old",
1478 Some(&Constraint::new(crate::constraint::Op::Eq, "9999999-dev"))
1479 ));
1480 assert!(Pool::matches(
1481 alias,
1482 "acme/old",
1483 Some(&Constraint::new(crate::constraint::Op::Eq, "dev-main"))
1484 ));
1485 let base = &arena[ids[1]];
1486 assert!(!base.replaces.has_numeric_keys());
1487 assert!(!Pool::matches(
1488 base,
1489 "acme/old",
1490 Some(&Constraint::new(crate::constraint::Op::Eq, "9999999-dev"))
1491 ));
1492 let pool = Pool::new(vec![ids[1], ids[0]], Vec::new(), &arena);
1493 assert_eq!(pool.what_provides(&arena, "acme/old", None), vec![1, 2]);
1494 assert_eq!(
1495 pool.what_provides(&arena, "lib-xsl", None),
1496 Vec::<usize>::new()
1497 );
1498 }
1499
1500 #[test]
1501 fn transitive_dependencies_allowed_without_allow_list() {
1502 let r = Request::new(None);
1503 assert!(r.update_allow_transitive_dependencies());
1504 assert!(!r.update_allow_transitive_root_dependencies());
1505 }
1506}