libpfu_style/
archgroup.rs

1//! Autobuild arch-group overrides checks.
2
3use std::collections::HashMap;
4
5use anyhow::Result;
6use async_trait::async_trait;
7use kstring::KString;
8use libabbs::apml::ast;
9use libpfu::{
10	Linter, Session, declare_lint, declare_linter,
11	message::{LintMessage, Snippet},
12	walk_apml,
13};
14use log::debug;
15
16declare_linter! {
17	pub ARCH_GROUP_LINTER,
18	ArchGroupLinter,
19	[
20		"missing-archgroup",
21		"redundant-arch-overrides",
22		"acbs-arch-groups",
23	]
24}
25
26declare_lint! {
27	pub MISSING_ARCHGROUP_LINT,
28	"missing-archgroup",
29	Warning,
30	"some arch-groups are missed from arch-overrides"
31}
32
33declare_lint! {
34	pub REDUNDANT_ARCH_OVERRIDES_LINT,
35	"redundant-arch-overrides",
36	Warning,
37	"some arch-overrides are redundant"
38}
39
40declare_lint! {
41	pub ACBS_ARCH_GROUPS_LINT,
42	"acbs-arch-groups",
43	Error,
44	"ACBS does not support arch-groups"
45}
46
47/// Architecture-overridable variables used by ACBS
48const ACBS_VARIABLES: &[&str] = &["PKGDEP", "BUILDDEP"];
49
50#[async_trait]
51impl Linter for ArchGroupLinter {
52	async fn apply(&self, sess: &Session) -> Result<()> {
53		let ab4_data = match &sess.ab4_data {
54			Some(v) => v,
55			None => return Ok(()),
56		};
57		for mut apml in walk_apml(sess) {
58			apml.with_upgraded(|apml| {
59				debug!("Looking for arch-overrides variables in {apml:?}");
60				let mut arch_overrides = HashMap::new();
61				let mut arch_groups = vec![];
62				'vars: for var in &apml.ast()?.0 {
63					let (var_name, target) =
64						if let Some(v) = var.name.split_once("__") {
65							v
66						} else {
67							continue 'vars;
68						};
69
70					let mut included_vars = vec![];
71					match &var.value {
72						ast::VariableValue::String(text) => {
73							for word in &text.0 {
74								match word {
75									ast::Word::Literal(text)
76										if text.trim().is_empty() => {}
77									ast::Word::Variable(exp)
78										if exp.modifier.is_none() =>
79									{
80										included_vars
81											.push(exp.name.to_string());
82									}
83									_ => {
84										arch_groups.push((
85											var.name.to_string(),
86											KString::from_ref(var_name),
87											target.to_ascii_lowercase(),
88											false,
89										));
90										continue 'vars;
91									}
92								}
93							}
94						}
95						ast::VariableValue::Array(elements) => {
96							for element in elements {
97								match element {
98									ast::ArrayElement::ArrayInclusion(name) => {
99										included_vars.push(name.to_string());
100									}
101									_ => {
102										arch_groups.push((
103											var.name.to_string(),
104											KString::from_ref(var_name),
105											target.to_ascii_lowercase(),
106											true,
107										));
108										continue 'vars;
109									}
110								}
111							}
112						}
113					}
114					let mut included_groups = vec![];
115					for var in &included_vars {
116						let (include_name, group) =
117							if let Some(v) = var.split_once("__") {
118								v
119							} else {
120								continue 'vars;
121							};
122						if include_name != var_name {
123							continue 'vars;
124						}
125						included_groups.push(group.to_ascii_lowercase());
126					}
127					debug!(
128						"Variable override '{}' included groups: {:?}",
129						var.name, included_groups
130					);
131					arch_overrides.insert(
132						(
133							KString::from_ref(var_name),
134							target.to_ascii_lowercase(),
135						),
136						(var.name.to_string(), included_groups),
137					);
138				}
139
140				for ((base_name, target), (var_name, groups)) in &arch_overrides
141				{
142					for (archgroup, targets) in &ab4_data.arch_groups {
143						if targets.contains(target.as_str())
144							&& !groups.contains(&archgroup.to_string())
145						{
146							let group_var_name = format!(
147								"{}__{}",
148								base_name,
149								archgroup.to_ascii_uppercase()
150							);
151							if apml.ctx()?.contains_var(&group_var_name) {
152								LintMessage::new(MISSING_ARCHGROUP_LINT)
153									.note(format!(
154										"'{group_var_name}' is defined but not included in '{var_name}'",
155									))
156									.note(format!(
157										"'{target}' is in arch-group '{archgroup}'",
158									))
159									.snippet(Snippet::new_variable(
160										sess, apml, var_name,
161									))
162									.emit(sess);
163							}
164						}
165					}
166
167					if !ACBS_VARIABLES.contains(&base_name.as_str())
168						&& groups.iter().all(|group| {
169							ab4_data
170								.arch_groups
171								.get(group.as_str())
172								.is_some_and(|targets| {
173									targets.contains(target.as_str())
174								})
175						}) {
176						LintMessage::new(REDUNDANT_ARCH_OVERRIDES_LINT)
177							.snippet(Snippet::new_variable(
178								sess, apml, var_name,
179							))
180							.emit(sess);
181						if !sess.dry {
182							apml.with_editor(|editor| {
183								if let Some(index) =
184									editor.find_var_index(var_name)
185								{
186									editor.remove_var(index);
187								}
188							});
189						}
190					}
191				}
192
193				// arch-groups
194				for (var_name, base_name, group, is_array) in arch_groups {
195					if !ACBS_VARIABLES.contains(&base_name.as_str()) {
196						continue;
197					}
198
199					if let Some(targets) =
200						ab4_data.arch_groups.get(group.as_str())
201					{
202						for target in targets {
203							let (okay, fixable) =
204								if let Some((_, groups)) = arch_overrides.get(
205									&(base_name.clone(), target.to_string()),
206								) {
207									(groups.contains(&group), false)
208								} else {
209									(false, true)
210								};
211
212							if !okay {
213								LintMessage::new(ACBS_ARCH_GROUPS_LINT)
214									.message(format!(
215										"'{var_name}' is not included in target '{target}'",
216									))
217									.snippet(Snippet::new_variable(
218										sess, apml, &var_name,
219									))
220									.emit(sess);
221								if !sess.dry && fixable {
222									apml.with_editor(|editor| {
223										let name =format!(
224												"{}__{}",
225												base_name,
226												target.to_ascii_uppercase()
227											);
228										let value = if is_array {
229											ast::VariableValue::Array(vec![ast::ArrayElement::ArrayInclusion(var_name.to_string().into())])
230										} else {
231											ast::VariableValue::String(ast::Text(vec![ast::Word::Variable(ast::VariableExpansion{ name: var_name.to_string().into(), modifier: None })]))
232										};
233										editor.append_var_ast(
234											name,
235											&value,
236											Some(&var_name),
237										);
238									});
239								}
240							}
241						}
242					}
243				}
244
245				Ok::<_, anyhow::Error>(())
246			})?;
247		}
248		Ok(())
249	}
250}