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