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}