alpm_srcinfo/source_info/v1/
package_base.rs

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