alpm_srcinfo/source_info/v1/
package_base.rs

1//! Handling of metadata found in the `pkgbase` section of SRCINFO data.
2use std::collections::{BTreeMap, HashSet};
3
4use alpm_types::{
5    Architecture,
6    Epoch,
7    License,
8    MakepkgOption,
9    Name,
10    OpenPGPIdentifier,
11    OptionalDependency,
12    PackageDescription,
13    PackageRelation,
14    PackageRelease,
15    PackageVersion,
16    RelativePath,
17    SkippableChecksum,
18    Source,
19    Url,
20    digests::{Blake2b512, Md5, Sha1, Sha224, Sha256, Sha384, Sha512},
21};
22use serde::{Deserialize, Serialize};
23
24use super::package::PackageArchitecture;
25#[cfg(doc)]
26use crate::{MergedPackage, SourceInfoV1, source_info::v1::package::Package};
27use crate::{
28    error::{SourceInfoError, lint, unrecoverable},
29    lints::{
30        duplicate_architecture,
31        missing_architecture_for_property,
32        non_spdx_license,
33        unsafe_checksum,
34    },
35    relation::RelationOrSoname,
36    source_info::{
37        helper::ordered_hashset,
38        parser::{self, PackageBaseProperty, RawPackageBase, SharedMetaProperty},
39    },
40};
41
42/// Package base metadata based on the `pkgbase` section in SRCINFO data.
43///
44/// All values in this struct act as default values for all [`Package`]s in the scope of specific
45/// SRCINFO data.
46///
47/// A [`MergedPackage`] (a full view on a package's metadata) can be created using
48/// [`SourceInfoV1::packages_for_architecture`].
49#[derive(Clone, Debug, Deserialize, Serialize)]
50pub struct PackageBase {
51    pub name: Name,
52    pub description: Option<PackageDescription>,
53    pub url: Option<Url>,
54    pub changelog: Option<RelativePath>,
55    pub licenses: Vec<License>,
56
57    // Build or package management related meta fields
58    pub install: Option<RelativePath>,
59    pub groups: Vec<String>,
60    pub options: Vec<MakepkgOption>,
61    pub backups: Vec<RelativePath>,
62
63    // These metadata fields are PackageBase specific
64    /// The version of the package
65    pub package_version: PackageVersion,
66    /// The release of the package
67    pub package_release: PackageRelease,
68    /// The epoch of the package
69    pub epoch: Option<Epoch>,
70
71    pub pgp_fingerprints: Vec<OpenPGPIdentifier>,
72
73    // Architectures and architecture specific properties
74    #[serde(serialize_with = "ordered_hashset")]
75    pub architectures: HashSet<Architecture>,
76    pub architecture_properties: BTreeMap<Architecture, PackageBaseArchitecture>,
77
78    pub dependencies: Vec<RelationOrSoname>,
79    pub optional_dependencies: Vec<OptionalDependency>,
80    pub provides: Vec<RelationOrSoname>,
81    pub conflicts: Vec<PackageRelation>,
82    pub replaces: Vec<PackageRelation>,
83    // The following dependencies are build-time specific dependencies.
84    // `makepkg` expects all dependencies for all split packages to be specified in the
85    // PackageBase.
86    pub check_dependencies: Vec<PackageRelation>,
87    pub make_dependencies: Vec<PackageRelation>,
88
89    pub sources: Vec<Source>,
90    pub no_extracts: Vec<String>,
91    pub b2_checksums: Vec<SkippableChecksum<Blake2b512>>,
92    pub md5_checksums: Vec<SkippableChecksum<Md5>>,
93    pub sha1_checksums: Vec<SkippableChecksum<Sha1>>,
94    pub sha224_checksums: Vec<SkippableChecksum<Sha224>>,
95    pub sha256_checksums: Vec<SkippableChecksum<Sha256>>,
96    pub sha384_checksums: Vec<SkippableChecksum<Sha384>>,
97    pub sha512_checksums: Vec<SkippableChecksum<Sha512>>,
98}
99
100/// Architecture specific package base properties for use in [`PackageBase`].
101///
102/// For each [`Architecture`] defined in [`PackageBase::architectures`] a
103/// [`PackageBaseArchitecture`] is present in [`PackageBase::architecture_properties`].
104#[derive(Clone, Debug, Default, Deserialize, Serialize)]
105pub struct PackageBaseArchitecture {
106    pub dependencies: Vec<RelationOrSoname>,
107    pub optional_dependencies: Vec<OptionalDependency>,
108    pub provides: Vec<RelationOrSoname>,
109    pub conflicts: Vec<PackageRelation>,
110    pub replaces: Vec<PackageRelation>,
111    // The following dependencies are build-time specific dependencies.
112    // `makepkg` expects all dependencies for all split packages to be specified in the
113    // PackageBase.
114    pub check_dependencies: Vec<PackageRelation>,
115    pub make_dependencies: Vec<PackageRelation>,
116
117    pub sources: Vec<Source>,
118    pub no_extracts: Vec<String>,
119    pub b2_checksums: Vec<SkippableChecksum<Blake2b512>>,
120    pub md5_checksums: Vec<SkippableChecksum<Md5>>,
121    pub sha1_checksums: Vec<SkippableChecksum<Sha1>>,
122    pub sha224_checksums: Vec<SkippableChecksum<Sha224>>,
123    pub sha256_checksums: Vec<SkippableChecksum<Sha256>>,
124    pub sha384_checksums: Vec<SkippableChecksum<Sha384>>,
125    pub sha512_checksums: Vec<SkippableChecksum<Sha512>>,
126}
127
128impl PackageBaseArchitecture {
129    /// Merges in the architecture specific properties of a package.
130    ///
131    /// Each existing field of `properties` overrides the architecture-independent pendant on
132    /// `self`.
133    pub fn merge_package_properties(&mut self, properties: PackageArchitecture) {
134        properties.dependencies.merge_vec(&mut self.dependencies);
135        properties
136            .optional_dependencies
137            .merge_vec(&mut self.optional_dependencies);
138        properties.provides.merge_vec(&mut self.provides);
139        properties.conflicts.merge_vec(&mut self.conflicts);
140        properties.replaces.merge_vec(&mut self.replaces);
141    }
142}
143
144/// Handles all potentially architecture specific Vector entries in the [`PackageBase::from_parsed`]
145/// function.
146///
147/// If no architecture is encountered, it simply adds the value on the [`PackageBase`] itself.
148/// Otherwise, it's added to the respective [`PackageBase::architecture_properties`].
149///
150/// Furthermore, adds linter warnings if an architecture is encountered that doesn't exist in the
151/// [`PackageBase::architectures`].
152macro_rules! package_base_arch_prop {
153    (
154        $line:ident,
155        $errors:ident,
156        $architectures:ident,
157        $architecture_properties:ident,
158        $arch_property:ident,
159        $field_name:ident,
160    ) => {
161        // Check if the property is architecture specific.
162        // If so, we have to perform some checks and preparation
163        if let Some(architecture) = $arch_property.architecture {
164            // Make sure the architecture specific properties are initialized.
165            let architecture_properties = $architecture_properties
166                .entry(architecture)
167                .or_insert(PackageBaseArchitecture::default());
168
169            // Set the architecture specific value.
170            architecture_properties
171                .$field_name
172                .push($arch_property.value);
173
174            // Throw an error for all architecture specific properties that don't have
175            // an explicit `arch` statement. This is considered bad style.
176            // Also handle the special `Any` [Architecture], which allows all architectures.
177            if !$architectures.contains(&architecture)
178                && !$architectures.contains(&Architecture::Any)
179            {
180                missing_architecture_for_property($errors, $line, architecture);
181            }
182        } else {
183            $field_name.push($arch_property.value)
184        }
185    };
186}
187
188impl PackageBase {
189    /// Creates a new [`PackageBase`] instance from a [`RawPackageBase`].
190    ///
191    /// # Parameters
192    ///
193    /// - `line_start`: The number of preceding lines, so that error/lint messages can reference the
194    ///   correct lines.
195    /// - `parsed`: The [`RawPackageBase`] representation of the SRCINFO data. The input guarantees
196    ///   that the keyword definitions have been parsed correctly, but not yet that they represent
197    ///   valid SRCINFO data as a whole.
198    /// - `errors`: All errors and lints encountered during the creation of the [`PackageBase`].
199    ///
200    /// # Errors
201    ///
202    /// This function does not return a [`Result`], but instead relies on aggregating all lints,
203    /// warnings and errors in `errors`. This allows to keep the function call recoverable, so
204    /// that all errors and lints can be returned all at once.
205    pub fn from_parsed(
206        line_start: usize,
207        parsed: RawPackageBase,
208        errors: &mut Vec<SourceInfoError>,
209    ) -> Self {
210        let mut description = None;
211        let mut url = None;
212        let mut licenses = Vec::new();
213        let mut changelog = None;
214        let mut architectures = HashSet::new();
215        let mut architecture_properties = BTreeMap::new();
216
217        // Build or package management related meta fields
218        let mut install = None;
219        let mut groups = Vec::new();
220        let mut options = Vec::new();
221        let mut backups = Vec::new();
222
223        // These metadata fields are PackageBase specific
224        let mut epoch: Option<Epoch> = None;
225        let mut package_version: Option<PackageVersion> = None;
226        let mut package_release: Option<PackageRelease> = None;
227        let mut pgp_fingerprints = Vec::new();
228
229        let mut dependencies = Vec::new();
230        let mut optional_dependencies = Vec::new();
231        let mut provides = Vec::new();
232        let mut conflicts = Vec::new();
233        let mut replaces = Vec::new();
234        // The following dependencies are build-time specific dependencies.
235        // `makepkg` expects all dependencies for all split packages to be specified in the
236        // PackageBase.
237        let mut check_dependencies = Vec::new();
238        let mut make_dependencies = Vec::new();
239
240        let mut sources = Vec::new();
241        let mut no_extracts = Vec::new();
242        let mut b2_checksums = Vec::new();
243        let mut md5_checksums = Vec::new();
244        let mut sha1_checksums = Vec::new();
245        let mut sha224_checksums = Vec::new();
246        let mut sha256_checksums = Vec::new();
247        let mut sha384_checksums = Vec::new();
248        let mut sha512_checksums = Vec::new();
249
250        // First up check all input for potential architecture declarations.
251        // We need this to do proper linting when doing our actual pass through the file.
252        for (index, prop) in parsed.properties.iter().enumerate() {
253            // We're only interested in architecture properties.
254            let PackageBaseProperty::MetaProperty(SharedMetaProperty::Architecture(architecture)) =
255                prop
256            else {
257                continue;
258            };
259
260            // Calculate the actual line in the document based on any preceding lines.
261            // We have to add one, as lines aren't 0 indexed.
262            let line = index + line_start;
263
264            // Lint to make sure there aren't duplicate architectures declarations.
265            if architectures.contains(architecture) {
266                duplicate_architecture(errors, line, *architecture);
267            }
268
269            // Add the architecture in case it hasn't already.
270            architectures.insert(*architecture);
271            architecture_properties
272                .entry(*architecture)
273                .or_insert(PackageBaseArchitecture::default());
274        }
275
276        // If no architecture is set, `makepkg` simply uses the host system as the default value.
277        // In practice this translates to `any`, as this package is valid to be build on any
278        // system as long as `makepkg` is executed on that system.
279        if architectures.is_empty() {
280            errors.push(lint(
281                None,
282                "No architecture has been specified. Assuming `any`.",
283            ));
284            architectures.insert(Architecture::Any);
285            architecture_properties
286                .entry(Architecture::Any)
287                .or_insert(PackageBaseArchitecture::default());
288        }
289
290        for (index, prop) in parsed.properties.into_iter().enumerate() {
291            // Calculate the actual line in the document based on any preceding lines.
292            let line = index + line_start;
293            match prop {
294                // Skip empty lines and comments
295                PackageBaseProperty::EmptyLine | PackageBaseProperty::Comment(_) => continue,
296                PackageBaseProperty::PackageVersion(inner) => package_version = Some(inner),
297                PackageBaseProperty::PackageRelease(inner) => package_release = Some(inner),
298                PackageBaseProperty::PackageEpoch(inner) => epoch = Some(inner),
299                PackageBaseProperty::ValidPgpKeys(inner) => {
300                    if let OpenPGPIdentifier::OpenPGPKeyId(_) = &inner {
301                        errors.push(lint(
302                            Some(line),
303                            concat!(
304                                "OpenPGP Key IDs are highly discouraged, as the length doesn't guarantee uniqueness.",
305                                "\nUse an OpenPGP v4 fingerprint instead.",
306                            )
307                        ));
308                    }
309                    pgp_fingerprints.push(inner);
310                }
311                PackageBaseProperty::CheckDependency(arch_property) => {
312                    package_base_arch_prop!(
313                        line,
314                        errors,
315                        architectures,
316                        architecture_properties,
317                        arch_property,
318                        check_dependencies,
319                    )
320                }
321                PackageBaseProperty::MakeDependency(arch_property) => {
322                    package_base_arch_prop!(
323                        line,
324                        errors,
325                        architectures,
326                        architecture_properties,
327                        arch_property,
328                        make_dependencies,
329                    )
330                }
331                PackageBaseProperty::MetaProperty(shared_meta_property) => {
332                    match shared_meta_property {
333                        SharedMetaProperty::Description(inner) => description = Some(inner),
334                        SharedMetaProperty::Url(inner) => url = Some(inner),
335                        SharedMetaProperty::License(inner) => {
336                            // Create lints for non-spdx licenses.
337                            if let License::Unknown(_) = &inner {
338                                non_spdx_license(errors, line, inner.to_string());
339                            }
340
341                            licenses.push(inner)
342                        }
343                        // We already handled those above.
344                        SharedMetaProperty::Architecture(_) => continue,
345                        SharedMetaProperty::Changelog(inner) => changelog = Some(inner),
346                        SharedMetaProperty::Install(inner) => install = Some(inner),
347                        SharedMetaProperty::Group(inner) => groups.push(inner),
348                        SharedMetaProperty::Option(inner) => options.push(inner),
349                        SharedMetaProperty::Backup(inner) => backups.push(inner),
350                    }
351                }
352                PackageBaseProperty::RelationProperty(relation_property) => match relation_property
353                {
354                    parser::RelationProperty::Dependency(arch_property) => package_base_arch_prop!(
355                        line,
356                        errors,
357                        architectures,
358                        architecture_properties,
359                        arch_property,
360                        dependencies,
361                    ),
362                    parser::RelationProperty::OptionalDependency(arch_property) => {
363                        package_base_arch_prop!(
364                            line,
365                            errors,
366                            architectures,
367                            architecture_properties,
368                            arch_property,
369                            optional_dependencies,
370                        )
371                    }
372                    parser::RelationProperty::Provides(arch_property) => package_base_arch_prop!(
373                        line,
374                        errors,
375                        architectures,
376                        architecture_properties,
377                        arch_property,
378                        provides,
379                    ),
380                    parser::RelationProperty::Conflicts(arch_property) => package_base_arch_prop!(
381                        line,
382                        errors,
383                        architectures,
384                        architecture_properties,
385                        arch_property,
386                        conflicts,
387                    ),
388                    parser::RelationProperty::Replaces(arch_property) => package_base_arch_prop!(
389                        line,
390                        errors,
391                        architectures,
392                        architecture_properties,
393                        arch_property,
394                        replaces,
395                    ),
396                },
397                PackageBaseProperty::SourceProperty(source_property) => match source_property {
398                    parser::SourceProperty::Source(arch_property) => package_base_arch_prop!(
399                        line,
400                        errors,
401                        architectures,
402                        architecture_properties,
403                        arch_property,
404                        sources,
405                    ),
406                    parser::SourceProperty::NoExtract(arch_property) => package_base_arch_prop!(
407                        line,
408                        errors,
409                        architectures,
410                        architecture_properties,
411                        arch_property,
412                        no_extracts,
413                    ),
414                    parser::SourceProperty::B2Checksum(arch_property) => package_base_arch_prop!(
415                        line,
416                        errors,
417                        architectures,
418                        architecture_properties,
419                        arch_property,
420                        b2_checksums,
421                    ),
422                    parser::SourceProperty::Md5Checksum(arch_property) => {
423                        unsafe_checksum(errors, line, "md5");
424                        package_base_arch_prop!(
425                            line,
426                            errors,
427                            architectures,
428                            architecture_properties,
429                            arch_property,
430                            md5_checksums,
431                        );
432                    }
433                    parser::SourceProperty::Sha1Checksum(arch_property) => {
434                        unsafe_checksum(errors, line, "sha1");
435                        package_base_arch_prop!(
436                            line,
437                            errors,
438                            architectures,
439                            architecture_properties,
440                            arch_property,
441                            sha1_checksums,
442                        );
443                    }
444                    parser::SourceProperty::Sha224Checksum(arch_property) => {
445                        package_base_arch_prop!(
446                            line,
447                            errors,
448                            architectures,
449                            architecture_properties,
450                            arch_property,
451                            sha224_checksums,
452                        )
453                    }
454                    parser::SourceProperty::Sha256Checksum(arch_property) => {
455                        package_base_arch_prop!(
456                            line,
457                            errors,
458                            architectures,
459                            architecture_properties,
460                            arch_property,
461                            sha256_checksums,
462                        )
463                    }
464                    parser::SourceProperty::Sha384Checksum(arch_property) => {
465                        package_base_arch_prop!(
466                            line,
467                            errors,
468                            architectures,
469                            architecture_properties,
470                            arch_property,
471                            sha384_checksums,
472                        )
473                    }
474                    parser::SourceProperty::Sha512Checksum(arch_property) => {
475                        package_base_arch_prop!(
476                            line,
477                            errors,
478                            architectures,
479                            architecture_properties,
480                            arch_property,
481                            sha512_checksums,
482                        )
483                    }
484                },
485            }
486        }
487
488        // Handle a missing package_version
489        let package_version = match package_version {
490            Some(package_version) => package_version,
491            None => {
492                errors.push(unrecoverable(
493                    None,
494                    "pkgbase section doesn't contain a 'pkgver' keyword assignment",
495                ));
496                // Set a package version nevertheless, so we continue parsing the rest of the file.
497                PackageVersion::new("0".to_string()).unwrap()
498            }
499        };
500
501        // Handle a missing package_version
502        let package_release = match package_release {
503            Some(package_release) => package_release,
504            None => {
505                errors.push(unrecoverable(
506                    None,
507                    "pkgbase section doesn't contain a 'pkgrel' keyword assignment",
508                ));
509                // Set a package version nevertheless, so we continue parsing the rest of the file.
510                PackageRelease::new(1, None)
511            }
512        };
513
514        PackageBase {
515            name: parsed.name,
516            description,
517            url,
518            licenses,
519            changelog,
520            architectures,
521            architecture_properties,
522            install,
523            groups,
524            options,
525            backups,
526            package_version,
527            package_release,
528            epoch,
529            pgp_fingerprints,
530            dependencies,
531            optional_dependencies,
532            provides,
533            conflicts,
534            replaces,
535            check_dependencies,
536            make_dependencies,
537            sources,
538            no_extracts,
539            b2_checksums,
540            md5_checksums,
541            sha1_checksums,
542            sha224_checksums,
543            sha256_checksums,
544            sha384_checksums,
545            sha512_checksums,
546        }
547    }
548}