alpm_types/version/buildtool.rs
1//! Build tool related version handling.
2
3use std::{
4 fmt::{Display, Formatter},
5 str::FromStr,
6};
7
8use serde::Serialize;
9
10#[cfg(doc)]
11use crate::BuildTool;
12use crate::{Architecture, Error, FullVersion, MinimalVersion, Version};
13
14/// The version and optional architecture of a build tool.
15///
16/// [`BuildToolVersion`] is used in conjunction with [`BuildTool`] to denote the specific build tool
17/// a package is built with.
18/// [`BuildToolVersion`] distinguishes between two types of representations:
19///
20/// - the one used by [makepkg], which relies on [`MinimalVersion`]
21/// - and the one used by [pkgctl] (devtools), which relies on [`FullVersion`] and the
22/// [`Architecture`] of the build tool.
23///
24/// For more information refer to the `buildtoolver` keyword in [BUILDINFOv2].
25///
26/// ## Examples
27/// ```
28/// use std::str::FromStr;
29///
30/// use alpm_types::{Architecture, BuildToolVersion, FullVersion, MinimalVersion};
31///
32/// # fn main() -> testresult::TestResult {
33/// // Representation used by makepkg
34/// assert_eq!(
35/// BuildToolVersion::from_str("1.0.0")?,
36/// BuildToolVersion::Makepkg(MinimalVersion::from_str("1.0.0")?)
37/// );
38/// assert_eq!(
39/// BuildToolVersion::from_str("1:1.0.0")?,
40/// BuildToolVersion::Makepkg(MinimalVersion::from_str("1:1.0.0")?)
41/// );
42///
43/// // Representation used by pkgctl
44/// assert_eq!(
45/// BuildToolVersion::from_str("1.0.0-1-any")?,
46/// BuildToolVersion::DevTools {
47/// version: FullVersion::from_str("1.0.0-1")?,
48/// architecture: Architecture::from_str("any")?
49/// }
50/// );
51/// assert_eq!(
52/// BuildToolVersion::from_str("1:1.0.0-1-any")?,
53/// BuildToolVersion::DevTools {
54/// version: FullVersion::from_str("1:1.0.0-1")?,
55/// architecture: Architecture::from_str("any")?
56/// }
57/// );
58/// # Ok(())
59/// # }
60/// ```
61///
62/// [BUILDINFOv2]: https://alpm.archlinux.page/specifications/BUILDINFOv2.5.html
63/// [makepkg]: https://man.archlinux.org/man/makepkg.8
64/// [pkgctl]: https://man.archlinux.org/man/pkgctl.1
65#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
66pub enum BuildToolVersion {
67 /// The version representation used by [makepkg].
68 ///
69 /// [makepkg]: https://man.archlinux.org/man/makepkg.8
70 Makepkg(MinimalVersion),
71 /// The version representation used by [pkgctl] (devtools).
72 ///
73 /// [pkgctl]: https://man.archlinux.org/man/pkgctl.1
74 DevTools {
75 /// The (_full_ or _full with epoch_) version of the build tool.
76 version: FullVersion,
77 /// The architecture of the build tool.
78 architecture: Architecture,
79 },
80}
81
82impl BuildToolVersion {
83 /// Returns the optional [`Architecture`].
84 ///
85 /// # Note
86 ///
87 /// If `self` is a [`BuildToolVersion::Makepkg`] this method always returns [`None`].
88 pub fn architecture(&self) -> Option<Architecture> {
89 if let Self::DevTools {
90 version: _,
91 architecture,
92 } = self
93 {
94 Some(architecture.clone())
95 } else {
96 None
97 }
98 }
99
100 /// Returns a [`Version`] that matches the underlying [`MinimalVersion`] or [`FullVersion`].
101 pub fn version(&self) -> Version {
102 match self {
103 Self::Makepkg(version) => Version::from(version),
104 Self::DevTools {
105 version,
106 architecture: _,
107 } => Version::from(version),
108 }
109 }
110}
111
112impl FromStr for BuildToolVersion {
113 type Err = Error;
114 /// Creates a [`BuildToolVersion`] from a string slice.
115 ///
116 /// # Errors
117 ///
118 /// Returns an error if
119 ///
120 /// - `s` contains no '-' and `s` is not a valid [`MinimalVersion`],
121 /// - or `s` contains at least one '-' and after splitting on the right most occurrence, either
122 /// the left-hand side is not a valid [`FullVersion`] or the right hand side is not a valid
123 /// [`Architecture`].
124 fn from_str(s: &str) -> Result<Self, Self::Err> {
125 match s.rsplit_once('-') {
126 Some((version, architecture)) => Ok(BuildToolVersion::DevTools {
127 version: FullVersion::from_str(version)?,
128 architecture: Architecture::from_str(architecture)?,
129 }),
130 None => Ok(BuildToolVersion::Makepkg(MinimalVersion::from_str(s)?)),
131 }
132 }
133}
134
135impl Display for BuildToolVersion {
136 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
137 match self {
138 Self::Makepkg(version) => write!(f, "{version}"),
139 Self::DevTools {
140 version,
141 architecture,
142 } => write!(f, "{version}-{architecture}"),
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use insta::assert_snapshot;
150 use rstest::rstest;
151 use testresult::TestResult;
152
153 use super::*;
154 use crate::configure_insta;
155
156 /// Ensure that valid strings are correctly parsed as [`BuildToolVersion`] and invalid ones lead
157 /// to an [`Error`].
158 #[rstest]
159 #[case::devtools_full(
160 "1.0.0-1-any",
161 BuildToolVersion::DevTools{version: FullVersion::from_str("1.0.0-1")?, architecture: Architecture::from_str("any")?},
162 )]
163 #[case::devtools_full_with_epoch(
164 "1:1.0.0-1-any",
165 BuildToolVersion::DevTools{version: FullVersion::from_str("1:1.0.0-1")?, architecture: Architecture::from_str("any")?},
166 )]
167 #[case::makepkg_minimal(
168 "1.0.0",
169 BuildToolVersion::Makepkg(MinimalVersion::from_str("1.0.0")?),
170 )]
171 #[case::makepkg_minimal_with_epoch(
172 "1:1.0.0",
173 BuildToolVersion::Makepkg(MinimalVersion::from_str("1:1.0.0")?),
174 )]
175 fn valid_buildtool_version(
176 #[case] input: &str,
177 #[case] expected: BuildToolVersion,
178 ) -> TestResult {
179 let version = match BuildToolVersion::from_str(input) {
180 Ok(version) => version,
181 Err(err) => {
182 panic!("Expected BuildToolVersion parsing of string {input} to succeed:\n{err}")
183 }
184 };
185
186 assert_eq!(
187 version, expected,
188 "Expected '{expected:#?}' when parsing '{input}' but got '{version:#?}'"
189 );
190
191 Ok(())
192 }
193
194 #[rstest]
195 #[case::minimal_version_with_architecture("1.0.0-any")]
196 #[case::minimal_version_with_epoch_and_architecture("1:1.0.0-any")]
197 fn invalid_buildtool_version(#[case] input: &str) -> TestResult {
198 let err = match BuildToolVersion::from_str(input) {
199 Err(err) => err,
200 Ok(_) => {
201 panic!("Expected BuildToolVersion parsing of string {input} to fail")
202 }
203 };
204
205 let (test_name, _guard) = configure_insta();
206 assert_snapshot!(test_name, err.to_string());
207
208 Ok(())
209 }
210
211 /// Ensures that [`BuildToolVersion::from_str`] fails on invalid version strings with specific
212 /// errors.
213 #[rstest]
214 #[case::minimal_version_with_architecture("1.0.0-any")]
215 #[case::minimal_version_with_unknown_architecture("1.0.0-foo")]
216 #[case::bad_package_version("ß-1-any")]
217 fn invalid_buildtoolver_new(#[case] input: &str) {
218 let err = match BuildToolVersion::from_str(input) {
219 Err(err) => err,
220 Ok(_) => {
221 panic!("Expected BuildToolVersion parsing of string {input} to fail")
222 }
223 };
224
225 let (test_name, _guard) = configure_insta();
226 assert_snapshot!(test_name, err.to_string());
227 }
228}