alpm_lint/lint_rules/source_info/
invalid_spdx_license.rs

1//! Ensures that license expressions in [SRCINFO] data are [SPDX] compliant.
2//!
3//! [SPDX]: https://spdx.org/licenses/
4//! [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
5
6use std::collections::BTreeMap;
7
8use alpm_srcinfo::source_info::v1::package::Override;
9use documented::Documented;
10
11use crate::{
12    Level,
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 each license in a [SRCINFO] is a valid SPDX license expression.
21///
22/// # Why is this bad?
23///
24/// The license attribution for packages clearly defines under what license(s) a package is
25/// distributed. When not using valid SPDX license identifiers to describe the license of a package,
26/// it may be unclear what license applies for it. Unclear license attribution has implication for
27/// the reuse of the package in binary form and whether source code must be made available for it.
28/// For this reason, Arch Linux decided to only allow valid SPDX license expressions (see [RFC
29/// 0016]).
30///
31/// # Examples
32///
33/// ```ini,ignore
34/// pkgbase = test
35///     pkgver = 1.0.0
36///     pkgrel = 1
37///     arch = x86_64
38///     license = Apache
39/// ```
40///
41/// Use instead:
42///
43/// ```ini,ignore
44/// pkgbase = test
45///     pkgver = 1.0.0
46///     pkgrel = 1
47///     arch = x86_64
48///     license = Apache-2.0
49/// ```
50///
51/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
52/// [RFC 0016]: https://rfc.archlinux.page/0016-spdx-license-identifiers/
53#[derive(Clone, Debug, Documented)]
54pub struct NotSPDX {}
55
56impl NotSPDX {
57    /// Create a new, boxed instance of [`NotSPDX`].
58    pub fn new_boxed(_: &LintRuleConfiguration) -> Box<dyn LintRule> {
59        Box::new(Self {})
60    }
61}
62
63impl LintRule for NotSPDX {
64    fn name(&self) -> &'static str {
65        "invalid_spdx_license"
66    }
67
68    fn scope(&self) -> LintScope {
69        LintScope::SourceInfo
70    }
71
72    fn level(&self) -> Level {
73        Level::Deny
74    }
75
76    fn documentation(&self) -> String {
77        NotSPDX::DOCS.into()
78    }
79
80    fn help_text(&self) -> String {
81        r#"Licenses should use SPDX identifiers as specified in the Arch Linux RFC 0016.
82
83SPDX license identifiers provide a standardized way to identify licenses.
84- Apache-2.0 (instead of "Apache")
85- GPL-3.0-or-later or GPL-3.0-only (instead of "GPL3")
86- MIT (instead of "MIT License")
87"#
88        .into()
89    }
90
91    fn run(&self, resources: &Resources, issues: &mut Vec<LintIssue>) -> Result<(), Error> {
92        // Extract the SourceInfo from the given resources.
93        let source_info = source_info_from_resource(resources, self.scoped_name())?;
94
95        // Check licenses in the base package
96        for license in &source_info.base.licenses {
97            if !license.is_spdx() {
98                issues.push(LintIssue::from_rule(
99                    self,
100                    SourceInfoIssue::BaseField {
101                        field_name: "license".to_string(),
102                        value: license.to_string(),
103                        context: "License is not SPDX compliant".to_string(),
104                        architecture: None,
105                    }
106                    .into(),
107                ));
108            }
109        }
110
111        // Check licenses for all split packages
112        for package in &source_info.packages {
113            // If we don't have an override, there's nothing to check.
114            let Override::Yes { value } = &package.licenses else {
115                continue;
116            };
117
118            for license in value {
119                if !license.is_spdx() {
120                    issues.push(LintIssue::from_rule(
121                        self,
122                        SourceInfoIssue::PackageField {
123                            package_name: package.name.to_string(),
124                            field_name: "license".to_string(),
125                            value: license.to_string(),
126                            context: "License is not SPDX compliant".to_string(),
127                            architecture: None,
128                        }
129                        .into(),
130                    ));
131                }
132            }
133        }
134
135        Ok(())
136    }
137
138    /// Return the associated links for this lint rule.
139    fn extra_links(&self) -> Option<BTreeMap<String, String>> {
140        let mut links = BTreeMap::new();
141        links.insert(
142            "Arch Linux RFC 0016".to_string(),
143            "https://rfc.archlinux.page/0016-spdx-license-identifiers/".to_string(),
144        );
145        links.insert(
146            "SRCINFO specification".to_string(),
147            "https://alpm.archlinux.page/specifications/SRCINFO.5.html".to_string(),
148        );
149
150        Some(links)
151    }
152}