Skip to main content

vivacity_resolver/
package.rs

1//! Package model of the resolver: port of `Composer\Package\{BasePackage,
2//! Package, CompletePackage, AliasPackage, Link}` reduced to what the pool
3//! and the solver read, plus the raw JSON of the version (to write the lock
4//! identically). Packages live in an arena (`Vec<Package>`) and refer to
5//! each other by index, as Composer does by object identity.
6
7use crate::constraint::{Constraint, Op};
8use crate::version::parse_stability;
9use serde_json::Value;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum LinkType {
13    Require,
14    DevRequire,
15    Conflict,
16    Provide,
17    Replace,
18}
19
20impl LinkType {
21    /// `BasePackage::$supportedLinkTypes[...]['description']`.
22    pub fn description(self) -> &'static str {
23        match self {
24            LinkType::Require => "requires",
25            LinkType::DevRequire => "requires (for development)",
26            LinkType::Conflict => "conflicts",
27            LinkType::Provide => "provides",
28            LinkType::Replace => "replaces",
29        }
30    }
31
32    pub fn json_key(self) -> &'static str {
33        match self {
34            LinkType::Require => "require",
35            LinkType::DevRequire => "require-dev",
36            LinkType::Conflict => "conflict",
37            LinkType::Provide => "provide",
38            LinkType::Replace => "replace",
39        }
40    }
41}
42
43/// `Composer\Package\Link`, plus its key in the PHP array holding it: the
44/// target in general, the bare name for the platform's `lib-*`
45/// (`PlatformRepository::addLibrary`), none (numeric key) for the
46/// `self.version` links added by `AliasPackage`. `Pool::match` looks up by
47/// key.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct Link {
50    pub key: Option<String>,
51    pub source: String,
52    pub target: String,
53    pub constraint: Constraint,
54    pub pretty_constraint: String,
55    pub kind: LinkType,
56}
57
58impl Link {
59    /// Link keyed by its target (the `ArrayLoader::parseLinks` case).
60    pub fn new(
61        source: &str,
62        target: &str,
63        constraint: Constraint,
64        pretty_constraint: &str,
65        kind: LinkType,
66    ) -> Link {
67        Link {
68            key: Some(target.to_owned()),
69            source: source.to_owned(),
70            target: target.to_owned(),
71            constraint,
72            pretty_constraint: pretty_constraint.to_owned(),
73            kind,
74        }
75    }
76}
77
78/// PHP array of links: insertion order, one entry per key (the last write
79/// wins, at the position of the first); keyless links are the numeric-key
80/// entries of `array_merge`.
81#[derive(Debug, Clone, Default, PartialEq, Eq)]
82pub struct Links(pub Vec<Link>);
83
84impl Links {
85    pub fn insert(&mut self, link: Link) {
86        if link.key.is_some() {
87            if let Some(existing) = self.0.iter_mut().find(|l| l.key == link.key) {
88                *existing = link;
89                return;
90            }
91        }
92        self.0.push(link);
93    }
94    /// `isset($links[$key])` / `$links[$key]`.
95    pub fn get(&self, key: &str) -> Option<&Link> {
96        self.0.iter().find(|l| l.key.as_deref() == Some(key))
97    }
98    /// `isset($links[0])`: at least one numeric-key entry.
99    pub fn has_numeric_keys(&self) -> bool {
100        self.0.iter().any(|l| l.key.is_none())
101    }
102    pub fn iter(&self) -> impl Iterator<Item = &Link> {
103        self.0.iter()
104    }
105    pub fn len(&self) -> usize {
106        self.0.len()
107    }
108    pub fn is_empty(&self) -> bool {
109        self.0.is_empty()
110    }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct SourceRef {
115    pub kind: String,
116    pub url: String,
117    pub reference: Option<String>,
118}
119
120/// Where a package comes from (`getRepository()` in Composer).
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum Origin {
123    Root,
124    Platform,
125    Locked,
126    /// Index of the remote repository in the repository list.
127    Repository(usize),
128    /// No repository: root alias created by `PoolBuilder::loadPackage`.
129    Detached,
130    /// `$resultRepo` of `extractDevPackages`: packages of the first solve
131    /// reloaded from their dump.
132    Result,
133}
134
135#[derive(Debug, Clone)]
136pub struct Package {
137    /// Lowercased name (`getName`).
138    pub name: String,
139    pub pretty_name: String,
140    /// Normalized version (`getVersion`).
141    pub version: String,
142    pub pretty_version: String,
143    pub package_type: String,
144    pub stability: &'static str,
145    pub is_default_branch: bool,
146    pub source: Option<SourceRef>,
147    pub dist: Option<SourceRef>,
148    pub requires: Links,
149    pub dev_requires: Links,
150    pub conflicts: Links,
151    pub provides: Links,
152    pub replaces: Links,
153    /// Raw JSON of the version (p2 metadata or lock entry).
154    pub raw: Value,
155    pub origin: Origin,
156    /// `AliasPackage`: arena index of the aliased package.
157    pub alias_of: Option<usize>,
158    /// `AliasPackage::isRootPackageAlias`.
159    pub root_package_alias: bool,
160    /// `AliasPackage::hasSelfVersionRequires`.
161    pub has_self_version_requires: bool,
162}
163
164impl Package {
165    pub fn new(pretty_name: &str, version: &str, pretty_version: &str, origin: Origin) -> Package {
166        Package {
167            name: pretty_name.to_lowercase(),
168            pretty_name: pretty_name.to_owned(),
169            version: version.to_owned(),
170            pretty_version: pretty_version.to_owned(),
171            package_type: "library".to_owned(),
172            stability: parse_stability(version),
173            is_default_branch: false,
174            source: None,
175            dist: None,
176            requires: Links::default(),
177            dev_requires: Links::default(),
178            conflicts: Links::default(),
179            provides: Links::default(),
180            replaces: Links::default(),
181            raw: Value::Null,
182            origin,
183            alias_of: None,
184            root_package_alias: false,
185            has_self_version_requires: false,
186        }
187    }
188
189    pub fn is_alias(&self) -> bool {
190        self.alias_of.is_some()
191    }
192
193    pub fn is_dev(&self) -> bool {
194        self.stability == "dev"
195    }
196
197    /// `getNames()`: name + targets of provide and replace.
198    pub fn names(&self, provides: bool) -> Vec<String> {
199        let mut names: Vec<String> = vec![self.name.clone()];
200        if provides {
201            for l in self.provides.iter() {
202                if !names.contains(&l.target) {
203                    names.push(l.target.clone());
204                }
205            }
206        }
207        for l in self.replaces.iter() {
208            if !names.contains(&l.target) {
209                names.push(l.target.clone());
210            }
211        }
212        names
213    }
214
215    pub fn unique_name(&self) -> String {
216        format!("{}-{}", self.name, self.version)
217    }
218
219    pub fn pretty_string(&self) -> String {
220        format!("{} {}", self.pretty_name, self.pretty_version)
221    }
222
223    pub fn source_reference(&self) -> Option<&str> {
224        self.source.as_ref().and_then(|s| s.reference.as_deref())
225    }
226
227    pub fn dist_reference(&self) -> Option<&str> {
228        self.dist.as_ref().and_then(|s| s.reference.as_deref())
229    }
230
231    pub fn links(&self, kind: LinkType) -> &Links {
232        match kind {
233            LinkType::Require => &self.requires,
234            LinkType::DevRequire => &self.dev_requires,
235            LinkType::Conflict => &self.conflicts,
236            LinkType::Provide => &self.provides,
237            LinkType::Replace => &self.replaces,
238        }
239    }
240
241    /// `AliasPackage::__construct`: copy of the package with the alias
242    /// version, the alias stability, and the `self.version` links rewritten
243    /// (`replaceSelfVersionDependencies`).
244    pub fn alias(&self, alias_of: usize, version: &str, pretty_version: &str) -> Package {
245        let mut a = self.clone();
246        a.version = version.to_owned();
247        a.pretty_version = pretty_version.to_owned();
248        a.stability = parse_stability(version);
249        a.alias_of = Some(alias_of);
250        a.root_package_alias = false;
251        a.has_self_version_requires = false;
252        let pretty = if pretty_version == crate::version::DEFAULT_BRANCH_ALIAS {
253            self.pretty_version.clone()
254        } else {
255            pretty_version.to_owned()
256        };
257        let rewrite = |links: &Links, kind: LinkType, has_self: &mut bool| -> Links {
258            let mut out = Links::default();
259            if matches!(
260                kind,
261                LinkType::Conflict | LinkType::Provide | LinkType::Replace
262            ) {
263                // `array_merge($links, $newLinks)`: the self.version links
264                // are appended (numeric keys), so the same target twice.
265                let mut extra: Vec<Link> = Vec::new();
266                for l in links.iter() {
267                    out.0.push(l.clone());
268                    if l.pretty_constraint == "self.version" {
269                        extra.push(Link {
270                            key: None,
271                            source: l.source.clone(),
272                            target: l.target.clone(),
273                            constraint: Constraint::new(Op::Eq, version),
274                            pretty_constraint: pretty.clone(),
275                            kind,
276                        });
277                    }
278                }
279                out.0.extend(extra);
280            } else {
281                for l in links.iter() {
282                    if l.pretty_constraint == "self.version" {
283                        if kind == LinkType::Require {
284                            *has_self = true;
285                        }
286                        out.0.push(Link {
287                            key: l.key.clone(),
288                            source: l.source.clone(),
289                            target: l.target.clone(),
290                            constraint: Constraint::new(Op::Eq, version),
291                            pretty_constraint: pretty.clone(),
292                            kind,
293                        });
294                    } else {
295                        out.0.push(l.clone());
296                    }
297                }
298            }
299            out
300        };
301        let mut has_self = false;
302        a.requires = rewrite(&self.requires, LinkType::Require, &mut has_self);
303        a.dev_requires = rewrite(&self.dev_requires, LinkType::DevRequire, &mut has_self);
304        a.conflicts = rewrite(&self.conflicts, LinkType::Conflict, &mut has_self);
305        a.provides = rewrite(&self.provides, LinkType::Provide, &mut has_self);
306        a.replaces = rewrite(&self.replaces, LinkType::Replace, &mut has_self);
307        a.has_self_version_requires = has_self;
308        a
309    }
310}