libpfu_style/
archgroup.rs1use 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
47const 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 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}