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