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    /// `unset($links[$name])`: removes the entry with that key, if any.
96    pub fn remove(&mut self, key: &str) {
97        self.0.retain(|l| l.key.as_deref() != Some(key));
98    }
99
100    pub fn get(&self, key: &str) -> Option<&Link> {
101        self.0.iter().find(|l| l.key.as_deref() == Some(key))
102    }
103    /// `isset($links[0])`: at least one numeric-key entry.
104    pub fn has_numeric_keys(&self) -> bool {
105        self.0.iter().any(|l| l.key.is_none())
106    }
107    pub fn iter(&self) -> impl Iterator<Item = &Link> {
108        self.0.iter()
109    }
110    pub fn len(&self) -> usize {
111        self.0.len()
112    }
113    pub fn is_empty(&self) -> bool {
114        self.0.is_empty()
115    }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct SourceRef {
120    pub kind: String,
121    pub url: String,
122    pub reference: Option<String>,
123}
124
125/// Where a package comes from (`getRepository()` in Composer).
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum Origin {
128    Root,
129    Platform,
130    Locked,
131    /// Index of the remote repository in the repository list.
132    Repository(usize),
133    /// No repository: root alias created by `PoolBuilder::loadPackage`.
134    Detached,
135    /// `$resultRepo` of `extractDevPackages`: packages of the first solve
136    /// reloaded from their dump.
137    Result,
138}
139
140#[derive(Debug, Clone)]
141pub struct Package {
142    /// Lowercased name (`getName`).
143    pub name: String,
144    pub pretty_name: String,
145    /// Normalized version (`getVersion`).
146    pub version: String,
147    pub pretty_version: String,
148    pub package_type: String,
149    pub stability: &'static str,
150    pub is_default_branch: bool,
151    pub source: Option<SourceRef>,
152    pub dist: Option<SourceRef>,
153    pub requires: Links,
154    pub dev_requires: Links,
155    pub conflicts: Links,
156    pub provides: Links,
157    pub replaces: Links,
158    /// Raw JSON of the version (p2 metadata or lock entry).
159    pub raw: Value,
160    pub origin: Origin,
161    /// `AliasPackage`: arena index of the aliased package.
162    pub alias_of: Option<usize>,
163    /// `AliasPackage::isRootPackageAlias`.
164    pub root_package_alias: bool,
165    /// `AliasPackage::hasSelfVersionRequires`.
166    pub has_self_version_requires: bool,
167}
168
169impl Package {
170    pub fn new(pretty_name: &str, version: &str, pretty_version: &str, origin: Origin) -> Package {
171        Package {
172            name: pretty_name.to_lowercase(),
173            pretty_name: pretty_name.to_owned(),
174            version: version.to_owned(),
175            pretty_version: pretty_version.to_owned(),
176            package_type: "library".to_owned(),
177            stability: parse_stability(version),
178            is_default_branch: false,
179            source: None,
180            dist: None,
181            requires: Links::default(),
182            dev_requires: Links::default(),
183            conflicts: Links::default(),
184            provides: Links::default(),
185            replaces: Links::default(),
186            raw: Value::Null,
187            origin,
188            alias_of: None,
189            root_package_alias: false,
190            has_self_version_requires: false,
191        }
192    }
193
194    pub fn is_alias(&self) -> bool {
195        self.alias_of.is_some()
196    }
197
198    pub fn is_dev(&self) -> bool {
199        self.stability == "dev"
200    }
201
202    /// `getNames()`: name + targets of provide and replace.
203    pub fn names(&self, provides: bool) -> Vec<String> {
204        let mut names: Vec<String> = vec![self.name.clone()];
205        if provides {
206            for l in self.provides.iter() {
207                if !names.contains(&l.target) {
208                    names.push(l.target.clone());
209                }
210            }
211        }
212        for l in self.replaces.iter() {
213            if !names.contains(&l.target) {
214                names.push(l.target.clone());
215            }
216        }
217        names
218    }
219
220    pub fn unique_name(&self) -> String {
221        format!("{}-{}", self.name, self.version)
222    }
223
224    pub fn pretty_string(&self) -> String {
225        format!("{} {}", self.pretty_name, self.pretty_version)
226    }
227
228    pub fn source_reference(&self) -> Option<&str> {
229        self.source.as_ref().and_then(|s| s.reference.as_deref())
230    }
231
232    pub fn dist_reference(&self) -> Option<&str> {
233        self.dist.as_ref().and_then(|s| s.reference.as_deref())
234    }
235
236    pub fn links(&self, kind: LinkType) -> &Links {
237        match kind {
238            LinkType::Require => &self.requires,
239            LinkType::DevRequire => &self.dev_requires,
240            LinkType::Conflict => &self.conflicts,
241            LinkType::Provide => &self.provides,
242            LinkType::Replace => &self.replaces,
243        }
244    }
245
246    /// `AliasPackage::__construct`: copy of the package with the alias
247    /// version, the alias stability, and the `self.version` links rewritten
248    /// (`replaceSelfVersionDependencies`).
249    pub fn alias(&self, alias_of: usize, version: &str, pretty_version: &str) -> Package {
250        let mut a = self.clone();
251        a.version = version.to_owned();
252        a.pretty_version = pretty_version.to_owned();
253        a.stability = parse_stability(version);
254        a.alias_of = Some(alias_of);
255        a.root_package_alias = false;
256        a.has_self_version_requires = false;
257        let pretty = if pretty_version == crate::version::DEFAULT_BRANCH_ALIAS {
258            self.pretty_version.clone()
259        } else {
260            pretty_version.to_owned()
261        };
262        let rewrite = |links: &Links, kind: LinkType, has_self: &mut bool| -> Links {
263            let mut out = Links::default();
264            if matches!(
265                kind,
266                LinkType::Conflict | LinkType::Provide | LinkType::Replace
267            ) {
268                // `array_merge($links, $newLinks)`: the self.version links
269                // are appended (numeric keys), so the same target twice.
270                let mut extra: Vec<Link> = Vec::new();
271                for l in links.iter() {
272                    out.0.push(l.clone());
273                    if l.pretty_constraint == "self.version" {
274                        extra.push(Link {
275                            key: None,
276                            source: l.source.clone(),
277                            target: l.target.clone(),
278                            constraint: Constraint::new(Op::Eq, version),
279                            pretty_constraint: pretty.clone(),
280                            kind,
281                        });
282                    }
283                }
284                out.0.extend(extra);
285            } else {
286                for l in links.iter() {
287                    if l.pretty_constraint == "self.version" {
288                        if kind == LinkType::Require {
289                            *has_self = true;
290                        }
291                        out.0.push(Link {
292                            key: l.key.clone(),
293                            source: l.source.clone(),
294                            target: l.target.clone(),
295                            constraint: Constraint::new(Op::Eq, version),
296                            pretty_constraint: pretty.clone(),
297                            kind,
298                        });
299                    } else {
300                        out.0.push(l.clone());
301                    }
302                }
303            }
304            out
305        };
306        let mut has_self = false;
307        a.requires = rewrite(&self.requires, LinkType::Require, &mut has_self);
308        a.dev_requires = rewrite(&self.dev_requires, LinkType::DevRequire, &mut has_self);
309        a.conflicts = rewrite(&self.conflicts, LinkType::Conflict, &mut has_self);
310        a.provides = rewrite(&self.provides, LinkType::Provide, &mut has_self);
311        a.replaces = rewrite(&self.replaces, LinkType::Replace, &mut has_self);
312        a.has_self_version_requires = has_self;
313        a
314    }
315}