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
188    // Go through all keywords and handle them.
189    for (raw_keyword, value) in &raw {
190        // Parse the keyword
191        let keyword = PackageBaseKeywords::parser
192            .parse(&raw_keyword.keyword)
193            .map_err(|err| (raw_keyword.clone(), err))?;
194
195        // Parse the architecture suffix if it exists.
196        let architecture = match &raw_keyword.suffix {
197            Some(suffix) => {
198                // SystemArchitecture::parser forbids "any"
199                let arch = SystemArchitecture::parser
200                    .parse(suffix)
201                    .map_err(|err| (raw_keyword.clone(), err))?;
202                Some(arch)
203            }
204            None => None,
205        };
206
207        // Parse and set the value based on which keyword it is.
208        match keyword {
209            PackageBaseKeywords::PkgBase => {
210                // Explicitly handled before
211                // We check for an unexpected suffix anyway in case somebody goofed up.
212                ensure_no_suffix(raw_keyword, architecture)?;
213                unreachable!(
214                    "'pkgbase' has been handled before and should no longer exist without a suffix."
215                )
216            }
217            PackageBaseKeywords::PackageBase(keyword) => match keyword {
218                // Both PkgVer and PkgRel have been handled above.
219                // We check for an unexpected suffix anyway in case somebody goofed up.
220                PackageBaseKeyword::PkgVer => {
221                    ensure_no_suffix(raw_keyword, architecture)?;
222                }
223                PackageBaseKeyword::PkgRel => {
224                    ensure_no_suffix(raw_keyword, architecture)?;
225                }
226                PackageBaseKeyword::Epoch => {
227                    ensure_no_suffix(raw_keyword, architecture)?;
228                    epoch = parse_optional_value(raw_keyword, value, Epoch::parser)?;
229                }
230                PackageBaseKeyword::ValidPGPKeys => {
231                    ensure_no_suffix(raw_keyword, architecture)?;
232                    pgp_fingerprints = parse_value_array(
233                        raw_keyword,
234                        value,
235                        rest.try_map(OpenPGPIdentifier::from_str),
236                    )?;
237                }
238                PackageBaseKeyword::CheckDepends => {
239                    package_base_value_array!(
240                        raw_keyword,
241                        value,
242                        check_dependencies,
243                        architecture,
244                        architecture_properties,
245                        PackageRelation::parser,
246                    )
247                }
248                PackageBaseKeyword::MakeDepends => package_base_value_array!(
249                    raw_keyword,
250                    value,
251                    make_dependencies,
252                    architecture,
253                    architecture_properties,
254                    PackageRelation::parser,
255                ),
256            },
257            PackageBaseKeywords::SharedMeta(keyword) => match keyword {
258                SharedMetaKeyword::PkgDesc => {
259                    ensure_no_suffix(raw_keyword, architecture)?;
260                    description = parse_optional_value(
261                        raw_keyword,
262                        value,
263                        rest.try_map(PackageDescription::from_str),
264                    )?;
265                }
266                SharedMetaKeyword::Url => {
267                    ensure_no_suffix(raw_keyword, architecture)?;
268                    url = parse_optional_value(raw_keyword, value, rest.try_map(Url::from_str))?;
269                }
270                SharedMetaKeyword::License => {
271                    ensure_no_suffix(raw_keyword, architecture)?;
272                    licenses =
273                        parse_value_array(raw_keyword, value, rest.try_map(License::from_str))?;
274                }
275                SharedMetaKeyword::Arch => {
276                    ensure_no_suffix(raw_keyword, architecture)?;
277                    architectures = parse_arch_array(raw_keyword, value)?;
278                }
279                SharedMetaKeyword::Changelog => {
280                    ensure_no_suffix(raw_keyword, architecture)?;
281                    changelog = parse_optional_value(
282                        raw_keyword,
283                        value,
284                        rest.try_map(Changelog::from_str),
285                    )?;
286                }
287                SharedMetaKeyword::Install => {
288                    ensure_no_suffix(raw_keyword, architecture)?;
289                    install =
290                        parse_optional_value(raw_keyword, value, rest.try_map(Install::from_str))?;
291                }
292                SharedMetaKeyword::Groups => {
293                    ensure_no_suffix(raw_keyword, architecture)?;
294                    groups = value.clone().as_owned_vec();
295                }
296                SharedMetaKeyword::Options => {
297                    ensure_no_suffix(raw_keyword, architecture)?;
298                    options = parse_value_array(raw_keyword, value, MakepkgOption::parser)?;
299                }
300                SharedMetaKeyword::Backup => {
301                    ensure_no_suffix(raw_keyword, architecture)?;
302                    backups =
303                        parse_value_array(raw_keyword, value, rest.try_map(Backup::from_str))?;
304                }
305            },
306            PackageBaseKeywords::Relation(keyword) => match keyword {
307                RelationKeyword::Depends => package_base_value_array!(
308                    raw_keyword,
309                    value,
310                    dependencies,
311                    architecture,
312                    architecture_properties,
313                    RelationOrSoname::parser,
314                ),
315                RelationKeyword::OptDepends => package_base_value_array!(
316                    raw_keyword,
317                    value,
318                    optional_dependencies,
319                    architecture,
320                    architecture_properties,
321                    OptionalDependency::parser,
322                ),
323                RelationKeyword::Provides => package_base_value_array!(
324                    raw_keyword,
325                    value,
326                    provides,
327                    architecture,
328                    architecture_properties,
329                    RelationOrSoname::parser,
330                ),
331                RelationKeyword::Conflicts => package_base_value_array!(
332                    raw_keyword,
333                    value,
334                    conflicts,
335                    architecture,
336                    architecture_properties,
337                    PackageRelation::parser,
338                ),
339                RelationKeyword::Replaces => package_base_value_array!(
340                    raw_keyword,
341                    value,
342                    replaces,
343                    architecture,
344                    architecture_properties,
345                    PackageRelation::parser,
346                ),
347            },
348
349            PackageBaseKeywords::Source(keyword) => match keyword {
350                SourceKeyword::NoExtract => {
351                    ensure_no_suffix(raw_keyword, architecture)?;
352                    no_extracts = value.clone().as_owned_vec();
353                }
354                SourceKeyword::Source => package_base_value_array!(
355                    raw_keyword,
356                    value,
357                    sources,
358                    architecture,
359                    architecture_properties,
360                    rest.try_map(Source::from_str),
361                ),
362                SourceKeyword::B2sums => package_base_value_array!(
363                    raw_keyword,
364                    value,
365                    b2_checksums,
366                    architecture,
367                    architecture_properties,
368                    SkippableChecksum::parser,
369                ),
370                SourceKeyword::Md5sums => package_base_value_array!(
371                    raw_keyword,
372                    value,
373                    md5_checksums,
374                    architecture,
375                    architecture_properties,
376                    SkippableChecksum::parser,
377                ),
378                SourceKeyword::Sha1sums => package_base_value_array!(
379                    raw_keyword,
380                    value,
381                    sha1_checksums,
382                    architecture,
383                    architecture_properties,
384                    SkippableChecksum::parser,
385                ),
386                SourceKeyword::Sha224sums => package_base_value_array!(
387                    raw_keyword,
388                    value,
389                    sha224_checksums,
390                    architecture,
391                    architecture_properties,
392                    SkippableChecksum::parser,
393                ),
394                SourceKeyword::Sha256sums => package_base_value_array!(
395                    raw_keyword,
396                    value,
397                    sha256_checksums,
398                    architecture,
399                    architecture_properties,
400                    SkippableChecksum::parser,
401                ),
402                SourceKeyword::Sha384sums => package_base_value_array!(
403                    raw_keyword,
404                    value,
405                    sha384_checksums,
406                    architecture,
407                    architecture_properties,
408                    SkippableChecksum::parser,
409                ),
410                SourceKeyword::Sha512sums => package_base_value_array!(
411                    raw_keyword,
412                    value,
413                    sha512_checksums,
414                    architecture,
415                    architecture_properties,
416                    SkippableChecksum::parser,
417                ),
418            },
419        }
420    }
421
422    let version = FullVersion::new(package_version, package_release, epoch);
423
424    Ok(PackageBase {
425        name,
426        description,
427        url,
428        changelog,
429        licenses,
430        install,
431        groups,
432        options,
433        backups,
434        version,
435        pgp_fingerprints,
436        architectures,
437        architecture_properties,
438        dependencies,
439        optional_dependencies,
440        provides,
441        conflicts,
442        replaces,
443        check_dependencies,
444        make_dependencies,
445        sources,
446        no_extracts,
447        b2_checksums,
448        md5_checksums,
449        sha1_checksums,
450        sha224_checksums,
451        sha256_checksums,
452        sha384_checksums,
453        sha512_checksums,
454    })
455}