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}