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