alpm_lint/lint_rules/source_info/
undefined_architecture.rs

1//! Ensures that [SRCINFO] data only contains architecture-specific fields for defined
2//! architectures.
3//!
4//! [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
5
6use std::collections::BTreeMap;
7
8use alpm_types::{Architectures, SystemArchitecture};
9use colored::Colorize;
10use documented::Documented;
11
12use crate::{
13    internal_prelude::*,
14    issue::SourceInfoIssue,
15    lint_rules::source_info::source_info_from_resource,
16};
17
18/// # What it does
19///
20/// Ensures that [SRCINFO] data only contains architecture-specific fields for declared
21/// architectures (see [alpm-architecture]).
22///
23/// # Why is this bad?
24///
25/// Architecture-specific fields can be used to provide overrides for a field on a specific
26/// [alpm-architecture]. If the architecture for an architecture-specific field is not specified in
27/// a [PKGBUILD] or [SRCINFO], the data of the architecture-specific fields is unused. Such fields
28/// are often remnants of architecture removals in the respective [PKGBUILD] that were not fully
29/// cleaned up.
30///
31/// # Example
32///
33/// ```ini,ignore
34/// pkgbase = test
35///     pkgver = 1.0.0
36///     pkgrel = 1
37///     arch = x86_64
38///     # A source property for the aarch64 architecture which isn't specified above.
39///     source_aarch64 = https://domain.tld/testing/aarch_64_test.tar.gz
40///     source_x86_64 = https://domain.tld/testing/x86_64_test.tar.gz
41/// ```
42///
43/// Use instead:
44///
45/// ```ini,ignore
46/// pkgbase = test
47///     pkgver = 1.0.0
48///     pkgrel = 1
49///     arch = x86_64
50///     source_x86_64 = https://domain.tld/testing/x86_64_test.tar.gz
51/// ```
52///
53/// [PKGBUILD]: https://man.archlinux.org/man/PKGBUILD.5
54/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
55/// [alpm-architecture]: https://alpm.archlinux.page/specifications/alpm-architecture.7.html
56#[derive(Clone, Debug, Documented)]
57pub struct UndefinedArchitecture {}
58
59impl UndefinedArchitecture {
60    /// Create a new, boxed instance of [`UndefinedArchitecture`].
61    pub fn new_boxed(_: &LintRuleConfiguration) -> Box<dyn LintRule> {
62        Box::new(Self {})
63    }
64}
65
66/// Check if a system architecture is contained by a (set) of [Architectures].
67fn contains_architecture(left: &Architectures, right: &SystemArchitecture) -> bool {
68    // If left is `Any`, all architectures are valid.
69    let Architectures::Some(left) = left else {
70        return true;
71    };
72
73    left.contains(right)
74}
75
76impl LintRule for UndefinedArchitecture {
77    fn name(&self) -> &'static str {
78        "undefined_architecture"
79    }
80
81    fn scope(&self) -> LintScope {
82        LintScope::SourceInfo
83    }
84
85    fn documentation(&self) -> String {
86        UndefinedArchitecture::DOCS.into()
87    }
88
89    fn help_text(&self) -> String {
90        r#"Architecture-specific fields should only be used for declared architectures.
91
92Make sure all architecture-specific fields correspond to architectures declared in the 'arch' field.
93"#
94        .into()
95    }
96
97    fn run(&self, resources: &Resources, issues: &mut Vec<LintIssue>) -> Result<(), Error> {
98        // Extract the SourceInfo from the given resources.
99        let source_info = source_info_from_resource(resources, self.scoped_name())?;
100
101        let base_archs = &source_info.base.architectures;
102
103        // Check package base architecture properties
104        for arch in source_info.base.architecture_properties.keys() {
105            if !contains_architecture(base_archs, arch) {
106                issues.push(LintIssue {
107                    lint_rule: self.scoped_name(),
108                    level: self.level(),
109                    help_text: self.help_text(),
110                    scope: self.scope(),
111                    issue_type: SourceInfoIssue::Generic {
112                            summary: "found field for an undefined architecture".to_string(),
113                            arrow_line: None,
114                            message: format!(
115                                "An architecture-specific field is used for the undeclared architecture '{}'",
116                                arch.to_string().bold()
117                            )
118                        }
119                        .into(),
120                    links: std::collections::BTreeMap::new(),
121                });
122            }
123        }
124
125        // Check package architecture properties
126        for package in &source_info.packages {
127            let package_archs = if let Some(archs) = &package.architectures {
128                archs
129            } else {
130                base_archs
131            };
132
133            for arch in package.architecture_properties.keys() {
134                if !contains_architecture(package_archs, arch) {
135                    issues.push(LintIssue::from_rule(self,
136                        SourceInfoIssue::Generic {
137                                summary: "found variable for an undefined architecture".to_string(),
138                                arrow_line: Some(format!("for package '{}'", package.name)),
139                                message: format!(
140                                    "An architecture-specific variable has been for the architecture '{}'",
141                                    arch.to_string().bold()
142                                )
143                            }.into(),
144                    ));
145                }
146            }
147        }
148
149        Ok(())
150    }
151
152    fn extra_links(&self) -> Option<BTreeMap<String, String>> {
153        let mut links = BTreeMap::new();
154        links.insert(
155            "PKGBUILD man page".to_string(),
156            "https://man.archlinux.org/man/PKGBUILD.5".to_string(),
157        );
158        links.insert(
159            "SRCINFO specification".to_string(),
160            "https://alpm.archlinux.page/specifications/SRCINFO.5.html".to_string(),
161        );
162        links.insert(
163            "alpm-architecture specification".to_string(),
164            "https://alpm.archlinux.page/specifications/alpm-architecture.7.html".to_string(),
165        );
166
167        Some(links)
168    }
169}