alpm_buildinfo/build_info/
v2.rs

1use std::{
2    fmt::{Display, Formatter},
3    str::FromStr,
4};
5
6use alpm_types::{
7    Architecture,
8    BuildDate,
9    BuildDirectory,
10    BuildEnvironmentOption,
11    BuildTool,
12    BuildToolVersion,
13    Checksum,
14    FullVersion,
15    InstalledPackage,
16    Name,
17    PackageOption,
18    Packager,
19    SchemaVersion,
20    StartDirectory,
21    digests::Sha256,
22    semver_version::Version as SemverVersion,
23};
24use serde_with::{DisplayFromStr, serde_as};
25
26use crate::{BuildInfoSchema, Error, build_info::format::BuildInfoFormat};
27
28/// BUILDINFO version 2
29///
30/// `BuildInfoV2` is (exclusively) compatible with data following the v2 specification of the
31/// BUILDINFO file.
32///
33/// ## Examples
34///
35/// ```
36/// use std::str::FromStr;
37///
38/// use alpm_buildinfo::BuildInfoV2;
39///
40/// # fn main() -> Result<(), alpm_buildinfo::Error> {
41/// let buildinfo_data = r#"format = 2
42/// pkgname = foo
43/// pkgbase = foo
44/// pkgver = 1:1.0.0-1
45/// pkgarch = any
46/// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
47/// packager = Foobar McFooface <foobar@mcfooface.org>
48/// builddate = 1
49/// builddir = /build
50/// startdir = /startdir/
51/// buildtool = devtools
52/// buildtoolver = 1:1.2.1-1-any
53/// buildenv = ccache
54/// buildenv = color
55/// options = lto
56/// options = !strip
57/// installed = bar-1.2.3-1-any
58/// installed = beh-2.2.3-4-any
59/// "#;
60///
61/// let buildinfo = BuildInfoV2::from_str(buildinfo_data)?;
62/// assert_eq!(buildinfo.to_string(), buildinfo_data);
63/// # Ok(())
64/// # }
65/// ```
66#[serde_as]
67#[derive(Clone, Debug, serde::Deserialize, PartialEq, serde_more::SerializeMore)]
68#[more(key = "format", position = "front")]
69pub struct BuildInfoV2 {
70    /// The package name
71    #[serde_as(as = "DisplayFromStr")]
72    pub pkgname: Name,
73
74    /// The package base name
75    #[serde_as(as = "DisplayFromStr")]
76    pub pkgbase: Name,
77
78    /// The package version
79    #[serde_as(as = "DisplayFromStr")]
80    pub pkgver: FullVersion,
81
82    /// The package architecture
83    #[serde_as(as = "DisplayFromStr")]
84    pub pkgarch: Architecture,
85
86    /// The package build SHA-256 checksum
87    #[serde_as(as = "DisplayFromStr")]
88    pub pkgbuild_sha256sum: Checksum<Sha256>,
89
90    /// The packager
91    #[serde_as(as = "DisplayFromStr")]
92    pub packager: Packager,
93
94    /// The build date
95    #[serde_as(as = "DisplayFromStr")]
96    pub builddate: BuildDate,
97
98    /// The build directory
99    #[serde_as(as = "DisplayFromStr")]
100    pub builddir: BuildDirectory,
101
102    /// The build environment
103    #[serde_as(as = "Vec<DisplayFromStr>")]
104    #[serde(default)]
105    pub buildenv: Vec<BuildEnvironmentOption>,
106
107    /// The package options
108    #[serde_as(as = "Vec<DisplayFromStr>")]
109    #[serde(default)]
110    pub options: Vec<PackageOption>,
111
112    /// The installed packages
113    #[serde_as(as = "Vec<DisplayFromStr>")]
114    #[serde(default)]
115    pub installed: Vec<InstalledPackage>,
116
117    /// The start directory of the build process
118    #[serde_as(as = "DisplayFromStr")]
119    pub startdir: StartDirectory,
120
121    /// The tool used for building the package
122    #[serde_as(as = "DisplayFromStr")]
123    pub buildtool: BuildTool,
124
125    /// The version of the build tool
126    #[serde_as(as = "DisplayFromStr")]
127    pub buildtoolver: BuildToolVersion,
128}
129
130impl BuildInfoV2 {
131    /// Used by serde_more to serialize the additional `format` field.
132    fn format(&self) -> String {
133        BuildInfoSchema::V2(SchemaVersion::new(SemverVersion::new(2, 0, 0))).to_string()
134    }
135}
136
137impl FromStr for BuildInfoV2 {
138    type Err = Error;
139    /// Create a BuildInfoV2 from a &str
140    ///
141    /// # Errors
142    ///
143    /// Returns an `Error` if any of the fields in `input` can not be validated according to
144    /// `BuildInfoV2` or their respective own specification.
145    fn from_str(input: &str) -> Result<BuildInfoV2, Self::Err> {
146        let build_info_format: BuildInfoFormat = alpm_parsers::custom_ini::from_str(input)?;
147        let schema_version: SchemaVersion = build_info_format.into();
148        if schema_version.inner().major != 2 {
149            return Err(Error::WrongSchemaVersion(schema_version));
150        }
151
152        let buildinfo: BuildInfoV2 = alpm_parsers::custom_ini::from_str(input)?;
153        Ok(buildinfo)
154    }
155}
156
157impl Display for BuildInfoV2 {
158    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
159        write!(
160            fmt,
161            "format = {}\n\
162            pkgname = {}\n\
163            pkgbase = {}\n\
164            pkgver = {}\n\
165            pkgarch = {}\n\
166            pkgbuild_sha256sum = {}\n\
167            packager = {}\n\
168            builddate = {}\n\
169            builddir = {}\n\
170            startdir = {}\n\
171            buildtool = {}\n\
172            buildtoolver = {}\n\
173            {}\n\
174            {}\n\
175            {}\n\
176            ",
177            self.format(),
178            self.pkgname,
179            self.pkgbase,
180            self.pkgver,
181            self.pkgarch,
182            self.pkgbuild_sha256sum,
183            self.packager,
184            self.builddate,
185            self.builddir,
186            self.startdir,
187            self.buildtool,
188            self.buildtoolver,
189            self.buildenv
190                .iter()
191                .map(|v| format!("buildenv = {v}"))
192                .collect::<Vec<String>>()
193                .join("\n"),
194            self.options
195                .iter()
196                .map(|v| format!("options = {v}"))
197                .collect::<Vec<String>>()
198                .join("\n"),
199            self.installed
200                .iter()
201                .map(|v| format!("installed = {v}"))
202                .collect::<Vec<String>>()
203                .join("\n"),
204        )
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use rstest::rstest;
211    use testresult::TestResult;
212
213    use super::*;
214
215    // Test data
216    const VALID_BUILDINFOV2_CASE1: &str = r#"
217format = 2
218builddate = 1
219builddir = /build
220startdir = /startdir/
221buildtool = devtools
222buildtoolver = 1:1.2.1-1-any
223buildenv = ccache
224buildenv = color
225installed = bar-1.2.3-1-any
226installed = beh-2.2.3-4-any
227options = lto
228options = !strip
229packager = Foobar McFooface <foobar@mcfooface.org>
230pkgarch = any
231pkgbase = foo
232pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
233pkgname = foo
234pkgver = 1:1.0.0-1
235"#;
236
237    // Test data without multiple values
238    const VALID_BUILDINFOV2_CASE2: &str = r#"
239format = 2
240builddate = 1
241builddir = /build
242startdir = /startdir/
243buildtool = devtools
244buildtoolver = 1:1.2.1-1-any
245buildenv = ccache
246installed = bar-1.2.3-1-any
247options = lto
248packager = Foobar McFooface <foobar@mcfooface.org>
249pkgarch = any
250pkgbase = foo
251pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
252pkgname = foo
253pkgver = 1:1.0.0-1
254"#;
255
256    // Wrong format version
257    const INVALID_BUILDINFOV2: &str = r#"
258format = 1
259builddate = 1
260builddir = /build
261startdir = /startdir/
262buildtool = devtools
263buildtoolver = 1:1.2.1-1-any
264buildenv = ccache
265buildenv = color
266installed = bar-1.2.3-1-any
267installed = beh-2.2.3-4-any
268options = lto
269options = !strip
270packager = Foobar McFooface <foobar@mcfooface.org>
271pkgarch = any
272pkgbase = foo
273pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
274pkgname = foo
275pkgver = 1:1.0.0-1
276"#;
277
278    #[rstest]
279    #[case(VALID_BUILDINFOV2_CASE1)]
280    #[case(VALID_BUILDINFOV2_CASE2)]
281    fn buildinfov2_from_str(#[case] buildinfo: &str) -> TestResult {
282        BuildInfoV2::from_str(buildinfo)?;
283        Ok(())
284    }
285
286    #[rstest]
287    fn buildinfov2() -> TestResult {
288        BuildInfoV2 {
289            builddate: 1,
290            builddir: BuildDirectory::from_str("/build")?,
291            startdir: StartDirectory::from_str("/startdir/")?,
292            buildtool: BuildTool::from_str("devtools")?,
293            buildtoolver: BuildToolVersion::from_str("1:1.2.1-1-any")?,
294            buildenv: vec![BuildEnvironmentOption::new("check")?],
295            installed: vec![InstalledPackage::from_str("bar-1:1.0.0-2-any")?],
296            options: vec![PackageOption::new("lto")?],
297            packager: Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?,
298            pkgarch: Architecture::Any,
299            pkgbase: Name::new("foo")?,
300            pkgbuild_sha256sum: Checksum::<Sha256>::calculate_from("foo"),
301            pkgname: Name::new("foo")?,
302            pkgver: FullVersion::from_str("1:1.0.0-1")?,
303        };
304        Ok(())
305    }
306
307    #[rstest]
308    fn buildinfov2_invalid_schemaversion() -> TestResult {
309        assert!(BuildInfoV2::from_str(INVALID_BUILDINFOV2).is_err());
310        Ok(())
311    }
312
313    #[rstest]
314    #[case("builddate = 2")]
315    #[case("builddir = /build2")]
316    #[case("startdir = /startdir2/")]
317    #[case("buildtool = devtools2")]
318    #[case("buildtoolver = 1:1.2.1-2-any")]
319    #[case("format = 1")]
320    #[case("packager = Foobar McFooface <foobar@mcfooface.org>")]
321    #[case("pkgarch = any")]
322    #[case("pkgbase = foo")]
323    #[case("pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c")]
324    #[case("pkgname = foo")]
325    #[case("pkgver = 1:1.0.0-1")]
326    fn buildinfov2_from_str_duplicate_fail(#[case] duplicate: &str) {
327        let mut buildinfov2 = VALID_BUILDINFOV2_CASE1.to_string();
328        buildinfov2.push_str(duplicate);
329        assert!(BuildInfoV2::from_str(&buildinfov2).is_err());
330    }
331}