Skip to main content

alpm_srcinfo/pkgbuild_bridge/
package.rs

1//! Convert parsed [`BridgeOutput::packages`] output into [`Package`]s.
2
3use std::{collections::HashMap, str::FromStr};
4
5use alpm_parsers::prelude::*;
6#[cfg(doc)]
7use alpm_pkgbuild::bridge::BridgeOutput;
8use alpm_pkgbuild::bridge::{ClearableValue, Keyword, RawPackageName};
9use alpm_types::{
10    Architecture,
11    Backup,
12    Changelog,
13    Group,
14    Install,
15    License,
16    MakepkgOption,
17    Name,
18    OptionalDependency,
19    PackageDescription,
20    PackageRelation,
21    RelationOrSoname,
22    SystemArchitecture,
23    Url,
24};
25use strum::VariantNames;
26use winnow::{
27    combinator::{alt, cut_err},
28    error::{ErrMode, ParseError},
29    token::rest,
30};
31
32use super::ensure_no_suffix;
33use crate::{
34    pkgbuild_bridge::error::BridgeError,
35    source_info::{
36        parser::{RelationKeyword, SharedMetaKeyword},
37        v1::package::{Override, Package, PackageArchitecture},
38    },
39};
40
41/// Converts parsed [`BridgeOutput::packages`] output into [`Package`]s.
42///
43/// # Enforced Invariants
44///
45/// All scoped package variables must have a respective entry in `pkgbase.pkgname`.
46///
47/// # Errors
48///
49/// Returns an error if
50///
51/// - a `package` function without an [alpm-package-name] suffix exists in an [alpm-split-package]
52///   setup,
53/// - a value cannot be turned into its [`alpm_types`] equivalent,
54/// - multiple values exist for a field that only accepts a singular value,
55/// - an [alpm-architecture] is duplicated,
56/// - an [alpm-architecture] is cleared in `package` function,
57/// - or an [alpm-architecture] suffix is set on a keyword that does not support it.
58///
59/// [alpm-architecture]: https://alpm.archlinux.page/specifications/alpm-architecture.7.html
60/// [alpm-package-name]: https://alpm.archlinux.page/specifications/alpm-package-name.7.html
61/// [alpm-split-package]: https://alpm.archlinux.page/specifications/alpm-split-package.7.html
62pub(crate) fn handle_packages(
63    base_package: Name,
64    valid_packages: Vec<Name>,
65    raw_values: HashMap<RawPackageName, HashMap<Keyword, ClearableValue>>,
66) -> Result<Vec<Package>, BridgeError> {
67    let mut package_map: HashMap<Name, Package> = HashMap::new();
68
69    for (name, values) in raw_values {
70        // Check if the variable is assigned to a specific split package.
71        // If it isn't, use the name of the base package instead, which is the default.
72        let name = if let Some(name) = name.0 {
73            Name::parser.parse(Input::new(&name)).map_err(|err| {
74                BridgeError::InvalidPackageName {
75                    name: name.clone(),
76                    error: err.into(),
77                }
78            })?
79        } else {
80            // If this is a literal `package` function we have to make sure that this isn't a split
81            // package! Split package `package` functions must have a `_$name` suffix.
82            if valid_packages.len() > 1 {
83                return Err(BridgeError::UnusedPackageFunction(base_package));
84            }
85
86            base_package.clone()
87        };
88
89        // Make sure the package has been declared in the package base section.
90        if !valid_packages.contains(&name) {
91            return Err(BridgeError::UndeclaredPackageName(name.to_string()));
92        }
93
94        // Get the package on which the properties should be set.
95        let package = package_map.entry(name.clone()).or_insert(name.into());
96
97        handle_package(package, values)?;
98    }
99
100    // Convert the package map into a vector that follows the same order as the `pkgbase`
101    let mut packages = Vec::new();
102    for name in valid_packages {
103        let Some(package) = package_map.remove(&name) else {
104            // Create a empty package entry for any packages that don't have any variable set and
105            // thereby haven't been initialized yet.
106            packages.push(name.into());
107            continue;
108        };
109
110        packages.push(package);
111    }
112
113    Ok(packages)
114}
115
116/// The combination of all keywords that're valid in the scope of a `package` section.
117enum PackageKeyword {
118    Relation(RelationKeyword),
119    SharedMeta(SharedMetaKeyword),
120}
121
122impl PackageKeyword {
123    /// Recognizes any of the [`PackageKeyword`] in an input string slice.
124    ///
125    /// Does not consume input and stops after any keyword matches.
126    ///
127    /// # Errors
128    ///
129    /// Returns an error, if an unknown keyword is encountered.
130    pub fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, PackageKeyword> {
131        cut_err(alt((
132            RelationKeyword::parser.map(PackageKeyword::Relation),
133            SharedMetaKeyword::parser.map(PackageKeyword::SharedMeta),
134        )))
135        .expected_strings(
136            [RelationKeyword::VARIANTS, SharedMetaKeyword::VARIANTS]
137                .into_iter()
138                .flatten(),
139        )
140        .layer("package base property keyword")
141        .parse_next(input)
142    }
143}
144
145/// Ensures that in a combination of [`Keyword`] and [`ClearableValue`], a
146/// [`ClearableValue::Single`] is used and returns the value.
147///
148/// # Errors
149///
150/// Returns an error, if value is a [`ClearableValue::Array`].
151fn ensure_single_clearable_value<'a>(
152    keyword: &Keyword,
153    value: &'a ClearableValue,
154) -> Result<&'a Option<String>, BridgeError> {
155    match value {
156        ClearableValue::Single(value) => Ok(value),
157        ClearableValue::Array(values) => Err(BridgeError::UnexpectedArray {
158            keyword: keyword.clone(),
159            values: values.clone().unwrap_or_default().clone(),
160        }),
161    }
162}
163
164/// Ensures that a combination of [`Keyword`] and [`ClearableValue`] uses a
165/// [`ClearableValue::Single`] and parses the value as a specific type.
166///
167/// # Errors
168///
169/// Returns an error if
170///
171/// - `value` cannot be parsed as a specific type,
172/// - or `value` is a [`ClearableValue::Array`].
173fn parse_clearable_value<'a, O, P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>>(
174    keyword: &Keyword,
175    value: &'a ClearableValue,
176    mut parser: P,
177) -> Result<Override<O>, BridgeError> {
178    // Make sure we have no array
179    let value = ensure_single_clearable_value(keyword, value)?;
180
181    // If the value is `None`, it indicates a cleared value.
182    let Some(value) = value else {
183        return Ok(Override::Clear);
184    };
185
186    let parsed_value = parser
187        .parse(Input::new(value))
188        .map_err(|err| (keyword.clone(), err))?;
189
190    Ok(Override::Yes {
191        value: parsed_value,
192    })
193}
194
195/// Parses all elements of a [`ClearableValue`] as a [`Vec`] of specific types.
196///
197/// # Legacy support
198///
199/// This does not differentiate between [`ClearableValue::Single`] and [`ClearableValue::Array`]
200/// variants, as [PKGBUILD] files allow both notations for array values.
201///
202/// Modern versions of [makepkg] enforce that certain values **must** be arrays.
203/// However, to be able to parse both historic and modern [PKGBUILD] files this function is less
204/// strict.
205///
206/// # Errors
207///
208/// Returns an error if the elements of `value` cannot be parsed as a [`Vec`] of a specific type.
209///
210/// [PKGBUILD]: https://man.archlinux.org/man/PKGBUILD.5
211/// [makepkg]: https://man.archlinux.org/man/makepkg.8
212// The error type is 184 bytes+ large, which is still completely acceptable for us.
213#[expect(clippy::result_large_err)]
214fn parse_clearable_value_array<'a, O, P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>>(
215    keyword: &Keyword,
216    value: &'a ClearableValue,
217    mut parser: P,
218) -> Result<Override<Vec<O>>, BridgeError> {
219    let values = match value {
220        ClearableValue::Single(value) => {
221            let Some(value) = value else {
222                return Ok(Override::Clear);
223            };
224            // An empty string is considered a clear.
225            if value.is_empty() {
226                return Ok(Override::Clear);
227            }
228            let value = parser
229                .parse(Input::new(value))
230                .map_err(|err| (keyword.clone(), err))?;
231
232            vec![value]
233        }
234        ClearableValue::Array(values) => {
235            let Some(values) = values else {
236                return Ok(Override::Clear);
237            };
238
239            values
240                .iter()
241                .map(|item| {
242                    parser
243                        .parse(Input::new(item))
244                        .map_err(|err| (keyword.clone(), err))
245                })
246                .collect::<Result<Vec<O>, (Keyword, ParseError<Input<'a>, ParseStack<'a>>)>>()?
247        }
248    };
249
250    Ok(Override::Yes { value: values })
251}
252
253/// Handles all potentially architecture specific Vector entries in the [`handle_package`] function.
254///
255/// If no architecture is encountered, it simply adds the value on the [`Package`] itself.
256/// Otherwise, it's added to the respective [`Package::architecture_properties`].
257macro_rules! package_value_array {
258    (
259        $keyword:expr,
260        $value:expr,
261        $package:ident,
262        $field_name:ident,
263        $architecture:ident,
264        $parser:expr,
265    ) => {
266        if let Some(architecture) = $architecture {
267            // Make sure the architecture specific properties are initialized.
268            let architecture_properties = $package
269                .architecture_properties
270                .entry(architecture)
271                .or_insert(PackageArchitecture::default());
272
273            // Set the architecture specific value.
274            architecture_properties.$field_name =
275                parse_clearable_value_array($keyword, $value, $parser)?;
276        } else {
277            $package.$field_name = parse_clearable_value_array($keyword, $value, $parser)?;
278        }
279    };
280}
281
282/// Adds a map of [`Keyword`] and [`ClearableValue`] to a [`Package`].
283///
284/// Handles parsing and type conversions of all raw input into their respective [`alpm_types`]
285/// types.
286///
287/// # Errors
288///
289/// Returns an error if
290///
291/// - one of the values in `values` cannot be converted into its respective [`alpm_types`] type,
292/// - or keywords incompatible with [`Package`] are encountered.
293fn handle_package(
294    package: &mut Package,
295    values: HashMap<Keyword, ClearableValue>,
296) -> Result<(), BridgeError> {
297    for (raw_keyword, value) in values {
298        // Parse the keyword
299        let keyword = PackageKeyword::parser
300            .parse(Input::new(&raw_keyword.keyword))
301            .map_err(|err| (raw_keyword.clone(), err))?;
302
303        // Parse the architecture suffix if it exists.
304        let architecture = match &raw_keyword.suffix {
305            Some(suffix) => {
306                // SystemArchitecture::parser forbids "any"
307                let arch = SystemArchitecture::parser
308                    .parse(Input::new(suffix))
309                    .map_err(|err| (raw_keyword.clone(), err))?;
310                Some(arch)
311            }
312            None => None,
313        };
314
315        // Parse and set the value based on which keyword it is.
316        match keyword {
317            PackageKeyword::Relation(keyword) => match keyword {
318                RelationKeyword::Depends => package_value_array!(
319                    &raw_keyword,
320                    &value,
321                    package,
322                    dependencies,
323                    architecture,
324                    RelationOrSoname::parser_until_eof,
325                ),
326                RelationKeyword::OptDepends => package_value_array!(
327                    &raw_keyword,
328                    &value,
329                    package,
330                    optional_dependencies,
331                    architecture,
332                    OptionalDependency::parser_until_eof,
333                ),
334                RelationKeyword::Provides => package_value_array!(
335                    &raw_keyword,
336                    &value,
337                    package,
338                    provides,
339                    architecture,
340                    RelationOrSoname::parser_until_eof,
341                ),
342                RelationKeyword::Conflicts => package_value_array!(
343                    &raw_keyword,
344                    &value,
345                    package,
346                    conflicts,
347                    architecture,
348                    PackageRelation::parser_until_eof,
349                ),
350                RelationKeyword::Replaces => package_value_array!(
351                    &raw_keyword,
352                    &value,
353                    package,
354                    replaces,
355                    architecture,
356                    PackageRelation::parser_until_eof,
357                ),
358            },
359            PackageKeyword::SharedMeta(keyword) => match keyword {
360                SharedMetaKeyword::PkgDesc => {
361                    ensure_no_suffix(&raw_keyword, architecture)?;
362                    package.description = parse_clearable_value(
363                        &raw_keyword,
364                        &value,
365                        rest.try_map(PackageDescription::from_str),
366                    )?;
367                }
368                SharedMetaKeyword::Url => {
369                    ensure_no_suffix(&raw_keyword, architecture)?;
370                    package.url =
371                        parse_clearable_value(&raw_keyword, &value, rest.try_map(Url::from_str))?;
372                }
373                SharedMetaKeyword::License => {
374                    ensure_no_suffix(&raw_keyword, architecture)?;
375                    package.licenses = parse_clearable_value_array(
376                        &raw_keyword,
377                        &value,
378                        rest.try_map(License::from_str),
379                    )?;
380                }
381                SharedMetaKeyword::Arch => {
382                    ensure_no_suffix(&raw_keyword, architecture)?;
383                    let archs = parse_clearable_value_array(
384                        &raw_keyword,
385                        &value,
386                        Architecture::parser_until_eof,
387                    )?;
388
389                    // Architectures are a bit special as they **are not** allowed to be cleared.
390                    package.architectures = match archs {
391                        Override::No => None,
392                        Override::Clear => {
393                            return Err(BridgeError::UnclearableValue {
394                                keyword: raw_keyword,
395                            });
396                        }
397                        Override::Yes { value } => Some(value.try_into()?),
398                    };
399                }
400                SharedMetaKeyword::Changelog => {
401                    ensure_no_suffix(&raw_keyword, architecture)?;
402                    package.changelog = parse_clearable_value(
403                        &raw_keyword,
404                        &value,
405                        rest.try_map(Changelog::from_str),
406                    )?;
407                }
408                SharedMetaKeyword::Install => {
409                    ensure_no_suffix(&raw_keyword, architecture)?;
410                    package.install = parse_clearable_value(
411                        &raw_keyword,
412                        &value,
413                        rest.try_map(Install::from_str),
414                    )?;
415                }
416                SharedMetaKeyword::Groups => {
417                    ensure_no_suffix(&raw_keyword, architecture)?;
418                    package.groups = parse_clearable_value_array(
419                        &raw_keyword,
420                        &value,
421                        rest.try_map(Group::from_str),
422                    )?;
423                }
424                SharedMetaKeyword::Options => {
425                    ensure_no_suffix(&raw_keyword, architecture)?;
426                    package.options = parse_clearable_value_array(
427                        &raw_keyword,
428                        &value,
429                        MakepkgOption::parser_until_eof,
430                    )?;
431                }
432                SharedMetaKeyword::Backup => {
433                    ensure_no_suffix(&raw_keyword, architecture)?;
434                    package.backups = parse_clearable_value_array(
435                        &raw_keyword,
436                        &value,
437                        rest.try_map(Backup::from_str),
438                    )?;
439                }
440            },
441        }
442    }
443
444    Ok(())
445}