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