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