Coverage for dak/cruft_report.py: 46%
421 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-04 16:18 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-04 16:18 +0000
1#! /usr/bin/env python3
3"""
4Check for obsolete binary packages
6@contact: Debian FTP Master <ftpmaster@debian.org>
7@copyright: 2000-2006 James Troup <james@nocrew.org>
8@copyright: 2009 Torsten Werner <twerner@debian.org>
9@license: GNU General Public License version 2 or later
10"""
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 2 of the License, or
15# (at your option) any later version.
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
22# You should have received a copy of the GNU General Public License
23# along with this program; if not, write to the Free Software
24# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26################################################################################
28# ``If you're claiming that's a "problem" that needs to be "fixed",
29# you might as well write some letters to God about how unfair entropy
30# is while you're at it.'' -- 20020802143104.GA5628@azure.humbug.org.au
32## TODO: fix NBS looping for version, implement Dubious NBS, fix up output of
33## duplicate source package stuff, improve experimental ?, add overrides,
34## avoid ANAIS for duplicated packages
36################################################################################
38import functools
39import os
40import re
41import sys
42from collections import defaultdict
43from collections.abc import Iterable
44from typing import TYPE_CHECKING, NoReturn, cast
46import apt_pkg
47from sqlalchemy import sql
48from sqlalchemy.engine import CursorResult
50from daklib import utils
51from daklib.config import Config
52from daklib.cruft import (
53 newer_version,
54 query_without_source,
55 queryNBS,
56 queryNBS_metadata,
57 report_multiple_source,
58)
59from daklib.dbconn import DBConn, Suite, get_suite, get_suite_architectures
60from daklib.regexes import re_extract_src_version
62if TYPE_CHECKING:
63 from sqlalchemy.engine import Result
64 from sqlalchemy.orm import Session
66################################################################################
68suite: Suite
69suite_id: int
71no_longer_in_suite: set[str] = set() # Really should be static to add_nbs, but I'm lazy
73source_binaries: dict[str, str] = {}
74source_versions: dict[str, str] = {}
76################################################################################
79def usage(exit_code=0) -> NoReturn:
80 print(
81 """Usage: dak cruft-report
82Check for obsolete or duplicated packages.
84 -h, --help show this help and exit.
85 -m, --mode=MODE chose the MODE to run in (full, daily, bdo).
86 -s, --suite=SUITE check suite SUITE.
87 -R, --rdep-check check reverse dependencies
88 -w, --wanna-build-dump where to find the copies of https://buildd.debian.org/stats/*.txt"""
89 )
90 sys.exit(exit_code)
93################################################################################
96def print_info(s="") -> None:
97 cnf = Config()
99 if cnf.subtree("Cruft-Report::Options")["Commands-Only"]: 99 ↛ 100line 99 didn't jump to line 100 because the condition on line 99 was never true
100 return
102 print(s)
105def print_cmd(s: str, indent=4) -> None:
106 cnf = Config()
108 # Indent if doing the human readable display
109 if not cnf.subtree("Cruft-Report::Options")["Commands-Only"]: 109 ↛ 113line 109 didn't jump to line 113 because the condition on line 109 was always true
110 ind = " " * indent
111 s = ind + s
113 print(s)
116################################################################################
119def add_nbs(
120 nbs_d: dict[str, dict[str, set[str]]],
121 source: str,
122 version: str,
123 package: str,
124 suite_id: int,
125 session: "Session",
126) -> None:
127 # Ensure the package is still in the suite (someone may have already removed it)
128 if package in no_longer_in_suite:
129 return
130 else:
131 q = session.execute(
132 sql.text(
133 """SELECT b.id FROM binaries b, bin_associations ba
134 WHERE ba.bin = b.id AND ba.suite = :suite_id
135 AND b.package = :package LIMIT 1"""
136 ),
137 {"suite_id": suite_id, "package": package},
138 )
139 if not q.fetchall():
140 no_longer_in_suite.add(package)
141 return
143 nbs_d[source][version].add(package)
146################################################################################
148# Check for packages built on architectures they shouldn't be.
151def do_anais(
152 architecture: str, binaries_list: Iterable[str], source: str, session: "Session"
153) -> str:
154 if architecture == "any" or architecture == "all":
155 return ""
157 version_sort_key = functools.cmp_to_key(apt_pkg.version_compare)
158 anais_output = ""
159 architectures = {a.strip() for a in architecture.split()}
160 for binary in binaries_list:
161 q = session.execute(
162 sql.text(
163 """SELECT a.arch_string, b.version
164 FROM binaries b, bin_associations ba, architecture a
165 WHERE ba.suite = :suiteid AND ba.bin = b.id
166 AND b.architecture = a.id AND b.package = :package"""
167 ),
168 {"suiteid": suite_id, "package": binary},
169 )
170 ql = q.fetchall()
171 versions = []
172 for arch, version in ql:
173 if arch in architectures:
174 versions.append(version)
175 versions.sort(key=version_sort_key)
176 if versions:
177 latest_version = versions.pop()
178 else:
179 latest_version = None
180 # Check for 'invalid' architectures
181 versions_d = defaultdict(list)
182 for arch, version in ql:
183 if arch not in architectures:
184 versions_d[version].append(arch)
186 if versions_d:
187 anais_output += "\n (*) %s_%s [%s]: %s\n" % (
188 binary,
189 latest_version,
190 source,
191 architecture,
192 )
193 for version in sorted(versions_d, key=version_sort_key):
194 arches = sorted(versions_d[version])
195 anais_output += " o %s: %s\n" % (version, ", ".join(arches))
196 return anais_output
199################################################################################
202# Check for out-of-date binaries on architectures that do not want to build that
203# package any more, and have them listed as Not-For-Us
204def do_nfu(nfu_packages: dict[str, list[tuple[str, str, str]]]) -> None:
205 output = ""
207 a2p: dict[str, list[str]] = {}
209 for architecture in nfu_packages: 209 ↛ 210line 209 didn't jump to line 210 because the loop on line 209 never started
210 a2p[architecture] = []
211 for package, bver, sver in nfu_packages[architecture]:
212 output += " * [%s] does not want %s (binary %s, source %s)\n" % (
213 architecture,
214 package,
215 bver,
216 sver,
217 )
218 a2p[architecture].append(package)
220 if output: 220 ↛ 221line 220 didn't jump to line 221 because the condition on line 220 was never true
221 print_info("Obsolete by Not-For-Us")
222 print_info("----------------------")
223 print_info()
224 print_info(output)
226 print_info("Suggested commands:")
227 for architecture in a2p:
228 if a2p[architecture]:
229 print_cmd(
230 (
231 'dak rm -o -m "[auto-cruft] NFU" -s %s -a %s -b %s'
232 % (suite.suite_name, architecture, " ".join(a2p[architecture]))
233 ),
234 indent=1,
235 )
236 print_info()
239def parse_nfu(architecture: str) -> set[str]:
240 cnf = Config()
241 # utils/hpodder_1.1.5.0: Not-For-Us [optional:out-of-date]
242 r = re.compile(r"^\w+/([^_]+)_.*: Not-For-Us")
244 ret = set()
246 filename = "%s/%s-all.txt" % (
247 cnf["Cruft-Report::Options::Wanna-Build-Dump"],
248 architecture,
249 )
251 # Not all architectures may have a wanna-build dump, so we want to ignore missin
252 # files
253 if os.path.exists(filename): 253 ↛ 254line 253 didn't jump to line 254 because the condition on line 253 was never true
254 with open(filename) as f:
255 for line in f:
256 if line[0] == " ":
257 continue
259 m = r.match(line)
260 if m:
261 ret.add(m.group(1))
262 else:
263 utils.warn("No wanna-build dump file for architecture %s" % architecture)
264 return ret
267################################################################################
270def do_newer_version(
271 lowersuite_name: str, highersuite_name: str, code: str, session: "Session"
272) -> None:
273 list = newer_version(lowersuite_name, highersuite_name, session)
274 if len(list) > 0: 274 ↛ 275line 274 didn't jump to line 275 because the condition on line 274 was never true
275 nv_to_remove = []
276 title = "Newer version in %s" % lowersuite_name
277 print_info(title)
278 print_info("-" * len(title))
279 print_info()
280 for i in list:
281 (source, higher_version, lower_version) = i
282 print_info(" o %s (%s, %s)" % (source, higher_version, lower_version))
283 nv_to_remove.append(source)
284 print_info()
285 print_info("Suggested command:")
286 print_cmd(
287 'dak rm -m "[auto-cruft] %s" -s %s %s'
288 % (code, highersuite_name, " ".join(nv_to_remove)),
289 indent=1,
290 )
291 print_info()
294################################################################################
297def reportWithoutSource(
298 suite_name: str, suite_id: int, session: "Session", rdeps=False
299) -> None:
300 rows = query_without_source(suite_id, session)
301 title = "packages without source in suite %s" % suite_name
302 if rows.rowcount > 0: 302 ↛ 303line 302 didn't jump to line 303 because the condition on line 302 was never true
303 print_info("%s\n%s\n" % (title, "-" * len(title)))
304 message = '"[auto-cruft] no longer built from source"'
305 for row in rows: 305 ↛ 306line 305 didn't jump to line 306 because the loop on line 305 never started
306 (package, version) = row
307 print_info(
308 "* package %s in version %s is no longer built from source"
309 % (package, version)
310 )
311 print_info(" - suggested command:")
312 print_cmd(
313 "dak rm -m %s -s %s -a all -p -R -b %s" % (message, suite_name, package)
314 )
315 if rdeps:
316 if utils.check_reverse_depends([package], suite_name, [], session, True):
317 print_info()
318 else:
319 print_info(" - No dependency problem found\n")
320 else:
321 print_info()
324def queryNewerAll(
325 suite_name: str, session: "Session"
326) -> CursorResult[tuple[str, str, str, str]]:
327 """searches for arch != all packages that have an arch == all
328 package with a higher version in the same suite"""
330 query = """
331select bab1.package, bab1.version as oldver,
332 array_to_string(array_agg(a.arch_string), ',') as oldarch,
333 bab2.version as newver
334 from bin_associations_binaries bab1
335 join bin_associations_binaries bab2
336 on bab1.package = bab2.package and bab1.version < bab2.version and
337 bab1.suite = bab2.suite and bab1.architecture > 2 and
338 bab2.architecture = 2
339 join architecture a on bab1.architecture = a.id
340 join suite s on bab1.suite = s.id
341 where s.suite_name = :suite_name
342 group by bab1.package, oldver, bab1.suite, newver"""
343 return cast(
344 CursorResult, session.execute(sql.text(query), {"suite_name": suite_name})
345 )
348def reportNewerAll(suite_name: str, session: "Session") -> None:
349 rows = queryNewerAll(suite_name, session)
350 title = "obsolete arch any packages in suite %s" % suite_name
351 if rows.rowcount > 0: 351 ↛ 352line 351 didn't jump to line 352 because the condition on line 351 was never true
352 print_info("%s\n%s\n" % (title, "-" * len(title)))
353 message = '"[auto-cruft] obsolete arch any package"'
354 for row in rows: 354 ↛ 355line 354 didn't jump to line 355 because the loop on line 354 never started
355 (package, oldver, oldarch, newver) = row
356 print_info(
357 "* package %s is arch any in version %s but arch all in version %s"
358 % (package, oldver, newver)
359 )
360 print_info(" - suggested command:")
361 print_cmd(
362 "dak rm -o -m %s -s %s -a %s -p -b %s\n"
363 % (message, suite_name, oldarch, package)
364 )
367def reportNBS(suite_name: str, suite_id: int, rdeps=False) -> None:
368 session = DBConn().session()
369 nbsRows = queryNBS(suite_id, session)
370 title = "NBS packages in suite %s" % suite_name
371 if nbsRows.rowcount > 0: 371 ↛ 372line 371 didn't jump to line 372 because the condition on line 371 was never true
372 print_info("%s\n%s\n" % (title, "-" * len(title)))
373 for row in nbsRows: 373 ↛ 374line 373 didn't jump to line 374 because the loop on line 373 never started
374 (pkg_list, arch_list, source, version) = row
375 pkg_string = " ".join(pkg_list)
376 arch_string = ",".join(arch_list)
377 print_info(
378 "* source package %s version %s no longer builds" % (source, version)
379 )
380 print_info(" binary package(s): %s" % pkg_string)
381 print_info(" on %s" % arch_string)
382 print_info(" - suggested command:")
383 message = '"[auto-cruft] NBS (no longer built by %s)"' % source
384 print_cmd(
385 "dak rm -o -m %s -s %s -a %s -p -R -b %s"
386 % (message, suite_name, arch_string, pkg_string)
387 )
388 if rdeps:
389 if utils.check_reverse_depends(
390 pkg_list, suite_name, arch_list, session, True
391 ):
392 print_info()
393 else:
394 print_info(" - No dependency problem found\n")
395 else:
396 print_info()
397 session.close()
400def reportNBSMetadata(
401 suite_name: str, suite_id: int, session: "Session", rdeps=False
402) -> None:
403 rows = queryNBS_metadata(suite_id, session)
404 title = "NBS packages (from metadata) in suite %s" % suite_name
405 if rows.rowcount > 0: 405 ↛ 407line 405 didn't jump to line 407 because the condition on line 405 was always true
406 print_info("%s\n%s\n" % (title, "-" * len(title)))
407 for row in rows:
408 (packages, architecture, source, version) = row
409 print_info(
410 "* source package %s version %s no longer builds" % (source, version)
411 )
412 print_info(" binary package(s): %s" % packages)
413 print_info(" on %s" % architecture)
414 print_info(" - suggested command:")
415 message = (
416 '"[auto-cruft] NBS (no longer built by %s - based on source metadata)"'
417 % source
418 )
419 print_cmd(
420 "dak rm -o -m %s -s %s -a %s -p -R -b %s"
421 % (message, suite_name, architecture, packages)
422 )
423 if rdeps: 423 ↛ 425line 423 didn't jump to line 425 because the condition on line 423 was never true
424 # when archs is None, rdeps are checked on all archs in the suite
425 archs = [architecture] if architecture != "all" else None
426 if utils.check_reverse_depends(
427 packages.split(), suite_name, archs, session, True
428 ):
429 print_info()
430 else:
431 print_info(" - No dependency problem found\n")
432 else:
433 print_info()
436def reportAllNBS(
437 suite_name: str, suite_id: int, session: "Session", rdeps=False
438) -> None:
439 reportWithoutSource(suite_name, suite_id, session, rdeps)
440 reportNewerAll(suite_name, session)
441 reportNBS(suite_name, suite_id, rdeps)
444################################################################################
447def do_dubious_nbs(dubious_nbs: dict[str, dict[str, set[str]]]) -> None:
448 print_info("Dubious NBS")
449 print_info("-----------")
450 print_info()
452 version_sort_key = functools.cmp_to_key(apt_pkg.version_compare)
453 for source in sorted(dubious_nbs):
454 print_info(
455 " * %s_%s builds: %s"
456 % (
457 source,
458 source_versions.get(source, "??"),
459 source_binaries.get(source, "(source does not exist)"),
460 )
461 )
462 print_info(" won't admit to building:")
463 versions = sorted(dubious_nbs[source], key=version_sort_key)
464 for version in versions:
465 packages = sorted(dubious_nbs[source][version])
466 print_info(" o %s: %s" % (version, ", ".join(packages)))
468 print_info()
471################################################################################
474def obsolete_source(
475 suite_name: str, session: "Session"
476) -> "CursorResult[tuple[int, str, str, str]]":
477 """returns obsolete source packages for suite_name without binaries
478 in the same suite sorted by install_date; install_date should help
479 detecting source only (or binary throw away) uploads; duplicates in
480 the suite are skipped
482 subquery 'source_suite_unique' returns source package names from
483 suite without duplicates; the rationale behind is that neither
484 cruft-report nor rm cannot handle duplicates (yet)"""
486 query = """
487WITH source_suite_unique AS
488 (SELECT source, suite
489 FROM source_suite GROUP BY source, suite HAVING count(*) = 1)
490SELECT ss.src, ss.source, ss.version,
491 to_char(ss.install_date, 'YYYY-MM-DD') AS install_date
492 FROM source_suite ss
493 JOIN source_suite_unique ssu
494 ON ss.source = ssu.source AND ss.suite = ssu.suite
495 JOIN suite s ON s.id = ss.suite
496 LEFT JOIN bin_associations_binaries bab
497 ON ss.src = bab.source AND ss.suite = bab.suite
498 WHERE s.suite_name = :suite_name AND bab.id IS NULL
499 AND now() - ss.install_date > '1 day'::interval
500 ORDER BY install_date"""
501 args = {"suite_name": suite_name}
502 return cast(CursorResult, session.execute(sql.text(query), args))
505def source_bin(source: str, session: "Session") -> "Result[tuple[str]]":
506 """returns binaries built by source for all or no suite grouped and
507 ordered by package name"""
509 query = """
510SELECT b.package
511 FROM binaries b
512 JOIN src_associations_src sas ON b.source = sas.src
513 WHERE sas.source = :source
514 GROUP BY b.package
515 ORDER BY b.package"""
516 args = {"source": source}
517 return session.execute(sql.text(query), args)
520def newest_source_bab(
521 suite_name: str, package: str, session: "Session"
522) -> "Result[tuple[str, str]]":
523 """returns newest source that builds binary package in suite grouped
524 and sorted by source and package name"""
526 query = """
527SELECT sas.source, MAX(sas.version) AS srcver
528 FROM src_associations_src sas
529 JOIN bin_associations_binaries bab ON sas.src = bab.source
530 JOIN suite s on s.id = bab.suite
531 WHERE s.suite_name = :suite_name AND bab.package = :package
532 GROUP BY sas.source, bab.package
533 ORDER BY sas.source, bab.package"""
534 args = {"suite_name": suite_name, "package": package}
535 return session.execute(sql.text(query), args)
538def report_obsolete_source(suite_name: str, session: "Session") -> None:
539 rows = obsolete_source(suite_name, session)
540 if rows.rowcount == 0: 540 ↛ 542line 540 didn't jump to line 542 because the condition on line 540 was always true
541 return
542 print_info(
543 """Obsolete source packages in suite %s
544----------------------------------%s\n"""
545 % (suite_name, "-" * len(suite_name))
546 )
547 for os_row in rows.fetchall():
548 (src, old_source, version, install_date) = os_row
549 print_info(
550 " * obsolete source %s version %s installed at %s"
551 % (old_source, version, install_date)
552 )
553 for sb_row in source_bin(old_source, session):
554 (package,) = sb_row
555 print_info(" - has built binary %s" % package)
556 for nsb_row in newest_source_bab(suite_name, package, session):
557 (new_source, srcver) = nsb_row
558 print_info(
559 " currently built by source %s version %s"
560 % (new_source, srcver)
561 )
562 print_info(" - suggested command:")
563 rm_opts = '-S -p -m "[auto-cruft] obsolete source package"'
564 print_cmd("dak rm -s %s %s %s\n" % (suite_name, rm_opts, old_source))
567def get_suite_binaries(suite: Suite, session: "Session") -> set[str]:
568 # Initalize a large hash table of all binary packages
569 print_info("Getting a list of binary packages in %s..." % suite.suite_name)
570 q = session.execute(
571 sql.text(
572 """SELECT distinct b.package
573 FROM binaries b, bin_associations ba
574 WHERE ba.suite = :suiteid AND ba.bin = b.id"""
575 ),
576 {"suiteid": suite.suite_id},
577 )
578 return {row[0] for row in q}
581################################################################################
584def report_outdated_nonfree(suite: str, session: "Session", rdeps=False) -> None:
586 packages: dict[str, dict[str, set[str]]] = {}
587 query = """WITH outdated_sources AS (
588 SELECT s.source, s.version, s.id
589 FROM source s
590 JOIN src_associations sa ON sa.source = s.id
591 WHERE sa.suite IN (
592 SELECT id
593 FROM suite
594 WHERE suite_name = :suite )
595 AND sa.created < (now() - interval :delay)
596 EXCEPT SELECT s.source, max(s.version) AS version, max(s.id)
597 FROM source s
598 JOIN src_associations sa ON sa.source = s.id
599 WHERE sa.suite IN (
600 SELECT id
601 FROM suite
602 WHERE suite_name = :suite )
603 AND sa.created < (now() - interval :delay)
604 GROUP BY s.source ),
605 binaries AS (
606 SELECT b.package, s.source, (
607 SELECT a.arch_string
608 FROM architecture a
609 WHERE a.id = b.architecture ) AS arch
610 FROM binaries b
611 JOIN outdated_sources s ON s.id = b.source
612 JOIN bin_associations ba ON ba.bin = b.id
613 JOIN override o ON o.package = b.package AND o.suite = ba.suite
614 WHERE ba.suite IN (
615 SELECT id
616 FROM suite
617 WHERE suite_name = :suite )
618 AND o.component IN (
619 SELECT id
620 FROM component
621 WHERE name = 'non-free' ) )
622 SELECT DISTINCT package, source, arch
623 FROM binaries
624 ORDER BY source, package, arch"""
626 res = session.execute(sql.text(query), {"suite": suite, "delay": "'15 days'"})
627 for package in res: 627 ↛ 628line 627 didn't jump to line 628 because the loop on line 627 never started
628 binary = package[0]
629 source = package[1]
630 arch = package[2]
631 if arch == "all":
632 continue
633 if source not in packages:
634 packages[source] = {}
635 if binary not in packages[source]:
636 packages[source][binary] = set()
637 packages[source][binary].add(arch)
638 if packages: 638 ↛ 639line 638 didn't jump to line 639 because the condition on line 638 was never true
639 title = "Outdated non-free binaries in suite %s" % suite
640 message = '"[auto-cruft] outdated non-free binaries"'
641 print_info("%s\n%s\n" % (title, "-" * len(title)))
642 for source in sorted(packages):
643 archs: set[str] = set()
644 binaries: set[str] = set()
645 print_info("* package %s has outdated non-free binaries" % source)
646 print_info(" - suggested command:")
647 for binary in sorted(packages[source]):
648 binaries.add(binary)
649 archs = archs.union(packages[source][binary])
650 print_cmd(
651 "dak rm -o -m %s -s %s -a %s -p -R -b %s"
652 % (message, suite, ",".join(archs), " ".join(binaries))
653 )
654 if rdeps:
655 if utils.check_reverse_depends(
656 list(binaries), suite, archs, session, True
657 ):
658 print_info()
659 else:
660 print_info(" - No dependency problem found\n")
661 else:
662 print_info()
665################################################################################
668def main() -> None:
669 global suite, suite_id, source_binaries, source_versions
671 cnf = Config()
673 Arguments = [
674 ("h", "help", "Cruft-Report::Options::Help"),
675 ("m", "mode", "Cruft-Report::Options::Mode", "HasArg"),
676 ("R", "rdep-check", "Cruft-Report::Options::Rdep-Check"),
677 ("s", "suite", "Cruft-Report::Options::Suite", "HasArg"),
678 ("w", "wanna-build-dump", "Cruft-Report::Options::Wanna-Build-Dump", "HasArg"),
679 ("c", "commands-only", "Cruft-Report::Options::Commands-Only"),
680 ]
681 for i in ["help", "Rdep-Check"]:
682 key = "Cruft-Report::Options::%s" % i
683 if key not in cnf: 683 ↛ 681line 683 didn't jump to line 681 because the condition on line 683 was always true
684 cnf[key] = ""
686 if "Cruft-Report::Options::Commands-Only" not in cnf: 686 ↛ 689line 686 didn't jump to line 689 because the condition on line 686 was always true
687 cnf["Cruft-Report::Options::Commands-Only"] = ""
689 cnf["Cruft-Report::Options::Suite"] = cnf.get("Dinstall::DefaultSuite", "unstable")
691 if "Cruft-Report::Options::Mode" not in cnf: 691 ↛ 694line 691 didn't jump to line 694 because the condition on line 691 was always true
692 cnf["Cruft-Report::Options::Mode"] = "daily"
694 if "Cruft-Report::Options::Wanna-Build-Dump" not in cnf: 694 ↛ 699line 694 didn't jump to line 699 because the condition on line 694 was always true
695 cnf["Cruft-Report::Options::Wanna-Build-Dump"] = (
696 "/srv/ftp-master.debian.org/scripts/nfu"
697 )
699 apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined]
701 Options = cnf.subtree("Cruft-Report::Options")
702 if Options["Help"]:
703 usage()
705 if Options["Rdep-Check"]: 705 ↛ 706line 705 didn't jump to line 706 because the condition on line 705 was never true
706 rdeps = True
707 else:
708 rdeps = False
710 # Set up checks based on mode
711 if Options["Mode"] == "daily": 711 ↛ 721line 711 didn't jump to line 721 because the condition on line 711 was always true
712 checks = [
713 "nbs",
714 "nviu",
715 "nvit",
716 "obsolete source",
717 "outdated non-free",
718 "nfu",
719 "nbs metadata",
720 ]
721 elif Options["Mode"] == "full":
722 checks = [
723 "nbs",
724 "nviu",
725 "nvit",
726 "obsolete source",
727 "outdated non-free",
728 "nfu",
729 "nbs metadata",
730 "dubious nbs",
731 "bnb",
732 "bms",
733 "anais",
734 ]
735 elif Options["Mode"] == "bdo":
736 checks = ["nbs", "obsolete source"]
737 else:
738 utils.warn(
739 "%s is not a recognised mode - only 'full', 'daily' or 'bdo' are understood."
740 % (Options["Mode"])
741 )
742 usage(1)
744 session = DBConn().session()
746 bin_pkgs = {}
747 src_pkgs = {}
748 bin2source: dict[str, dict[str, str]] = {}
749 bins_in_suite: set[str] = set()
750 nbs: dict[str, dict[str, set[str]]] = defaultdict(lambda: defaultdict(set)) 750 ↛ exitline 750 didn't run the lambda on line 750
751 source_versions = {}
753 anais_output = ""
755 nfu_packages: dict[str, list[tuple[str, str, str]]] = defaultdict(list)
757 suite_or_none = get_suite(Options["Suite"].lower(), session)
758 if not suite_or_none: 758 ↛ 759line 758 didn't jump to line 759 because the condition on line 758 was never true
759 utils.fubar("Cannot find suite %s" % Options["Suite"].lower())
760 suite = suite_or_none
762 suite_id = suite.suite_id
763 suite_name = suite.suite_name.lower()
765 if "obsolete source" in checks: 765 ↛ 768line 765 didn't jump to line 768 because the condition on line 765 was always true
766 report_obsolete_source(suite_name, session)
768 if "nbs" in checks: 768 ↛ 771line 768 didn't jump to line 771 because the condition on line 768 was always true
769 reportAllNBS(suite_name, suite_id, session, rdeps)
771 if "nbs metadata" in checks: 771 ↛ 774line 771 didn't jump to line 774 because the condition on line 771 was always true
772 reportNBSMetadata(suite_name, suite_id, session, rdeps)
774 if "outdated non-free" in checks: 774 ↛ 777line 774 didn't jump to line 777 because the condition on line 774 was always true
775 report_outdated_nonfree(suite_name, session, rdeps)
777 bin_not_built = defaultdict(set)
779 if "bnb" in checks: 779 ↛ 780line 779 didn't jump to line 780 because the condition on line 779 was never true
780 bins_in_suite = get_suite_binaries(suite, session)
782 section: apt_pkg.TagSection
784 # Checks based on the Sources files
785 components = [c.component_name for c in suite.components]
786 for component in [c.component_name for c in suite.components]:
787 filename = "%s/dists/%s/%s/source/Sources" % (
788 suite.archive.path,
789 suite_name,
790 component,
791 )
792 filename = utils.find_possibly_compressed_file(filename)
793 with apt_pkg.TagFile(filename) as Sources:
794 while Sources.step(): # type: ignore[attr-defined]
795 section = Sources.section # type: ignore[attr-defined]
796 source = section.find("Package")
797 source_version = section.find("Version")
798 architecture = section.find("Architecture")
799 binaries = section.find("Binary")
800 binaries_list = [i.strip() for i in binaries.split(",")]
802 if "bnb" in checks: 802 ↛ 804line 802 didn't jump to line 804 because the condition on line 802 was never true
803 # Check for binaries not built on any architecture.
804 for binary in binaries_list:
805 if binary not in bins_in_suite:
806 bin_not_built[source].add(binary)
808 if "anais" in checks: 808 ↛ 809line 808 didn't jump to line 809 because the condition on line 808 was never true
809 anais_output += do_anais(
810 architecture, binaries_list, source, session
811 )
813 # build indices for checking "no source" later
814 source_index = component + "/" + source
815 src_pkgs[source] = source_index
816 for binary in binaries_list:
817 bin_pkgs[binary] = source
818 source_binaries[source] = binaries
819 source_versions[source] = source_version
821 # Checks based on the Packages files
822 check_components = components[:]
823 if suite_name != "experimental": 823 ↛ 826line 823 didn't jump to line 826 because the condition on line 823 was always true
824 check_components.append("main/debian-installer")
826 for component in check_components:
827 architectures = [
828 a.arch_string
829 for a in get_suite_architectures(
830 suite_name, skipsrc=True, skipall=True, session=session
831 )
832 ]
833 for architecture in architectures:
834 if component == "main/debian-installer" and re.match( 834 ↛ 837line 834 didn't jump to line 837 because the condition on line 834 was never true
835 "kfreebsd", architecture
836 ):
837 continue
839 if "nfu" in checks: 839 ↛ 842line 839 didn't jump to line 842 because the condition on line 839 was always true
840 nfu_entries = parse_nfu(architecture)
842 filename = "%s/dists/%s/%s/binary-%s/Packages" % (
843 suite.archive.path,
844 suite_name,
845 component,
846 architecture,
847 )
848 filename = utils.find_possibly_compressed_file(filename)
849 with apt_pkg.TagFile(filename) as Packages:
850 while Packages.step(): # type: ignore[attr-defined]
851 section = Packages.section # type: ignore[attr-defined]
852 package = section.find("Package")
853 source = section.find("Source", "")
854 version = section.find("Version")
855 if source == "":
856 source = package
857 if ( 857 ↛ 864line 857 didn't jump to line 864
858 package in bin2source
859 and apt_pkg.version_compare(
860 version, bin2source[package]["version"]
861 )
862 > 0
863 ):
864 bin2source[package]["version"] = version
865 bin2source[package]["source"] = source
866 else:
867 bin2source[package] = {}
868 bin2source[package]["version"] = version
869 bin2source[package]["source"] = source
870 if source.find("(") != -1: 870 ↛ 871line 870 didn't jump to line 871 because the condition on line 870 was never true
871 m = re_extract_src_version.match(source)
872 assert m is not None
873 source = m.group(1)
874 version = m.group(2)
875 if package not in bin_pkgs: 875 ↛ 876line 875 didn't jump to line 876 because the condition on line 875 was never true
876 nbs[source][package].add(version)
877 else:
878 if "nfu" in checks: 878 ↛ 850line 878 didn't jump to line 850 because the condition on line 878 was always true
879 if ( 879 ↛ 883line 879 didn't jump to line 883
880 package in nfu_entries
881 and version != source_versions[source]
882 ): # only suggest to remove out-of-date packages
883 nfu_packages[architecture].append(
884 (package, version, source_versions[source])
885 )
887 # Distinguish dubious (version numbers match) and 'real' NBS (they don't)
888 dubious_nbs: dict[str, dict[str, set[str]]] = defaultdict(lambda: defaultdict(set)) 888 ↛ exitline 888 didn't run the lambda on line 888
889 version_sort_key = functools.cmp_to_key(apt_pkg.version_compare)
890 for source in nbs: 890 ↛ 891line 890 didn't jump to line 891 because the loop on line 890 never started
891 for package in nbs[source]:
892 latest_version = max(nbs[source][package], key=version_sort_key)
893 source_version = source_versions.get(source, "0")
894 if apt_pkg.version_compare(latest_version, source_version) == 0:
895 add_nbs(dubious_nbs, source, latest_version, package, suite_id, session)
897 if "nviu" in checks: 897 ↛ 900line 897 didn't jump to line 900 because the condition on line 897 was always true
898 do_newer_version("unstable", "experimental", "NVIU", session)
900 if "nvit" in checks: 900 ↛ 905line 900 didn't jump to line 905 because the condition on line 900 was always true
901 do_newer_version("testing", "testing-proposed-updates", "NVIT", session)
903 ###
905 if Options["Mode"] == "full": 905 ↛ 906line 905 didn't jump to line 906 because the condition on line 905 was never true
906 print_info("=" * 75)
907 print_info()
909 if "nfu" in checks: 909 ↛ 912line 909 didn't jump to line 912 because the condition on line 909 was always true
910 do_nfu(nfu_packages)
912 if "bnb" in checks: 912 ↛ 913line 912 didn't jump to line 913 because the condition on line 912 was never true
913 print_info("Unbuilt binary packages")
914 print_info("-----------------------")
915 print_info()
916 for source in sorted(bin_not_built):
917 binaries = sorted(bin_not_built[source])
918 print_info(" o %s: %s" % (source, ", ".join(binaries)))
919 print_info()
921 if "bms" in checks: 921 ↛ 922line 921 didn't jump to line 922 because the condition on line 921 was never true
922 report_multiple_source(suite)
924 if "anais" in checks: 924 ↛ 925line 924 didn't jump to line 925 because the condition on line 924 was never true
925 print_info("Architecture Not Allowed In Source")
926 print_info("----------------------------------")
927 print_info(anais_output)
928 print_info()
930 if "dubious nbs" in checks: 930 ↛ 931line 930 didn't jump to line 931 because the condition on line 930 was never true
931 do_dubious_nbs(dubious_nbs)
934################################################################################
936if __name__ == "__main__":
937 main()