alpm_srcinfo/source_info/
package_base.rs

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