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