Skip to main content

alpm_srcinfo/pkgbuild_bridge/
package_base.rs

1//! Convert parsed [`BridgeOutput::package_base`] output into a [`PackageBase`].
2
3use std::{
4    collections::{BTreeMap, HashMap},
5    str::FromStr,
6};
7
8use alpm_parsers::prelude::*;
9#[cfg(doc)]
10use alpm_pkgbuild::bridge::BridgeOutput;
11use alpm_pkgbuild::bridge::{Keyword, Value};
12use alpm_types::{
13    Architectures,
14    Backup,
15    Changelog,
16    Epoch,
17    FullVersion,
18    Install,
19    License,
20    MakepkgOption,
21    Name,
22    OpenPGPIdentifier,
23    OptionalDependency,
24    PackageDescription,
25    PackageRelation,
26    PackageRelease,
27    PackageVersion,
28    RelationOrSoname,
29    SkippableChecksum,
30    Source,
31    SystemArchitecture,
32    Url,
33};
34use strum::VariantNames;
35use winnow::{
36    combinator::{alt, cut_err},
37    token::rest,
38};
39
40use crate::{
41    pkgbuild_bridge::{
42        ensure_keyword_exists,
43        ensure_no_suffix,
44        error::BridgeError,
45        parse_arch_array,
46        parse_optional_value,
47        parse_value,
48        parse_value_array,
49    },
50    source_info::{
51        parser::{ExclusivePackageBaseKeyword, RelationKeyword, SharedMetaKeyword, SourceKeyword},
52        v1::package_base::{PackageBase, PackageBaseArchitecture},
53    },
54};
55
56/// The combination of all keywords that're valid in the scope of a `pkgbase` section.
57enum PackageBaseKeywords {
58    // The `pkgbase` keyword.
59    PkgBase,
60    PackageBase(ExclusivePackageBaseKeyword),
61    Relation(RelationKeyword),
62    SharedMeta(SharedMetaKeyword),
63    Source(SourceKeyword),
64}
65
66impl PackageBaseKeywords {
67    /// Recognizes any of the [`PackageBaseKeywords`] in an input string slice.
68    ///
69    /// Does not consume input and stops after any keyword matches.
70    ///
71    /// # Errors
72    ///
73    /// Returns an error if `input` contains an unexpected keyword.
74    pub fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, PackageBaseKeywords> {
75        cut_err(alt((
76            "pkgbase".map(|_| PackageBaseKeywords::PkgBase),
77            ExclusivePackageBaseKeyword::parser.map(PackageBaseKeywords::PackageBase),
78            RelationKeyword::parser.map(PackageBaseKeywords::Relation),
79            SharedMetaKeyword::parser.map(PackageBaseKeywords::SharedMeta),
80            SourceKeyword::parser.map(PackageBaseKeywords::Source),
81        )))
82        .expected_strings(
83            [
84                &["pkgbase"],
85                ExclusivePackageBaseKeyword::VARIANTS,
86                RelationKeyword::VARIANTS,
87                SharedMetaKeyword::VARIANTS,
88                SourceKeyword::VARIANTS,
89            ]
90            .into_iter()
91            .flatten(),
92        )
93        .context(StrContext::Label("package base property keyword"))
94        .parse_next(input)
95    }
96}
97
98/// Handles all potentially architecture specific Vector entries in the [`handle_package_base`]
99/// function.
100///
101/// If no architecture is encountered, it simply adds the value on the [`PackageBase`] itself.
102/// Otherwise, it's added to the respective [`PackageBase::architecture_properties`].
103macro_rules! package_base_value_array {
104    (
105        $keyword:ident,
106        $value:ident,
107        $field_name:ident,
108        $architecture:ident,
109        $architecture_properties:ident,
110        $parser:expr,
111    ) => {
112        if let Some(architecture) = $architecture {
113            // Make sure the architecture specific properties are initialized.
114            let architecture_properties = $architecture_properties
115                .entry(architecture)
116                .or_insert(PackageBaseArchitecture::default());
117
118            // Set the architecture specific value.
119            architecture_properties.$field_name = parse_value_array($keyword, $value, $parser)?;
120        } else {
121            $field_name = parse_value_array($keyword, $value, $parser)?;
122        }
123    };
124}
125
126/// Convert the raw keyword map from the [`BridgeOutput`] into a well-formed and typed
127/// [`PackageBase`].
128///
129/// Handles parsing and type conversions of all raw input into their respective `alpm-types`.
130///
131/// # Errors
132///
133/// Returns an error if:
134///
135/// - values cannot be parsed,
136/// - or unexpected keywords are encountered.
137pub fn handle_package_base(
138    name: Name,
139    mut raw: HashMap<Keyword, Value>,
140) -> Result<PackageBase, BridgeError> {
141    // First up, we handle keywords that're required.
142    let pkgver_keyword = Keyword::simple("pkgver");
143    let value = ensure_keyword_exists(&pkgver_keyword, &mut raw)?;
144    let package_version: PackageVersion =
145        parse_value(&pkgver_keyword, &value, PackageVersion::parser)?;
146
147    let pkgrel_keyword = Keyword::simple("pkgrel");
148    let value = ensure_keyword_exists(&pkgrel_keyword, &mut raw)?;
149    let package_release: PackageRelease =
150        parse_value(&pkgver_keyword, &value, PackageRelease::parser)?;
151
152    let mut description = None;
153    let mut url = None;
154    let mut licenses = Vec::new();
155    let mut changelog = None;
156    let mut architectures = Architectures::Some(Vec::new());
157    let mut architecture_properties = BTreeMap::new();
158
159    // Build or package management related meta fields
160    let mut install = None;
161    let mut groups = Vec::new();
162    let mut options = Vec::new();
163    let mut backups = Vec::new();
164
165    let mut epoch: Option<Epoch> = None;
166    let mut pgp_fingerprints = Vec::new();
167
168    let mut dependencies = Vec::new();
169    let mut optional_dependencies = Vec::new();
170    let mut provides = Vec::new();
171    let mut conflicts = Vec::new();
172    let mut replaces = Vec::new();
173    // The following dependencies are build-time specific dependencies.
174    // `makepkg` expects all dependencies for all split packages to be specified in the
175    // PackageBase.
176    let mut check_dependencies = Vec::new();
177    let mut make_dependencies = Vec::new();
178
179    let mut sources = Vec::new();
180    let mut no_extracts = Vec::new();
181    let mut b2_checksums = Vec::new();
182    let mut md5_checksums = Vec::new();
183    let mut sha1_checksums = Vec::new();
184    let mut sha224_checksums = Vec::new();
185    let mut sha256_checksums = Vec::new();
186    let mut sha384_checksums = Vec::new();
187    let mut sha512_checksums = Vec::new();
188    let mut crc_checksums = Vec::new();
189
190    // Go through all keywords and handle them.
191    for (raw_keyword, value) in &raw {
192        // Parse the keyword
193        let keyword = PackageBaseKeywords::parser
194            .parse(Input::new(&raw_keyword.keyword))
195            .map_err(|err| (raw_keyword.clone(), err))?;
196
197        // Parse the architecture suffix if it exists.
198        let architecture = match &raw_keyword.suffix {
199            Some(suffix) => {
200                // SystemArchitecture::parser forbids "any"
201                let arch = SystemArchitecture::parser
202                    .parse(Input::new(suffix))
203                    .map_err(|err| (raw_keyword.clone(), err))?;
204                Some(arch)
205            }
206            None => None,
207        };
208
209        // Parse and set the value based on which keyword it is.
210        match keyword {
211            PackageBaseKeywords::PkgBase => {
212                // Explicitly handled before
213                // We check for an unexpected suffix anyway in case somebody goofed up.
214                ensure_no_suffix(raw_keyword, architecture)?;
215                unreachable!(
216                    "'pkgbase' has been handled before and should no longer exist without a suffix."
217                )
218            }
219            PackageBaseKeywords::PackageBase(keyword) => match keyword {
220                // Both PkgVer and PkgRel have been handled above.
221                // We check for an unexpected suffix anyway in case somebody goofed up.
222                ExclusivePackageBaseKeyword::PkgVer => {
223                    ensure_no_suffix(raw_keyword, architecture)?;
224                }
225                ExclusivePackageBaseKeyword::PkgRel => {
226                    ensure_no_suffix(raw_keyword, architecture)?;
227                }
228                ExclusivePackageBaseKeyword::Epoch => {
229                    ensure_no_suffix(raw_keyword, architecture)?;
230                    epoch = parse_optional_value(raw_keyword, value, Epoch::parser_until_eof)?;
231                }
232                ExclusivePackageBaseKeyword::ValidPGPKeys => {
233                    ensure_no_suffix(raw_keyword, architecture)?;
234                    pgp_fingerprints = parse_value_array(
235                        raw_keyword,
236                        value,
237                        rest.try_map(OpenPGPIdentifier::from_str),
238                    )?;
239                }
240                ExclusivePackageBaseKeyword::CheckDepends => {
241                    package_base_value_array!(
242                        raw_keyword,
243                        value,
244                        check_dependencies,
245                        architecture,
246                        architecture_properties,
247                        PackageRelation::parser_until_eof,
248                    )
249                }
250                ExclusivePackageBaseKeyword::MakeDepends => package_base_value_array!(
251                    raw_keyword,
252                    value,
253                    make_dependencies,
254                    architecture,
255                    architecture_properties,
256                    PackageRelation::parser_until_eof,
257                ),
258            },
259            PackageBaseKeywords::SharedMeta(keyword) => match keyword {
260                SharedMetaKeyword::PkgDesc => {
261                    ensure_no_suffix(raw_keyword, architecture)?;
262                    description = parse_optional_value(
263                        raw_keyword,
264                        value,
265                        rest.try_map(PackageDescription::from_str),
266                    )?;
267                }
268                SharedMetaKeyword::Url => {
269                    ensure_no_suffix(raw_keyword, architecture)?;
270                    url = parse_optional_value(raw_keyword, value, rest.try_map(Url::from_str))?;
271                }
272                SharedMetaKeyword::License => {
273                    ensure_no_suffix(raw_keyword, architecture)?;
274                    licenses =
275                        parse_value_array(raw_keyword, value, rest.try_map(License::from_str))?;
276                }
277                SharedMetaKeyword::Arch => {
278                    ensure_no_suffix(raw_keyword, architecture)?;
279                    architectures = parse_arch_array(raw_keyword, value)?;
280                }
281                SharedMetaKeyword::Changelog => {
282                    ensure_no_suffix(raw_keyword, architecture)?;
283                    changelog = parse_optional_value(
284                        raw_keyword,
285                        value,
286                        rest.try_map(Changelog::from_str),
287                    )?;
288                }
289                SharedMetaKeyword::Install => {
290                    ensure_no_suffix(raw_keyword, architecture)?;
291                    install =
292                        parse_optional_value(raw_keyword, value, rest.try_map(Install::from_str))?;
293                }
294                SharedMetaKeyword::Groups => {
295                    ensure_no_suffix(raw_keyword, architecture)?;
296                    groups = value.clone().as_owned_vec();
297                }
298                SharedMetaKeyword::Options => {
299                    ensure_no_suffix(raw_keyword, architecture)?;
300                    options =
301                        parse_value_array(raw_keyword, value, MakepkgOption::parser_until_eof)?;
302                }
303                SharedMetaKeyword::Backup => {
304                    ensure_no_suffix(raw_keyword, architecture)?;
305                    backups =
306                        parse_value_array(raw_keyword, value, rest.try_map(Backup::from_str))?;
307                }
308            },
309            PackageBaseKeywords::Relation(keyword) => match keyword {
310                RelationKeyword::Depends => package_base_value_array!(
311                    raw_keyword,
312                    value,
313                    dependencies,
314                    architecture,
315                    architecture_properties,
316                    RelationOrSoname::parser_until_eof,
317                ),
318                RelationKeyword::OptDepends => package_base_value_array!(
319                    raw_keyword,
320                    value,
321                    optional_dependencies,
322                    architecture,
323                    architecture_properties,
324                    OptionalDependency::parser_until_eof,
325                ),
326                RelationKeyword::Provides => package_base_value_array!(
327                    raw_keyword,
328                    value,
329                    provides,
330                    architecture,
331                    architecture_properties,
332                    RelationOrSoname::parser_until_eof,
333                ),
334                RelationKeyword::Conflicts => package_base_value_array!(
335                    raw_keyword,
336                    value,
337                    conflicts,
338                    architecture,
339                    architecture_properties,
340                    PackageRelation::parser_until_eof,
341                ),
342                RelationKeyword::Replaces => package_base_value_array!(
343                    raw_keyword,
344                    value,
345                    replaces,
346                    architecture,
347                    architecture_properties,
348                    PackageRelation::parser_until_eof,
349                ),
350            },
351
352            PackageBaseKeywords::Source(keyword) => match keyword {
353                SourceKeyword::NoExtract => {
354                    ensure_no_suffix(raw_keyword, architecture)?;
355                    no_extracts = value.clone().as_owned_vec();
356                }
357                SourceKeyword::Source => package_base_value_array!(
358                    raw_keyword,
359                    value,
360                    sources,
361                    architecture,
362                    architecture_properties,
363                    rest.try_map(Source::from_str),
364                ),
365                SourceKeyword::B2sums => package_base_value_array!(
366                    raw_keyword,
367                    value,
368                    b2_checksums,
369                    architecture,
370                    architecture_properties,
371                    SkippableChecksum::parser_until_eof,
372                ),
373                SourceKeyword::Md5sums => package_base_value_array!(
374                    raw_keyword,
375                    value,
376                    md5_checksums,
377                    architecture,
378                    architecture_properties,
379                    SkippableChecksum::parser_until_eof,
380                ),
381                SourceKeyword::Sha1sums => package_base_value_array!(
382                    raw_keyword,
383                    value,
384                    sha1_checksums,
385                    architecture,
386                    architecture_properties,
387                    SkippableChecksum::parser_until_eof,
388                ),
389                SourceKeyword::Sha224sums => package_base_value_array!(
390                    raw_keyword,
391                    value,
392                    sha224_checksums,
393                    architecture,
394                    architecture_properties,
395                    SkippableChecksum::parser_until_eof,
396                ),
397                SourceKeyword::Sha256sums => package_base_value_array!(
398                    raw_keyword,
399                    value,
400                    sha256_checksums,
401                    architecture,
402                    architecture_properties,
403                    SkippableChecksum::parser_until_eof,
404                ),
405                SourceKeyword::Sha384sums => package_base_value_array!(
406                    raw_keyword,
407                    value,
408                    sha384_checksums,
409                    architecture,
410                    architecture_properties,
411                    SkippableChecksum::parser_until_eof,
412                ),
413                SourceKeyword::Sha512sums => package_base_value_array!(
414                    raw_keyword,
415                    value,
416                    sha512_checksums,
417                    architecture,
418                    architecture_properties,
419                    SkippableChecksum::parser_until_eof,
420                ),
421                SourceKeyword::Cksums => package_base_value_array!(
422                    raw_keyword,
423                    value,
424                    crc_checksums,
425                    architecture,
426                    architecture_properties,
427                    SkippableChecksum::parser_until_eof,
428                ),
429            },
430        }
431    }
432
433    let version = FullVersion::new(package_version, package_release, epoch);
434
435    Ok(PackageBase {
436        name,
437        description,
438        url,
439        changelog,
440        licenses,
441        install,
442        groups,
443        options,
444        backups,
445        version,
446        pgp_fingerprints,
447        architectures,
448        architecture_properties,
449        dependencies,
450        optional_dependencies,
451        provides,
452        conflicts,
453        replaces,
454        check_dependencies,
455        make_dependencies,
456        sources,
457        no_extracts,
458        b2_checksums,
459        md5_checksums,
460        sha1_checksums,
461        sha224_checksums,
462        sha256_checksums,
463        sha384_checksums,
464        sha512_checksums,
465        crc_checksums,
466    })
467}