alpm_buildinfo/
build_info.rs

1//! Handling of BuildInfo versions.
2
3pub mod v1;
4pub mod v2;
5use std::{
6    fmt::Display,
7    fs::File,
8    path::{Path, PathBuf},
9    str::FromStr,
10};
11
12use alpm_common::{FileFormatSchema, MetadataFile};
13
14use crate::{BuildInfoSchema, BuildInfoV1, BuildInfoV2, Error};
15
16/// A representation of the [BUILDINFO] file format.
17///
18/// Tracks all available variants of the file format.
19///
20/// [BUILDINFO]: https://alpm.archlinux.page/specifications/BUILDINFO.5.html
21#[derive(Clone, Debug, PartialEq, serde::Serialize)]
22#[serde(untagged)]
23pub enum BuildInfo {
24    /// The [BUILDINFOv1] file format.
25    ///
26    /// [BUILDINFOv1]: https://alpm.archlinux.page/specifications/BUILDINFOv1.5.html
27    V1(BuildInfoV1),
28    /// The [BUILDINFOv2] file format.
29    ///
30    /// [BUILDINFOv2]: https://alpm.archlinux.page/specifications/BUILDINFOv2.5.html
31    V2(BuildInfoV2),
32}
33
34impl MetadataFile<BuildInfoSchema> for BuildInfo {
35    type Err = Error;
36
37    /// Creates a [`BuildInfo`] from `file`, optionally validated using a [`BuildInfoSchema`].
38    ///
39    /// Opens the `file` and defers to [`BuildInfo::from_reader_with_schema`].
40    ///
41    /// # Note
42    ///
43    /// To automatically derive the [`BuildInfoSchema`], use [`BuildInfo::from_file`].
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use std::{fs::File, io::Write};
49    ///
50    /// use alpm_buildinfo::{BuildInfo, BuildInfoSchema};
51    /// use alpm_common::{FileFormatSchema, MetadataFile};
52    /// use alpm_types::{SchemaVersion, semver_version::Version};
53    ///
54    /// # fn main() -> testresult::TestResult {
55    /// // Prepare a file with BUILDINFO data
56    /// let (file, buildinfo_data) = {
57    ///     let buildinfo_data = r#"format = 2
58    /// pkgname = foo
59    /// pkgbase = foo
60    /// pkgver = 1:1.0.0-1
61    /// pkgarch = any
62    /// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
63    /// packager = Foobar McFooface <foobar@mcfooface.org>
64    /// builddate = 1
65    /// builddir = /build
66    /// startdir = /startdir/
67    /// buildtool = devtools
68    /// buildtoolver = 1:1.2.1-1-any
69    /// buildenv = ccache
70    /// options = lto
71    /// installed = bar-1.2.3-1-any
72    /// "#;
73    ///     let file = tempfile::NamedTempFile::new()?;
74    ///     let mut output = File::create(&file)?;
75    ///     write!(output, "{}", buildinfo_data)?;
76    ///     (file, buildinfo_data)
77    /// };
78    ///
79    /// let buildinfo = BuildInfo::from_file_with_schema(
80    ///     file.path(),
81    ///     Some(BuildInfoSchema::V2(SchemaVersion::new(Version::new(
82    ///         2, 0, 0,
83    ///     )))),
84    /// )?;
85    /// assert_eq!(buildinfo.to_string(), buildinfo_data);
86    /// # Ok(())
87    /// # }
88    /// ```
89    ///
90    /// # Errors
91    ///
92    /// Returns an error if
93    /// - the `file` cannot be opened for reading,
94    /// - no variant of [`BuildInfo`] can be constructed from the contents of `file`,
95    /// - or `schema` is [`Some`] and the [`BuildInfoSchema`] does not match the contents of `file`.
96    fn from_file_with_schema(
97        file: impl AsRef<Path>,
98        schema: Option<BuildInfoSchema>,
99    ) -> Result<Self, Error> {
100        let file = file.as_ref();
101        Self::from_reader_with_schema(
102            File::open(file).map_err(|source| {
103                Error::IoPathError(PathBuf::from(file), "opening the file for reading", source)
104            })?,
105            schema,
106        )
107    }
108
109    /// Creates a [`BuildInfo`] from a `reader`, optionally validated using a [`BuildInfoSchema`].
110    ///
111    /// Reads the `reader` to string and defers to [`BuildInfo::from_str_with_schema`].
112    ///
113    /// # Note
114    ///
115    /// To automatically derive the [`BuildInfoSchema`], use [`BuildInfo::from_reader`].
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use std::{fs::File, io::Write};
121    ///
122    /// use alpm_buildinfo::{BuildInfo, BuildInfoSchema};
123    /// use alpm_common::MetadataFile;
124    /// use alpm_types::{SchemaVersion, semver_version::Version};
125    ///
126    /// # fn main() -> testresult::TestResult {
127    /// // Prepare a reader with BUILDINFO data
128    /// let (reader, buildinfo_data) = {
129    ///     let buildinfo_data = r#"format = 2
130    /// pkgname = foo
131    /// pkgbase = foo
132    /// pkgver = 1:1.0.0-1
133    /// pkgarch = any
134    /// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
135    /// packager = Foobar McFooface <foobar@mcfooface.org>
136    /// builddate = 1
137    /// builddir = /build
138    /// startdir = /startdir/
139    /// buildtool = devtools
140    /// buildtoolver = 1:1.2.1-1-any
141    /// buildenv = ccache
142    /// options = lto
143    /// installed = bar-1.2.3-1-any
144    /// "#;
145    ///     let buildinfo_file = tempfile::NamedTempFile::new()?;
146    ///     let mut output = File::create(&buildinfo_file)?;
147    ///     write!(output, "{}", buildinfo_data)?;
148    ///     (File::open(&buildinfo_file.path())?, buildinfo_data)
149    /// };
150    ///
151    /// let buildinfo = BuildInfo::from_reader_with_schema(
152    ///     reader,
153    ///     Some(BuildInfoSchema::V2(SchemaVersion::new(Version::new(
154    ///         2, 0, 0,
155    ///     )))),
156    /// )?;
157    /// assert_eq!(buildinfo.to_string(), buildinfo_data);
158    /// # Ok(())
159    /// # }
160    /// ```
161    ///
162    /// # Errors
163    ///
164    /// Returns an error if
165    /// - the `reader` cannot be read to string,
166    /// - no variant of [`BuildInfo`] can be constructed from the contents of the `reader`,
167    /// - or `schema` is [`Some`] and the [`BuildInfoSchema`] does not match the contents of the
168    ///   `reader`.
169    fn from_reader_with_schema(
170        mut reader: impl std::io::Read,
171        schema: Option<BuildInfoSchema>,
172    ) -> Result<Self, Error> {
173        let mut buf = String::new();
174        reader
175            .read_to_string(&mut buf)
176            .map_err(|source| Error::IoReadError {
177                context: "reading BuildInfo data",
178                source,
179            })?;
180        Self::from_str_with_schema(&buf, schema)
181    }
182
183    /// Creates a [`BuildInfo`] from string slice, optionally validated using a [`BuildInfoSchema`].
184    ///
185    /// If `schema` is [`None`] attempts to detect the [`BuildInfoSchema`] from `s`.
186    /// Attempts to create a [`BuildInfo`] variant that corresponds to the [`BuildInfoSchema`].
187    ///
188    /// # Note
189    ///
190    /// To automatically derive the [`BuildInfoSchema`], use [`BuildInfo::from_str`].
191    ///
192    /// # Examples
193    ///
194    /// ```
195    /// use std::{fs::File, io::Write};
196    ///
197    /// use alpm_buildinfo::{BuildInfo, BuildInfoSchema};
198    /// use alpm_common::MetadataFile;
199    /// use alpm_types::{SchemaVersion, semver_version::Version};
200    ///
201    /// # fn main() -> testresult::TestResult {
202    /// let buildinfo_v2_data = r#"format = 2
203    /// pkgname = foo
204    /// pkgbase = foo
205    /// pkgver = 1:1.0.0-1
206    /// pkgarch = any
207    /// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
208    /// packager = Foobar McFooface <foobar@mcfooface.org>
209    /// builddate = 1
210    /// builddir = /build
211    /// startdir = /startdir/
212    /// buildtool = devtools
213    /// buildtoolver = 1:1.2.1-1-any
214    /// buildenv = ccache
215    /// options = lto
216    /// installed = bar-1.2.3-1-any
217    /// "#;
218    ///
219    /// let buildinfo_v2 = BuildInfo::from_str_with_schema(
220    ///     buildinfo_v2_data,
221    ///     Some(BuildInfoSchema::V2(SchemaVersion::new(Version::new(
222    ///         2, 0, 0,
223    ///     )))),
224    /// )?;
225    /// assert_eq!(buildinfo_v2.to_string(), buildinfo_v2_data);
226    ///
227    /// let buildinfo_v1_data = r#"format = 1
228    /// pkgname = foo
229    /// pkgbase = foo
230    /// pkgver = 1:1.0.0-1
231    /// pkgarch = any
232    /// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
233    /// packager = Foobar McFooface <foobar@mcfooface.org>
234    /// builddate = 1
235    /// builddir = /build
236    /// buildenv = ccache
237    /// options = lto
238    /// installed = bar-1.2.3-1-any
239    /// "#;
240    ///
241    /// let buildinfo_v1 = BuildInfo::from_str_with_schema(
242    ///     buildinfo_v1_data,
243    ///     Some(BuildInfoSchema::V1(SchemaVersion::new(Version::new(
244    ///         1, 0, 0,
245    ///     )))),
246    /// )?;
247    /// assert_eq!(buildinfo_v1.to_string(), buildinfo_v1_data);
248    /// # Ok(())
249    /// # }
250    /// ```
251    ///
252    /// # Errors
253    ///
254    /// Returns an error if
255    /// - `schema` is [`Some`] and the specified variant of [`BuildInfo`] cannot be constructed from
256    ///   `s`,
257    /// - `schema` is [`None`] and
258    ///   - a [`BuildInfoSchema`] cannot be derived from `s`,
259    ///   - or the detected variant of [`BuildInfo`] cannot be constructed from `s`.
260    fn from_str_with_schema(s: &str, schema: Option<BuildInfoSchema>) -> Result<Self, Error> {
261        let schema = match schema {
262            Some(schema) => schema,
263            None => BuildInfoSchema::derive_from_str(s)?,
264        };
265
266        match schema {
267            BuildInfoSchema::V1(_) => Ok(BuildInfo::V1(BuildInfoV1::from_str(s)?)),
268            BuildInfoSchema::V2(_) => Ok(BuildInfo::V2(BuildInfoV2::from_str(s)?)),
269        }
270    }
271}
272
273impl Display for BuildInfo {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        write!(
276            f,
277            "{}",
278            match self {
279                Self::V1(buildinfo) => buildinfo.to_string(),
280                Self::V2(buildinfo) => buildinfo.to_string(),
281            },
282        )
283    }
284}
285
286impl FromStr for BuildInfo {
287    type Err = Error;
288
289    /// Creates a [`BuildInfo`] from string slice `s`.
290    ///
291    /// Calls [`BuildInfo::from_str_with_schema`] with `schema` set to [`None`].
292    ///
293    /// # Errors
294    ///
295    /// Returns an error if
296    /// - a [`BuildInfoSchema`] cannot be derived from `s`,
297    /// - or the detected variant of [`BuildInfo`] cannot be constructed from `s`.
298    fn from_str(s: &str) -> Result<Self, Self::Err> {
299        Self::from_str_with_schema(s, None)
300    }
301}