alpm_buildinfo/build_info/
v1.rs

1use std::{
2    fmt::{Display, Formatter},
3    str::FromStr,
4};
5
6use alpm_common::FileFormatSchema;
7use alpm_types::{
8    Architecture,
9    BuildDate,
10    BuildDirectory,
11    BuildEnvironmentOption,
12    Checksum,
13    InstalledPackage,
14    Name,
15    PackageOption,
16    Packager,
17    SchemaVersion,
18    Version,
19    digests::Sha256,
20};
21use serde_with::{DisplayFromStr, serde_as};
22
23use crate::{BuildInfoSchema, Error};
24
25/// Generates a struct based on the BUILDINFO version 1 specification with additional fields.
26macro_rules! generate_buildinfo {
27    // Meta: The meta information for the struct (e.g. doc comments)
28    // Name: The name of the struct
29    // Extra fields: Additional fields that should be added to the struct
30    ($(#[$meta:meta])* $name:ident { $($extra_fields:tt)* }) => {
31        $(#[$meta])*
32        #[serde_as]
33        #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
34        #[serde(deny_unknown_fields)]
35        pub struct $name {
36            #[serde_as(as = "DisplayFromStr")]
37            format: BuildInfoSchema,
38
39            #[serde_as(as = "DisplayFromStr")]
40            pkgname: Name,
41
42            #[serde_as(as = "DisplayFromStr")]
43            pkgbase: Name,
44
45            #[serde_as(as = "DisplayFromStr")]
46            pkgver: Version,
47
48            #[serde_as(as = "DisplayFromStr")]
49            pkgarch: Architecture,
50
51            #[serde_as(as = "DisplayFromStr")]
52            pkgbuild_sha256sum: Checksum<Sha256>,
53
54            #[serde_as(as = "DisplayFromStr")]
55            packager: Packager,
56
57            #[serde_as(as = "DisplayFromStr")]
58            builddate: BuildDate,
59
60            #[serde_as(as = "DisplayFromStr")]
61            builddir: BuildDirectory,
62
63            #[serde_as(as = "Vec<DisplayFromStr>")]
64            #[serde(default)]
65            buildenv: Vec<BuildEnvironmentOption>,
66
67            #[serde_as(as = "Vec<DisplayFromStr>")]
68            #[serde(default)]
69            options: Vec<PackageOption>,
70
71            #[serde_as(as = "Vec<DisplayFromStr>")]
72            #[serde(default)]
73            installed: Vec<InstalledPackage>,
74
75            $($extra_fields)*
76        }
77
78        impl $name {
79            /// Returns the format of the BUILDINFO file
80            pub fn format(&self) -> &SchemaVersion {
81                self.format.inner()
82            }
83
84            /// Returns the package name
85            pub fn pkgname(&self) -> &Name {
86                &self.pkgname
87            }
88
89            /// Returns the package base
90            pub fn pkgbase(&self) -> &Name {
91                &self.pkgbase
92            }
93
94            /// Returns the package version
95            pub fn pkgver(&self) -> &Version {
96                &self.pkgver
97            }
98
99            /// Returns the package architecture
100            pub fn pkgarch(&self) -> &Architecture {
101                &self.pkgarch
102            }
103
104            /// Returns the package build SHA-256 checksum
105            pub fn pkgbuild_sha256sum(&self) -> &Checksum<Sha256> {
106                &self.pkgbuild_sha256sum
107            }
108
109            /// Returns the packager
110            pub fn packager(&self) -> &Packager {
111                &self.packager
112            }
113
114            /// Returns the build date
115            pub fn builddate(&self) -> &BuildDate {
116                &self.builddate
117            }
118
119            /// Returns the build directory
120            pub fn builddir(&self) -> &BuildDirectory {
121                &self.builddir
122            }
123
124            /// Returns the build environment
125            pub fn buildenv(&self) -> &Vec<BuildEnvironmentOption> {
126                &self.buildenv
127            }
128
129            /// Returns the package options
130            pub fn options(&self) -> &Vec<PackageOption> {
131                &self.options
132            }
133
134            /// Returns the installed packages
135            pub fn installed(&self) -> &Vec<InstalledPackage> {
136                &self.installed
137            }
138        }
139    }
140}
141
142pub(crate) use generate_buildinfo;
143
144generate_buildinfo! {
145    /// BUILDINFO version 1
146    ///
147    /// `BuildInfoV1` is (exclusively) compatible with data following the first specification of the
148    /// BUILDINFO file.
149    ///
150    /// ## Examples
151    ///
152    /// ```
153    /// use std::str::FromStr;
154    ///
155    /// use alpm_buildinfo::BuildInfoV1;
156    ///
157    /// # fn main() -> Result<(), alpm_buildinfo::Error> {
158    /// let buildinfo_data = r#"format = 1
159    /// pkgname = foo
160    /// pkgbase = foo
161    /// pkgver = 1:1.0.0-1
162    /// pkgarch = any
163    /// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
164    /// packager = Foobar McFooface <foobar@mcfooface.org>
165    /// builddate = 1
166    /// builddir = /build
167    /// buildenv = envfoo
168    /// buildenv = envbar
169    /// options = some_option
170    /// options = !other_option
171    /// installed = bar-1.2.3-1-any
172    /// installed = beh-2.2.3-4-any
173    /// "#;
174    ///
175    /// let buildinfo = BuildInfoV1::from_str(buildinfo_data)?;
176    /// assert_eq!(buildinfo.to_string(), buildinfo_data);
177    /// # Ok(())
178    /// # }
179    /// ```
180    BuildInfoV1 {}
181}
182
183impl BuildInfoV1 {
184    /// Create a new BuildInfoV1 from all required components
185    #[allow(clippy::too_many_arguments)]
186    pub fn new(
187        builddate: BuildDate,
188        builddir: BuildDirectory,
189        buildenv: Vec<BuildEnvironmentOption>,
190        format: SchemaVersion,
191        installed: Vec<InstalledPackage>,
192        options: Vec<PackageOption>,
193        packager: Packager,
194        pkgarch: Architecture,
195        pkgbase: Name,
196        pkgbuild_sha256sum: Checksum<Sha256>,
197        pkgname: Name,
198        pkgver: Version,
199    ) -> Result<Self, Error> {
200        if format.inner().major != 1 {
201            return Err(Error::WrongSchemaVersion(format));
202        }
203        Ok(BuildInfoV1 {
204            builddate,
205            builddir,
206            buildenv,
207            format: BuildInfoSchema::try_from(format)?,
208            installed,
209            options,
210            packager,
211            pkgarch,
212            pkgbase,
213            pkgbuild_sha256sum,
214            pkgname,
215            pkgver,
216        })
217    }
218}
219
220impl FromStr for BuildInfoV1 {
221    type Err = Error;
222    /// Create a BuildInfoV1 from a &str
223    ///
224    /// # Errors
225    ///
226    /// Returns an `Error` if any of the fields in `input` can not be validated according to
227    /// `BuildInfoV1` or their respective own specification.
228    fn from_str(input: &str) -> Result<BuildInfoV1, Self::Err> {
229        let buildinfo: BuildInfoV1 = alpm_parsers::custom_ini::from_str(input)?;
230        if buildinfo.format().inner().major != 1 {
231            return Err(Error::WrongSchemaVersion(buildinfo.format().clone()));
232        }
233        Ok(buildinfo)
234    }
235}
236
237impl Display for BuildInfoV1 {
238    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
239        write!(
240            fmt,
241            "format = {}\n\
242            pkgname = {}\n\
243            pkgbase = {}\n\
244            pkgver = {}\n\
245            pkgarch = {}\n\
246            pkgbuild_sha256sum = {}\n\
247            packager = {}\n\
248            builddate = {}\n\
249            builddir = {}\n\
250            {}\n\
251            {}\n\
252            {}\n\
253            ",
254            self.format().inner().major,
255            self.pkgname(),
256            self.pkgbase(),
257            self.pkgver(),
258            self.pkgarch(),
259            self.pkgbuild_sha256sum(),
260            self.packager(),
261            self.builddate(),
262            self.builddir(),
263            self.buildenv()
264                .iter()
265                .map(|v| format!("buildenv = {v}"))
266                .collect::<Vec<String>>()
267                .join("\n"),
268            self.options()
269                .iter()
270                .map(|v| format!("options = {v}"))
271                .collect::<Vec<String>>()
272                .join("\n"),
273            self.installed()
274                .iter()
275                .map(|v| format!("installed = {v}"))
276                .collect::<Vec<String>>()
277                .join("\n"),
278        )
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use rstest::{fixture, rstest};
285    use testresult::TestResult;
286
287    use super::*;
288
289    #[fixture]
290    fn valid_buildinfov1() -> String {
291        r#"builddate = 1
292builddir = /build
293buildenv = envfoo
294buildenv = envbar
295format = 1
296installed = bar-1.2.3-1-any
297installed = beh-2.2.3-4-any
298options = some_option
299options = !other_option
300packager = Foobar McFooface <foobar@mcfooface.org>
301pkgarch = any
302pkgbase = foo
303pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
304pkgname = foo
305pkgver = 1:1.0.0-1
306"#
307        .to_string()
308    }
309
310    #[rstest]
311    fn buildinfov1_from_str(valid_buildinfov1: String) -> TestResult {
312        BuildInfoV1::from_str(&valid_buildinfov1)?;
313        Ok(())
314    }
315
316    #[rstest]
317    fn buildinfov1() -> TestResult {
318        BuildInfoV1::new(
319            1,
320            BuildDirectory::from_str("/build")?,
321            vec![BuildEnvironmentOption::new("some")?],
322            SchemaVersion::from_str("1")?,
323            vec![InstalledPackage::from_str("bar-1:1.0.0-2-any")?],
324            vec![PackageOption::new("buildoption")?],
325            Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?,
326            Architecture::Any,
327            Name::new("foo")?,
328            Checksum::<Sha256>::calculate_from("foo"),
329            Name::new("foo")?,
330            Version::from_str("1:1.0.0-1")?,
331        )?;
332        Ok(())
333    }
334
335    #[rstest]
336    fn buildinfov1_invalid_schemaversion() -> TestResult {
337        assert!(
338            BuildInfoV1::new(
339                1,
340                BuildDirectory::from_str("/build")?,
341                vec![BuildEnvironmentOption::new("some")?],
342                SchemaVersion::from_str("2")?,
343                vec![InstalledPackage::from_str("bar-1:1.0.0-2-any")?],
344                vec![PackageOption::new("buildoption")?],
345                Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?,
346                Architecture::Any,
347                Name::new("foo")?,
348                Checksum::<Sha256>::calculate_from("foo"),
349                Name::new("foo")?,
350                Version::from_str("1:1.0.0-1")?,
351            )
352            .is_err()
353        );
354        Ok(())
355    }
356
357    #[rstest]
358    #[case("builddate = 2")]
359    #[case("builddir = /build2")]
360    #[case("format = 1")]
361    #[case("packager = Foobar McFooface <foobar@mcfooface.org>")]
362    #[case("pkgarch = any")]
363    #[case("pkgbase = foo")]
364    #[case("pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c")]
365    #[case("pkgname = foo")]
366    #[case("pkgver = 1:1.0.0-1")]
367    fn buildinfov1_from_str_duplicate_fail(mut valid_buildinfov1: String, #[case] duplicate: &str) {
368        valid_buildinfov1.push_str(duplicate);
369        assert!(BuildInfoV1::from_str(&valid_buildinfov1).is_err());
370    }
371}