1use crate::cond::{self, Releases};
50use crate::diff;
51use crate::norm;
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum Kind {
56 Same,
59 Conditional,
61 PerRelease,
64}
65
66#[derive(Debug, Clone)]
68pub struct Merged {
69 pub text: String,
71 pub kind: Kind,
73 pub guarded: bool,
75 pub branches: usize,
77 pub defined_the_macro: bool,
79 pub problems: Vec<String>,
81}
82
83pub const GUARD: &str = "#ifndef __GLIBC_MINOR__\n# error \"this is rucc's merged glibc header \
91 tree, in which the compiler defines __GLIBC_MINOR__ from the target; see \
92 spec/cross-compile/08-sysroots.md section 8.3\"\n#endif\n";
93
94pub fn one(releases: &Releases, path: &str, texts: &[Option<&str>]) -> Result<Merged, String> {
101 assert_eq!(texts.len(), releases.count(), "one text per release, present or not");
102 for (n, text) in texts.iter().enumerate() {
103 if text.is_some_and(cond::carries_mark) {
104 return Err(format!(
105 "{path}: the {} copy contains {}, so it has been through a merge already and \
106 merging it again would read its conditionals as ours",
107 releases.spelled(n),
108 cond::MARK
109 ));
110 }
111 }
112
113 let mut problems = Vec::new();
114 let patched: Vec<Option<(String, bool)>> = texts.iter().map(|t| t.map(patch)).collect();
115 let want: Vec<Option<&str>> =
116 patched.iter().map(|p| p.as_ref().map(|(text, _)| text.as_str())).collect();
117 let present: Vec<bool> = want.iter().map(Option::is_some).collect();
118 let have: Vec<usize> = (0..releases.count()).filter(|&n| present[n]).collect();
119 let (Some(&first), Some(&newest)) = (have.first(), have.last()) else {
120 return Err(format!("{path}: no release has it"));
121 };
122 let cut: Vec<Option<norm::Pieces>> = want.iter().map(|t| t.map(norm::pieces)).collect();
123 let pieces = |which: usize| cut[which].as_ref().expect("a release that has the file");
124
125 let mut spine: Vec<String> = pieces(first).keys().iter().map(|&k| k.to_owned()).collect();
128 let mut at: Vec<Vec<usize>> = vec![(0..spine.len()).collect()];
129 for &r in &have[1..] {
130 let theirs = pieces(r).keys();
131 let mine: Vec<&str> = spine.iter().map(String::as_str).collect();
132 let pairs = diff::aligned(&mine, &theirs);
133 let kept: Vec<String> = pairs.iter().map(|&(x, _)| spine[x].clone()).collect();
134 for row in &mut at {
135 *row = pairs.iter().map(|&(x, _)| row[x]).collect();
136 }
137 at.push(pairs.iter().map(|&(_, y)| y).collect());
138 spine = kept;
139 }
140
141 let slots = 2 * spine.len() + 1;
144 let by_slot: Vec<Vec<String>> = have
145 .iter()
146 .enumerate()
147 .map(|(j, &r)| {
148 let items = &pieces(r).items;
149 (0..slots)
150 .map(|slot| {
151 if slot % 2 == 1 {
152 return items[at[j][slot / 2]].text.clone();
153 }
154 let gap = slot / 2;
155 let from = if gap == 0 { 0 } else { at[j][gap - 1] + 1 };
156 let to = if gap == spine.len() { items.len() } else { at[j][gap] };
157 let mut text: String =
158 items[from..to].iter().map(|i| i.text.as_str()).collect();
159 if gap == spine.len() {
160 text.push_str(&pieces(r).tail);
162 }
163 text
164 })
165 .collect()
166 })
167 .collect();
168 let region = |lo: usize, hi: usize| grouped(&have, |j, _| by_slot[j][lo..=hi].concat());
169 let content: Vec<bool> =
173 (0..slots).map(|slot| by_slot.iter().any(|row| !row[slot].is_empty())).collect();
174 let whole_file =
175 |lo: usize, hi: usize| !content[..lo].contains(&true) && !content[hi + 1..].contains(&true);
176
177 let mut regions: Vec<(usize, usize)> = Vec::new();
184 let mut slot = 0;
185 while slot < slots {
186 let (mut lo, mut hi) = (slot, slot);
187 loop {
188 let groups = region(lo, hi);
189 let reach =
190 groups.iter().fold(Reach::default(), |all, (_, _, text)| all.with(&needs(text)));
191 if groups.len() == 1 || !reach.out_of_it() {
192 break;
193 }
194 let below = reach.below && hi + 1 < slots;
197 let above = reach.above && lo > 0;
198 if below {
199 hi += 1;
200 } else if above {
201 lo = regions.pop().expect("a region above to take back").0;
202 } else if hi + 1 < slots {
203 hi += 1;
204 } else if lo > 0 {
205 lo = regions.pop().expect("a region above to take back").0;
206 } else {
207 break;
210 }
211 }
212 regions.push((lo, hi));
213 slot = hi + 1;
214 }
215
216 let mut body = String::new();
217 let mut branches = 0;
218 let mut kind = Kind::Same;
219 for &(lo, hi) in ®ions {
220 let groups = region(lo, hi);
221 if let [(_, _, only)] = &groups[..] {
222 body.push_str(only);
223 continue;
224 }
225 let said: Vec<&(String, Vec<usize>, String)> =
228 groups.iter().filter(|(_, _, text)| !text.is_empty()).collect();
229 branches += 1;
230 kind = if whole_file(lo, hi) { Kind::PerRelease } else { Kind::Conditional };
231 for (n, (_, members, text)) in said.iter().enumerate() {
232 line_end(&mut body);
233 body.push_str(&cond::directive(
234 if n == 0 { "if" } else { "elif" },
235 Some(&condition(releases, members)),
236 ));
237 body.push_str(text);
238 if !stands_alone(text) {
239 problems.push(format!(
240 "{path}: the copies for {} do not have balanced conditionals, so no branch \
241 around them is right",
242 spelled(releases, members)
243 ));
244 }
245 }
246 line_end(&mut body);
247 body.push_str(&cond::directive("endif", None));
248 }
249
250 let guarded = have.len() != releases.count();
253 let text = if have.len() == releases.count() {
254 body
255 } else {
256 let mut text = cond::directive("if", Some(&condition(releases, &have)));
257 text.push_str(&body);
258 line_end(&mut text);
259 text.push_str(&cond::directive("else", None));
260 text.push_str(&format!(
261 "#error \"rucc: {path} is not a header of this glibc release; it is in {}\"\n",
262 spelled(releases, &have)
263 ));
264 text.push_str(&cond::directive("endif", None));
265 text
266 };
267
268 for (n, each) in want.iter().enumerate() {
270 let Some(each) = each else { continue };
271 match cond::evaluate(&text, releases.minors()[n]) {
272 Ok(got) if norm::code(&got) == norm::code(each) => {}
273 Ok(got) => problems.push(format!(
274 "{path}: what this writes does not give the {} copy back, {}",
275 releases.spelled(n),
276 first_difference(&norm::code(&got), &norm::code(each))
277 )),
278 Err(why) => problems.push(format!(
279 "{path}: reading back what this writes for {} failed: {why}",
280 releases.spelled(n)
281 )),
282 }
283 }
284 if kind == Kind::Same && !guarded {
285 let same = want[newest].unwrap_or_default();
287 if text.trim_end_matches('\n') != same.trim_end_matches('\n') {
288 problems.push(format!(
289 "{path}: no conditional was needed and the text still is not the {} copy",
290 releases.spelled(newest)
291 ));
292 }
293 }
294
295 Ok(Merged {
296 text,
297 kind,
298 guarded,
299 branches,
300 defined_the_macro: patched.iter().flatten().any(|(_, did)| *did),
301 problems,
302 })
303}
304
305fn grouped(
312 have: &[usize],
313 mut text: impl FnMut(usize, usize) -> String,
314) -> Vec<(String, Vec<usize>, String)> {
315 let mut groups: Vec<(String, Vec<usize>, String)> = Vec::new();
316 for (j, &r) in have.iter().enumerate() {
317 let text = text(j, r);
318 let code = norm::code(&text);
319 match groups.iter_mut().find(|group| group.0 == code) {
320 Some(group) => {
321 group.1.push(r);
322 group.2 = text;
323 }
324 None => groups.push((code, vec![r], text)),
325 }
326 }
327 groups
328}
329
330fn condition(releases: &Releases, members: &[usize]) -> String {
332 let mut flags = vec![false; releases.count()];
333 for &m in members {
334 flags[m] = true;
335 }
336 releases.condition(&flags).unwrap_or_else(|| "1".to_owned())
339}
340
341fn spelled(releases: &Releases, members: &[usize]) -> String {
343 members.iter().map(|&m| releases.spelled(m)).collect::<Vec<_>>().join(" ")
344}
345
346fn stands_alone(text: &str) -> bool {
352 !needs(text).out_of_it()
353}
354
355#[derive(Debug, Default, Clone, Copy)]
357struct Reach {
358 above: bool,
360 below: bool,
362}
363
364impl Reach {
365 fn with(self, other: &Reach) -> Self {
367 Self { above: self.above || other.above, below: self.below || other.below }
368 }
369
370 fn out_of_it(self) -> bool {
372 self.above || self.below
373 }
374}
375
376fn needs(text: &str) -> Reach {
383 let mut depth = 0i32;
384 let mut reach = Reach::default();
385 for line in norm::code(text).lines() {
386 let Some(rest) = line.trim_start().strip_prefix('#') else { continue };
387 let rest = rest.trim_start();
388 if rest.starts_with("if") {
389 depth += 1;
390 } else if rest.starts_with("endif") {
391 depth -= 1;
392 if depth < 0 {
393 reach.above = true;
394 depth = 0;
395 }
396 } else if (rest.starts_with("else") || rest.starts_with("elif")) && depth == 0 {
397 reach.above = true;
398 }
399 }
400 if depth > 0 {
401 reach.below = true;
402 }
403 reach
404}
405
406fn patch(text: &str) -> (String, bool) {
408 let cut = norm::pieces(text);
409 if !cut.items.iter().any(|item| defines_the_macro(&item.key)) {
410 return (text.to_owned(), false);
411 }
412 let mut out = String::with_capacity(text.len());
413 for item in &cut.items {
414 if defines_the_macro(&item.key) {
415 out.push_str(&item.text[..item.code_at]);
418 out.push_str(GUARD);
419 } else {
420 out.push_str(&item.text);
421 }
422 }
423 out.push_str(&cut.tail);
424 (out, true)
425}
426
427fn defines_the_macro(key: &str) -> bool {
429 let Some(rest) = key.strip_prefix('#') else { return false };
430 let Some(rest) = rest.trim_start().strip_prefix("define") else { return false };
431 let Some(rest) = rest.trim_start().strip_prefix(cond::MACRO) else { return false };
432 let value = rest.trim();
433 !value.is_empty() && value.bytes().all(|b| b.is_ascii_digit())
434}
435
436fn line_end(out: &mut String) {
438 if !out.is_empty() && !out.ends_with('\n') {
439 out.push('\n');
440 }
441}
442
443fn first_difference(got: &str, want: &str) -> String {
445 for (n, (left, right)) in got.lines().zip(want.lines()).enumerate() {
446 if left != right {
447 return format!("at line {} of the code: {} against {}", n + 1, cut(left), cut(right));
448 }
449 }
450 format!("{} lines of code against {}", got.lines().count(), want.lines().count())
451}
452
453fn cut(line: &str) -> String {
455 let line = line.trim();
456 if line.chars().count() <= 60 {
457 return format!("`{line}`");
458 }
459 format!("`{}...`", line.chars().take(57).collect::<String>())
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 fn releases() -> Releases {
467 Releases::new(vec![28, 31, 34]).expect("ascending")
468 }
469
470 fn merged(texts: &[Option<&str>]) -> Merged {
472 let all = releases();
473 let out = one(&all, "sys/thing.h", texts).expect("a tree nobody merged before");
474 assert_eq!(out.problems, Vec::<String>::new());
475 for (n, want) in texts.iter().enumerate() {
476 if let Some(want) = want {
477 let got = cond::evaluate(&out.text, all.minors()[n]).expect("our own conditionals");
478 assert_eq!(norm::code(&got), norm::code(want), "the 2.{} copy", all.minors()[n]);
479 }
480 }
481 out
482 }
483
484 #[test]
485 fn three_copies_of_one_file_are_that_file() {
486 let text = "#ifndef _THING_H\n#define _THING_H 1\nint f (void);\n#endif\n";
487 let out = merged(&[Some(text), Some(text), Some(text)]);
488 assert_eq!(out.kind, Kind::Same);
489 assert_eq!(out.text, text);
490 assert_eq!(out.branches, 0);
491 assert!(!out.guarded);
492 }
493
494 #[test]
495 fn a_year_in_a_comment_is_not_worth_a_conditional() {
496 let old = "/* Copyright (C) 2018 FSF. */\nint f (void);\n";
497 let new = "/* Copyright (C) 2024 FSF. */\nint f (void);\n";
498 let out = merged(&[Some(old), Some(old), Some(new)]);
499 assert_eq!(out.kind, Kind::Same);
500 assert_eq!(out.text, new);
502 }
503
504 #[test]
505 fn a_declaration_added_in_the_newest_release_is_behind_a_conditional() {
506 let old = "int f (void);\n";
507 let new = "int f (void);\nint g (void);\n";
508 let out = merged(&[Some(old), Some(old), Some(new)]);
509 assert_eq!(out.kind, Kind::Conditional);
510 assert_eq!(out.branches, 1);
511 assert_eq!(
513 out.text,
514 "int f (void);\n#if __GLIBC_MINOR__ >= 34 /* rucc */\nint g (void);\n#endif /* rucc */\n"
515 );
516 }
517
518 #[test]
519 fn a_declaration_removed_in_the_newest_release_is_behind_one_too() {
520 let old = "int f (void);\nint gone (void);\n";
521 let new = "int f (void);\n";
522 let out = merged(&[Some(old), Some(old), Some(new)]);
523 assert_eq!(out.kind, Kind::Conditional);
524 assert!(out.text.contains("#if __GLIBC_MINOR__ < 34 /* rucc */"), "{}", out.text);
525 }
526
527 #[test]
528 fn a_constant_that_changed_value_is_one_conditional_with_two_branches() {
529 let out = merged(&[
530 Some("#define _STAT_VER 1\n"),
531 Some("#define _STAT_VER 1\n"),
532 Some("#define _STAT_VER 3\n"),
533 ]);
534 assert_eq!(out.branches, 1);
535 assert_eq!(out.text.matches("#elif").count(), 1);
536 }
537
538 #[test]
539 fn a_header_that_arrives_later_says_so_for_the_releases_without_it() {
540 let out = merged(&[None, None, Some("int f (void);\n")]);
541 assert!(out.guarded);
542 assert!(out.text.starts_with("#if __GLIBC_MINOR__ >= 34 /* rucc */"), "{}", out.text);
543 assert!(
544 out.text.contains(
545 "#error \"rucc: sys/thing.h is not a header of this glibc \
546 release; it is in 2.34\""
547 ),
548 "{}",
549 out.text
550 );
551 let gone = cond::evaluate(&out.text, 28).expect("ours");
553 assert!(gone.contains("#error"), "{gone}");
554 assert!(!gone.contains("int f (void);"), "{gone}");
555 }
556
557 #[test]
558 fn a_header_that_went_away_is_the_same_the_other_way_round() {
559 let out = merged(&[Some("int f (void);\n"), Some("int f (void);\n"), None]);
560 assert!(out.guarded);
561 assert!(out.text.starts_with("#if __GLIBC_MINOR__ < 34 /* rucc */"), "{}", out.text);
562 }
563
564 #[test]
567 fn a_region_inside_the_files_own_conditional_is_left_where_it_is() {
568 let old = "#ifdef __USE_GNU\nint f (void);\n#endif\n";
569 let new = "#ifdef __USE_GNU\nint f (void);\nint g (void);\n#endif\nint h (void);\n";
570 let out = merged(&[Some(old), Some(old), Some(new)]);
571 assert_eq!(out.kind, Kind::Conditional);
572 assert_eq!(out.branches, 2);
575 assert!(out.text.contains("#ifdef __USE_GNU\n"), "{}", out.text);
576 for release in [28, 34] {
578 let got = cond::evaluate(&out.text, release).expect("ours");
579 assert_eq!(got.matches("#ifdef __USE_GNU").count(), 1, "{got}");
580 assert_eq!(got.matches("#endif").count(), 1, "{got}");
581 }
582 }
583
584 #[test]
588 fn a_changed_condition_takes_its_block_with_it_and_not_the_file() {
589 let old = "int before (void);\n#ifdef A\nint f (void);\n#endif\nint after (void);\n";
590 let new = "int before (void);\n#if defined A || defined B\nint f (void);\n#endif\n\
591 int after (void);\n";
592 let out = merged(&[Some(old), Some(old), Some(new)]);
593 assert_eq!(out.kind, Kind::Conditional);
594 assert_eq!(out.branches, 1);
595 assert_eq!(out.text.matches("int before (void);").count(), 1, "{}", out.text);
597 assert_eq!(out.text.matches("int after (void);").count(), 1, "{}", out.text);
598 assert_eq!(out.text.matches("int f (void);").count(), 2, "{}", out.text);
599 }
600
601 #[test]
605 fn a_file_whose_conditionals_nest_differently_is_one_copy_per_release() {
606 let old = "#if A\nint f (void);\n#endif\n#if B\nint g (void);\n#endif\n";
607 let new = "#if A\nint f (void);\n#if B\nint g (void);\n#endif\n#endif\n";
608 let out = merged(&[Some(old), Some(old), Some(new)]);
609 assert_eq!(out.kind, Kind::PerRelease, "{}", out.text);
610 assert_eq!(out.branches, 1);
611 assert!(out.text.starts_with("#if __GLIBC_MINOR__ < 34 /* rucc */"), "{}", out.text);
612 assert_eq!(out.text.matches("int f (void);").count(), 2, "{}", out.text);
613 assert!(out.problems.is_empty(), "{:?}", out.problems);
614 }
615
616 #[test]
617 fn the_definition_of_the_version_macro_is_replaced_by_the_check_for_one() {
618 let all = releases();
619 let texts: Vec<String> = all
620 .minors()
621 .iter()
622 .map(|m| format!("#define __GLIBC__ 2\n#define\t__GLIBC_MINOR__\t{m}\nint f (void);\n"))
623 .collect();
624 let given: Vec<Option<&str>> = texts.iter().map(|t| Some(t.as_str())).collect();
625 let out = one(&all, "features.h", &given).expect("not merged before");
626 assert_eq!(out.problems, Vec::<String>::new());
627 assert!(out.defined_the_macro);
628 assert_eq!(out.kind, Kind::Same, "{}", out.text);
629 assert!(!out.text.contains("#define\t__GLIBC_MINOR__"), "{}", out.text);
630 assert!(out.text.contains("#define __GLIBC__ 2"), "{}", out.text);
631 assert!(out.text.contains("#ifndef __GLIBC_MINOR__"), "{}", out.text);
632 assert!(out.text.contains("# error"), "{}", out.text);
633 }
634
635 #[test]
637 fn a_comment_about_the_version_macro_is_left_alone() {
638 let text = "/* #define __GLIBC_MINOR__ 44 is what glibc does. */\nint f (void);\n";
639 let out = merged(&[Some(text), Some(text), Some(text)]);
640 assert!(!out.defined_the_macro);
641 assert_eq!(out.text, text);
642 }
643
644 #[test]
645 fn a_tree_that_has_been_merged_once_is_refused() {
646 let all = releases();
647 let text = format!("int f (void);\n{}", cond::directive("endif", None));
648 let why = one(&all, "sys/thing.h", &[Some(&text), Some(&text), Some(&text)])
649 .expect_err("it carries the marker");
650 assert!(why.contains("through a merge already"), "{why}");
651 }
652
653 #[test]
654 fn a_file_no_release_has_is_an_error_rather_than_an_empty_file() {
655 assert!(one(&releases(), "sys/thing.h", &[None, None, None]).is_err());
656 }
657
658 #[test]
661 fn a_continued_macro_that_changed_is_replaced_whole() {
662 let old = "#define F(a) \\\n ((a) + 1)\nint f (void);\n";
663 let new = "#define F(a) \\\n ((a) + 2)\nint f (void);\n";
664 let out = merged(&[Some(old), Some(old), Some(new)]);
665 assert_eq!(out.kind, Kind::Conditional);
666 for release in [28, 34] {
668 let got = cond::evaluate(&out.text, release).expect("ours");
669 assert_eq!(got.matches("#define F(a)").count(), 1, "{got}");
670 }
671 }
672
673 #[test]
674 fn a_file_with_no_trailing_newline_still_gets_whole_directives() {
675 let out = merged(&[Some("int f (void);"), Some("int f (void);"), Some("int g (void);")]);
676 for line in out.text.lines() {
677 assert!(!line.contains("#endif") || line.trim_start().starts_with('#'), "{line}");
678 }
679 }
680
681 #[test]
682 fn which_way_a_branch_reaches_out_of_itself() {
683 assert!(needs("#endif\n").above);
684 assert!(needs("#else\nint f (void);\n").above);
685 assert!(needs("#ifdef A\nint f (void);\n").below);
686 assert!(!needs("#ifdef A\nint f (void);\n#endif\n").out_of_it());
687 let both = needs("#endif\n#ifdef A\nint f (void);\n");
689 assert!(both.above && both.below);
690 }
691
692 #[test]
693 fn what_stands_alone_and_what_does_not() {
694 assert!(stands_alone("int f (void);\n"));
695 assert!(stands_alone("#ifdef A\nint f (void);\n#endif\n"));
696 assert!(!stands_alone("#endif\n"));
697 assert!(!stands_alone("#else\nint f (void);\n"));
698 assert!(!stands_alone("#ifdef A\nint f (void);\n"));
699 assert!(stands_alone("/* #endif */\nint f (void);\n"));
701 }
702}