lean_ctx_patterns/
make.rs1use std::collections::HashMap;
4
5macro_rules! static_regex {
6 ($pattern:expr_2021) => {{
7 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
8 RE.get_or_init(|| {
9 regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
10 })
11 }};
12}
13
14fn compiler_invocation_re() -> &'static regex::Regex {
15 static_regex!(
16 r"(?i)^(/[^\s]+/)?(gcc|g\+\+|c\+\+|cc|clang\+\+|clang|ld\.bfd|ld\.gold|ld|ar|rustc|javac|zig|nvcc|emcc|icc|icpc)\s"
17 )
18}
19
20fn is_compiler_echo_line(line: &str) -> bool {
21 compiler_invocation_re().is_match(line.trim_start())
22}
23
24fn is_warning_line(line: &str) -> bool {
25 let l = line.to_ascii_lowercase();
26 l.contains("warning:") || l.contains(" warning ") || l.contains(": warning ")
27}
28
29fn is_error_line(line: &str) -> bool {
30 let l = line.to_ascii_lowercase();
31 l.contains("error:")
32 || l.contains("error ")
33 || l.contains("*** ")
34 || l.contains("fatal error")
35 || l.contains("undefined reference")
36 || l.contains("undefined symbol")
37 || l.contains("make: ***")
38 || l.contains("gmake: ***")
39 || l.contains("ninja: error")
40}
41
42fn is_make_meta_line(line: &str) -> bool {
43 let t = line.trim_start();
44 t.starts_with("make[") || t.starts_with("gmake[") || t.starts_with("make:")
45}
46
47pub fn compress(command: &str, output: &str) -> Option<String> {
48 let cl = command.trim();
49 let cl = cl.to_ascii_lowercase();
50 if cl != "make" && !cl.starts_with("make ") {
51 return None;
52 }
53 Some(compress_make_output(output))
54}
55
56fn compress_make_output(output: &str) -> String {
57 let mut kept_non_warning = Vec::new();
58 let mut warning_counts: HashMap<String, u32> = HashMap::new();
59 let mut last_significant: Option<String> = None;
60
61 for line in output.lines() {
62 let trimmed = line.trim_end();
63 if trimmed.is_empty() {
64 continue;
65 }
66 if is_compiler_echo_line(trimmed) {
67 continue;
68 }
69
70 last_significant = Some(trimmed.to_string());
71
72 if is_warning_line(trimmed) {
73 let key = trimmed.trim().to_string();
74 *warning_counts.entry(key).or_insert(0) += 1;
75 continue;
76 }
77
78 let tl = trimmed.to_ascii_lowercase();
79 if tl.contains("nothing to be done")
80 || tl.contains("is up to date.")
81 || is_error_line(trimmed)
82 || is_make_meta_line(trimmed)
83 {
84 kept_non_warning.push(trimmed.to_string());
85 continue;
86 }
87
88 if trimmed.starts_with('@') {
89 continue;
90 }
91
92 if trimmed.contains("Entering directory") || trimmed.contains("Leaving directory") {
93 kept_non_warning.push(trimmed.to_string());
94 }
95 }
96
97 if let Some(ref last) = last_significant
98 && !kept_non_warning.iter().any(|k| k == last)
99 {
100 kept_non_warning.push(format!("result: {last}"));
101 }
102
103 let mut sections: Vec<String> = Vec::new();
104
105 if !warning_counts.is_empty() {
106 let mut wlines: Vec<String> = warning_counts
107 .into_iter()
108 .map(|(text, n)| {
109 if n > 1 {
110 format!("{text} (x{n})")
111 } else {
112 text
113 }
114 })
115 .collect();
116 wlines.sort();
117 sections.push(format!("warnings:\n{}", wlines.join("\n")));
118 }
119
120 if !kept_non_warning.is_empty() {
121 sections.push(kept_non_warning.join("\n"));
122 }
123
124 if sections.is_empty() {
125 "make (no warnings/errors/meta)".to_string()
126 } else {
127 sections.join("\n\n")
128 }
129}