pkgsrc/depend.rs
1/*
2 * Copyright (c) 2024 Jonathan Perkin <jonathan@perkin.org.uk>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17use crate::{Pattern, PatternError, PkgPath, PkgPathError};
18use std::fmt;
19use std::str::FromStr;
20use thiserror::Error;
21
22#[cfg(feature = "serde")]
23use serde_with::{DeserializeFromStr, SerializeDisplay};
24
25/**
26 * Parse `DEPENDS` and other package dependency types.
27 *
28 * pkgsrc uses a few different ways to express package dependencies. The most
29 * common looks something like this, where a dependency on any version of mutt
30 * is expressed, with mutt most likely to be found at `mail/mutt` (though not
31 * always).
32 *
33 * ```text
34 * DEPENDS+= mutt-[0-9]*:../../mail/mutt
35 * ```
36 *
37 * There are a few different types, expressed in [`DependType`].
38 *
39 * A `DEPENDS` match is essentially of the form "[`Pattern`]:[`PkgPath`]"
40 */
41#[derive(Clone, Debug, Eq, Hash, PartialEq)]
42#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
43pub struct Depend {
44 /**
45 * A [`Pattern`] containing the package match.
46 */
47 pattern: Pattern,
48 /**
49 * A [`PkgPath`] containing the most likely location for this dependency.
50 * Note that when multiple packages that match the pattern are available
51 * then this may not be the [`PkgPath`] that is ultimately chosen, if a
52 * package at a different location ends up being a better match.
53 */
54 pkgpath: PkgPath,
55}
56
57impl Depend {
58 /**
59 * Create a new [`Depend`] from a [`str`] slice. Return a [`DependError`]
60 * if it cannot be created successfully.
61 *
62 * # Errors
63 *
64 * Returns [`DependError::Invalid`] if the string doesn't contain exactly
65 * one `:` separator.
66 *
67 * Returns [`DependError::Pattern`] if the pattern portion is invalid.
68 *
69 * Returns [`DependError::PkgPath`] if the pkgpath portion is invalid.
70 *
71 * # Examples
72 *
73 * ```
74 * use pkgsrc::{Depend, Pattern, PkgPath};
75 *
76 * let dep = Depend::new("mktool-[0-9]*:../../pkgtools/mktool").unwrap();
77 * assert_eq!(dep.pattern(), &Pattern::new("mktool-[0-9]*").unwrap());
78 * assert_eq!(dep.pkgpath(), &PkgPath::new("pkgtools/mktool").unwrap());
79 *
80 * // Invalid, too many ":".
81 * assert!(Depend::new("pkg>0::../../cat/pkg").is_err());
82 *
83 * // Invalid, incorrect Dewey specification.
84 * assert!(Depend::new("pkg>0>2:../../cat/pkg").is_err());
85 * ```
86 */
87 pub fn new(s: &str) -> Result<Self, DependError> {
88 let v: Vec<_> = s.split(':').collect();
89 if v.len() != 2 {
90 return Err(DependError::Invalid);
91 }
92 let pattern = Pattern::new(v[0])?;
93 let pkgpath = PkgPath::from_str(v[1])?;
94 Ok(Depend { pattern, pkgpath })
95 }
96
97 /**
98 * Return the [`Pattern`] portion of this [`Depend`].
99 */
100 #[must_use]
101 pub fn pattern(&self) -> &Pattern {
102 &self.pattern
103 }
104
105 /**
106 * Return the [`PkgPath`] portion of this [`Depend`].
107 */
108 #[must_use]
109 pub fn pkgpath(&self) -> &PkgPath {
110 &self.pkgpath
111 }
112}
113
114/**
115 * Type of dependency (full, build, bootstrap, test, etc.)
116 */
117#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
119pub enum DependType {
120 /**
121 * A regular full pkgsrc dependency for this package, usually specified
122 * using `DEPENDS`. The default value.
123 */
124 #[default]
125 Full,
126 /**
127 * A pkgsrc dependency that is only required to build the package, usually
128 * specified using `BUILD_DEPENDS`.
129 */
130 Build,
131 /**
132 * Dependency required to make the pkgsrc infrastructure work for this
133 * package (for example a checksum tool to verify distfiles).
134 */
135 Bootstrap,
136 /**
137 * A host tool required to build this package. May turn into a pkgsrc
138 * dependency if the host does not provide a compatible tool. May be
139 * defined using `USE_TOOLS` or `TOOL_DEPENDS`.
140 */
141 Tool,
142 /**
143 * A pkgsrc dependency that is only required to run the test suite for a
144 * package.
145 */
146 Test,
147}
148
149/**
150 * A `DEPENDS` parsing error.
151 */
152#[derive(Debug, Error)]
153pub enum DependError {
154 /**
155 * An invalid string that doesn't match `<pattern>:<pkgpath>`.
156 */
157 #[error("Invalid DEPENDS string")]
158 Invalid,
159 /**
160 * A transparent [`PatternError`] error.
161 *
162 * [`PatternError`]: crate::pattern::PatternError
163 */
164 #[error(transparent)]
165 Pattern(#[from] PatternError),
166 /**
167 * A transparent [`PkgPathError`] error.
168 *
169 * [`PkgPathError`]: crate::pkgpath::PkgPathError
170 */
171 #[error(transparent)]
172 PkgPath(#[from] PkgPathError),
173}
174
175impl fmt::Display for Depend {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 write!(f, "{}:../../{}", self.pattern, self.pkgpath)
178 }
179}
180
181impl FromStr for Depend {
182 type Err = DependError;
183
184 fn from_str(s: &str) -> Result<Self, DependError> {
185 Depend::new(s)
186 }
187}
188
189impl crate::kv::FromKv for Depend {
190 fn from_kv(value: &str, span: crate::kv::Span) -> crate::kv::Result<Self> {
191 Self::new(value).map_err(|e| crate::kv::Error::Parse {
192 message: e.to_string(),
193 span,
194 })
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_good() -> Result<(), DependError> {
204 let pkgmatch = Pattern::new("mktools-[0-9]").unwrap();
205 let pkgpath = PkgPath::new("../../pkgtools/mktools").unwrap();
206 let dep = Depend::new("mktools-[0-9]:../../pkgtools/mktools")?;
207 assert_eq!(dep.pattern(), &pkgmatch);
208 assert_eq!(dep.pkgpath(), &pkgpath);
209 let dep = Depend::new("mktools-[0-9]:pkgtools/mktools")?;
210 assert_eq!(dep.pattern(), &pkgmatch);
211 assert_eq!(dep.pkgpath(), &pkgpath);
212 Ok(())
213 }
214
215 #[test]
216 fn test_bad() {
217 // Missing ":" separator.
218 let dep = Depend::new("pkg");
219 assert!(matches!(dep, Err(DependError::Invalid)));
220
221 // Too many ":" separators.
222 let dep = Depend::new("pkg-[0-9]*::../../pkgtools/pkg");
223 assert!(matches!(dep, Err(DependError::Invalid)));
224
225 // Invalid Pattern
226 let dep = Depend::new("pkg>2>3:../../pkgtools/pkg");
227 assert!(matches!(dep, Err(DependError::Pattern(_))));
228
229 // Invalid PkgPath
230 let dep = Depend::new("ojnk:foo");
231 assert!(matches!(dep, Err(DependError::PkgPath(_))));
232 }
233
234 #[test]
235 fn test_display() {
236 let dep = Depend::new("mktool-[0-9]*:../../pkgtools/mktool").unwrap();
237 assert_eq!(dep.to_string(), "mktool-[0-9]*:../../pkgtools/mktool");
238 }
239
240 #[test]
241 fn test_from_str() {
242 use std::str::FromStr;
243
244 let dep =
245 Depend::from_str("mktool-[0-9]*:../../pkgtools/mktool").unwrap();
246 assert_eq!(dep.pattern(), &Pattern::new("mktool-[0-9]*").unwrap());
247
248 let dep: Depend = "pkg>=1.0:cat/pkg".parse().unwrap();
249 assert_eq!(dep.pkgpath(), &PkgPath::new("cat/pkg").unwrap());
250 }
251}