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}