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