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