Skip to content

Commit 7ac8983

Browse files
Handle known ARM64 libc6 diversion warnings
Recognize complete, exact libc6 ARM64 usr-merge diagnostic blocks before warning context is discarded. Preserve unknown warnings, raw logs, lintian handling, RPM behavior, and existing ignore policies. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 52d4e780-cd8c-405c-8944-5e3167ae4e59
1 parent 3d6a8b1 commit 7ac8983

3 files changed

Lines changed: 284 additions & 3 deletions

File tree

‎packaging_automation/packaging_warning_handler.py‎

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import re
33
import sys
44
from enum import Enum
5-
from typing import List, Tuple
5+
from itertools import groupby
6+
from typing import List, Set, Tuple
67

78
import yaml
89

@@ -62,6 +63,43 @@ def validate_output(output: str, ignore_file_path: str, package_type: PackageTyp
6263
print("Build output check completed succesfully. No warnings")
6364

6465

66+
def _known_arm64_diversion_warnings(output_lines: List[str]) -> Set[int]:
67+
warning = "dpkg-shlibdeps: warning: diversions involved - output may be incorrect"
68+
source = " diversion by libc6 from: /lib/ld-linux-aarch64.so.1"
69+
target = " diversion by libc6 to: /lib/ld-linux-aarch64.so.1.usr-is-merged"
70+
known_warnings = set()
71+
72+
# Keep unknown/malformed diversion records in the block so they veto it.
73+
for is_diagnostic, block in groupby(
74+
enumerate(output_lines),
75+
lambda item: item[1]
76+
.lstrip()
77+
.startswith(("dpkg-shlibdeps:", "diversion", "local diversion")),
78+
):
79+
if not is_diagnostic:
80+
continue
81+
records = list(block)
82+
lines = [line for _, line in records]
83+
if (
84+
set(lines) != {warning, source, target}
85+
or lines.count(source) != lines.count(target)
86+
or lines.count(warning) != 2 * lines.count(source)
87+
):
88+
continue
89+
90+
# Parallel dh_shlibdeps may interleave records, but every detail must
91+
# follow a warning. Only complete libc6 ARM64 usr-merge blocks qualify.
92+
pending_warnings = 0
93+
for line in lines:
94+
pending_warnings += 1 if line == warning else -1
95+
if pending_warnings < 0:
96+
break
97+
else:
98+
known_warnings.update(index for index, line in records if line == warning)
99+
100+
return known_warnings
101+
102+
65103
def filter_warning_lines(
66104
output_lines: List[str], package_type: PackageType
67105
) -> Tuple[List[str], List[str]]:
@@ -76,7 +114,10 @@ def filter_warning_lines(
76114
package_specific_warning_lines = []
77115
is_deb_warning_line = False
78116
is_rpm_warning_line = False
79-
for output_line in output_lines:
117+
known_diversion_warnings = set()
118+
if package_type == PackageType.deb:
119+
known_diversion_warnings = _known_arm64_diversion_warnings(output_lines)
120+
for line_index, output_line in enumerate(output_lines):
80121
if package_type == PackageType.deb:
81122
if debian_lintian_starter in output_line:
82123
is_deb_warning_line = True
@@ -87,7 +128,7 @@ def filter_warning_lines(
87128
package_specific_warning_lines.append(output_line)
88129
else:
89130
is_deb_warning_line = False
90-
else:
131+
elif line_index not in known_diversion_warnings:
91132
base_warning_lines.append(output_line)
92133
else:
93134
if rpm_lintian_starter in output_line:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
dh_shlibdeps -a
2+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
3+
diversion by libc6 from: /lib/ld-linux-aarch64.so.1
4+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
5+
diversion by libc6 to: /lib/ld-linux-aarch64.so.1.usr-is-merged
6+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
7+
diversion by libc6 from: /lib/ld-linux-aarch64.so.1
8+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
9+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
10+
diversion by libc6 to: /lib/ld-linux-aarch64.so.1.usr-is-merged
11+
diversion by libc6 from: /lib/ld-linux-aarch64.so.1
12+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
13+
diversion by libc6 to: /lib/ld-linux-aarch64.so.1.usr-is-merged
14+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
15+
diversion by libc6 from: /lib/ld-linux-aarch64.so.1
16+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
17+
diversion by libc6 to: /lib/ld-linux-aarch64.so.1.usr-is-merged
18+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
19+
diversion by libc6 from: /lib/ld-linux-aarch64.so.1
20+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
21+
diversion by libc6 to: /lib/ld-linux-aarch64.so.1.usr-is-merged
22+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
23+
diversion by libc6 from: /lib/ld-linux-aarch64.so.1
24+
dpkg-shlibdeps: warning: diversions involved - output may be incorrect
25+
diversion by libc6 to: /lib/ld-linux-aarch64.so.1.usr-is-merged
26+
dh_installdeb -a

‎packaging_automation/tests/test_packaging_warning_handler.py‎

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
)
1616

1717
TEST_BASE_PATH = pathlib2.Path(__file__).parent
18+
IGNORE_FILE = f"{TEST_BASE_PATH}/files/packaging_warning/packaging_ignore.yml"
19+
DIVERSION_WARNING = (
20+
"dpkg-shlibdeps: warning: diversions involved - output may be incorrect"
21+
)
22+
DIVERSION_FROM = " diversion by libc6 from: /lib/ld-linux-aarch64.so.1"
23+
DIVERSION_TO = " diversion by libc6 to: /lib/ld-linux-aarch64.so.1.usr-is-merged"
24+
KNOWN_DIVERSION = [DIVERSION_WARNING, DIVERSION_FROM, DIVERSION_WARNING, DIVERSION_TO]
1825

1926

2027
def test_parse_ignore_lists():
@@ -205,3 +212,210 @@ def test_validate_output_rpm_success():
205212
f"{TEST_BASE_PATH}/files/packaging_warning/packaging_ignore.yml",
206213
PackageType.rpm,
207214
)
215+
216+
217+
@pytest.mark.parametrize(
218+
"lines",
219+
[
220+
pytest.param(KNOWN_DIVERSION, id="serial"),
221+
pytest.param(KNOWN_DIVERSION * 3, id="repeated"),
222+
pytest.param(
223+
[DIVERSION_WARNING, DIVERSION_WARNING, DIVERSION_TO, DIVERSION_FROM],
224+
id="interleaved",
225+
),
226+
pytest.param(
227+
KNOWN_DIVERSION + [" dh_gencontrol -a"] + KNOWN_DIVERSION,
228+
id="separate-complete-blocks",
229+
),
230+
],
231+
)
232+
def test_known_arm64_diversion(lines):
233+
original_lines = lines.copy()
234+
assert filter_warning_lines(lines, PackageType.deb) == ([], [])
235+
validate_output("\n".join(lines), IGNORE_FILE, PackageType.deb)
236+
assert lines == original_lines
237+
238+
239+
def test_known_arm64_diversion_topn_noble_output():
240+
# Original topn/Noble output, including warning/warning/to/from interleaving.
241+
output = (
242+
TEST_BASE_PATH
243+
/ "files/packaging_warning/sample_warning_build_output_deb_arm64_diversions.txt"
244+
).read_text(
245+
encoding=DEFAULT_ENCODING_FOR_FILE_HANDLING,
246+
errors=DEFAULT_UNICODE_ERROR_HANDLER,
247+
)
248+
assert filter_warning_lines(output.splitlines(), PackageType.deb) == ([], [])
249+
validate_output(output, IGNORE_FILE, PackageType.deb)
250+
251+
252+
@pytest.mark.parametrize(
253+
"source,target",
254+
[
255+
pytest.param(
256+
DIVERSION_FROM.replace("libc6", "other"),
257+
DIVERSION_TO.replace("libc6", "other"),
258+
id="unknown-owner",
259+
),
260+
pytest.param(
261+
DIVERSION_FROM.replace("libc6", "libc6:arm64"),
262+
DIVERSION_TO,
263+
id="different-owner-spelling",
264+
),
265+
pytest.param(
266+
DIVERSION_FROM.replace("diversion by libc6", "local diversion"),
267+
DIVERSION_TO.replace("diversion by libc6", "local diversion"),
268+
id="local-diversion",
269+
),
270+
pytest.param(
271+
DIVERSION_FROM.replace("aarch64", "x86-64"),
272+
DIVERSION_TO.replace("aarch64", "x86-64"),
273+
id="other-architecture",
274+
),
275+
pytest.param(
276+
DIVERSION_FROM.replace("/lib/", "/usr/lib/"),
277+
DIVERSION_TO,
278+
id="other-source-path",
279+
),
280+
pytest.param(
281+
DIVERSION_FROM,
282+
DIVERSION_TO.replace(".usr-is-merged", ".distrib"),
283+
id="other-target-path",
284+
),
285+
pytest.param("", DIVERSION_TO, id="missing-source"),
286+
pytest.param(DIVERSION_FROM, "", id="missing-target"),
287+
pytest.param(DIVERSION_FROM, DIVERSION_TO + ".extra", id="target-suffix"),
288+
pytest.param(DIVERSION_FROM + " extra", DIVERSION_TO, id="extra-text"),
289+
pytest.param(DIVERSION_FROM.lstrip(), DIVERSION_TO, id="missing-indent"),
290+
pytest.param("\t" + DIVERSION_FROM, DIVERSION_TO, id="extra-indent"),
291+
pytest.param(DIVERSION_FROM, DIVERSION_TO + " ", id="trailing-space"),
292+
pytest.param(
293+
DIVERSION_FROM.replace("from:", "from"),
294+
DIVERSION_TO,
295+
id="malformed-record",
296+
),
297+
pytest.param(
298+
DIVERSION_FROM,
299+
DIVERSION_TO.replace("to: /lib/ld-linux-aarch64.so.1.usr-is-merged", "to:"),
300+
id="incomplete-record",
301+
),
302+
],
303+
)
304+
@pytest.mark.parametrize("interleaved", [False, True])
305+
def test_unknown_diversion_context(source, target, interleaved):
306+
if interleaved:
307+
lines = [DIVERSION_WARNING, DIVERSION_WARNING, target, source]
308+
else:
309+
lines = [DIVERSION_WARNING, source, DIVERSION_WARNING, target]
310+
assert filter_warning_lines(lines, PackageType.deb) == (
311+
[DIVERSION_WARNING, DIVERSION_WARNING],
312+
[],
313+
)
314+
# Valid records elsewhere must not rescue missing or unknown context.
315+
output = "\n".join(KNOWN_DIVERSION + lines + KNOWN_DIVERSION)
316+
with pytest.raises(SystemExit) as error:
317+
validate_output(output, IGNORE_FILE, PackageType.deb)
318+
assert error.value.code == 1
319+
320+
321+
@pytest.mark.parametrize(
322+
"lines",
323+
[
324+
pytest.param([DIVERSION_WARNING], id="no-context"),
325+
pytest.param([DIVERSION_WARNING, DIVERSION_FROM], id="source-only"),
326+
pytest.param([DIVERSION_WARNING, DIVERSION_TO], id="target-only"),
327+
pytest.param(
328+
[DIVERSION_WARNING, DIVERSION_WARNING, DIVERSION_FROM],
329+
id="truncated-interleaving",
330+
),
331+
pytest.param([DIVERSION_WARNING, DIVERSION_FROM] * 2, id="unbalanced-source"),
332+
pytest.param([DIVERSION_WARNING, DIVERSION_TO] * 2, id="unbalanced-target"),
333+
pytest.param(KNOWN_DIVERSION + [DIVERSION_WARNING], id="extra-warning"),
334+
pytest.param(
335+
KNOWN_DIVERSION + [DIVERSION_FROM, DIVERSION_TO],
336+
id="extra-details",
337+
),
338+
pytest.param(
339+
[DIVERSION_FROM, DIVERSION_WARNING, DIVERSION_WARNING, DIVERSION_TO],
340+
id="detail-before-warning",
341+
),
342+
pytest.param(
343+
[DIVERSION_WARNING, DIVERSION_FROM, DIVERSION_TO, DIVERSION_WARNING],
344+
id="detail-overtakes-warning",
345+
),
346+
pytest.param(
347+
KNOWN_DIVERSION
348+
+ ["dpkg-shlibdeps: warning: cannot find library libunknown.so"],
349+
id="other-shlibdeps-warning",
350+
),
351+
pytest.param(
352+
KNOWN_DIVERSION + ["dpkg-shlibdeps: error: unknown diagnostic"],
353+
id="extra-diagnostic-type",
354+
),
355+
pytest.param(
356+
KNOWN_DIVERSION + [" local diversion from: /unknown"],
357+
id="extra-local-diversion",
358+
),
359+
pytest.param(
360+
KNOWN_DIVERSION + [DIVERSION_WARNING + " extra"],
361+
id="nonliteral-warning",
362+
),
363+
],
364+
)
365+
def test_incomplete_or_ambiguous_diversion_block(lines):
366+
assert filter_warning_lines(lines, PackageType.deb) == (
367+
[line for line in lines if "warning" in line.lower()],
368+
[],
369+
)
370+
with pytest.raises(SystemExit) as error:
371+
validate_output("\n".join(lines), IGNORE_FILE, PackageType.deb)
372+
assert error.value.code == 1
373+
374+
375+
@pytest.mark.parametrize(
376+
"boundary",
377+
["", "unrelated output", " dh_gencontrol -a", "compiler: warning: unrelated"],
378+
)
379+
def test_diversion_context_does_not_cross_boundaries(boundary):
380+
lines = [
381+
DIVERSION_WARNING,
382+
DIVERSION_FROM,
383+
boundary,
384+
DIVERSION_WARNING,
385+
DIVERSION_TO,
386+
]
387+
base_warnings, _ = filter_warning_lines(lines, PackageType.deb)
388+
assert base_warnings.count(DIVERSION_WARNING) == 2
389+
with pytest.raises(SystemExit):
390+
validate_output("\n".join(lines), IGNORE_FILE, PackageType.deb)
391+
392+
393+
@pytest.mark.parametrize("before", [False, True])
394+
def test_unrelated_diversion_warning_remains_fatal(before):
395+
missing_context = [DIVERSION_WARNING]
396+
blocks = (
397+
[missing_context, KNOWN_DIVERSION]
398+
if before
399+
else [KNOWN_DIVERSION, missing_context]
400+
)
401+
lines = blocks[0] + [" dh_gencontrol -a"] + blocks[1]
402+
assert filter_warning_lines(lines, PackageType.deb) == ([DIVERSION_WARNING], [])
403+
with pytest.raises(SystemExit):
404+
validate_output("\n".join(lines), IGNORE_FILE, PackageType.deb)
405+
406+
407+
def test_known_diversion_does_not_ignore_lintian():
408+
lintian_lines = ["package: W: unknown-warning", "package: E: unknown-error"]
409+
lines = KNOWN_DIVERSION + ["Now running lintian"] + lintian_lines
410+
assert filter_warning_lines(lines, PackageType.deb) == ([], lintian_lines)
411+
with pytest.raises(SystemExit):
412+
validate_output("\n".join(lines), IGNORE_FILE, PackageType.deb)
413+
414+
415+
def test_known_diversion_not_ignored_for_rpm():
416+
assert filter_warning_lines(KNOWN_DIVERSION, PackageType.rpm) == (
417+
[DIVERSION_WARNING, DIVERSION_WARNING],
418+
[],
419+
)
420+
with pytest.raises(SystemExit):
421+
validate_output("\n".join(KNOWN_DIVERSION), IGNORE_FILE, PackageType.rpm)

0 commit comments

Comments
 (0)