Coverage for src/debputy/build_support/clean_logic.py: 11%
127 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
1import os.path
2from typing import (
3 Set,
4 cast,
5 List,
6)
8from debputy.build_support.build_context import BuildContext
9from debputy.build_support.build_logic import (
10 in_build_env,
11 assign_stems,
12)
13from debputy.build_support.buildsystem_detection import auto_detect_buildsystem
14from debputy.commands.debputy_cmd.context import CommandContext
15from debputy.highlevel_manifest import HighLevelManifest
16from debputy.plugin.debputy.to_be_api_types import BuildSystemRule, CleanHelper
17from debputy.util import _info, print_command, _error, _debug_log, _warn
18from debputy.util import (
19 run_build_system_command,
20)
22_REMOVE_DIRS = frozenset(
23 [
24 "__pycache__",
25 "autom4te.cache",
26 ]
27)
28_IGNORE_DIRS = frozenset(
29 [
30 ".git",
31 ".svn",
32 ".bzr",
33 ".hg",
34 "CVS",
35 ".pc",
36 "_darcs",
37 ]
38)
39DELETE_FILE_EXT = (
40 "~",
41 ".orig",
42 ".rej",
43 ".bak",
44)
45DELETE_FILE_BASENAMES = {
46 "DEADJOE",
47 ".SUMS",
48 "TAGS",
49}
52def _debhelper_left_overs() -> bool:
53 if os.path.lexists("debian/.debhelper") or os.path.lexists(
54 "debian/debhelper-build-stamp"
55 ):
56 return True
57 with os.scandir(".") as root_dir:
58 for child in root_dir:
59 if child.is_file(follow_symlinks=False) and (
60 child.name.endswith(".debhelper.log")
61 or child.name.endswith(".debhelper")
62 ):
63 return True
64 return False
67class CleanHelperImpl(CleanHelper):
69 def __init__(self) -> None:
70 self.files_to_remove: Set[str] = set()
71 self.dirs_to_remove: Set[str] = set()
73 def schedule_removal_of_files(self, *args: str) -> None:
74 self.files_to_remove.update(args)
76 def schedule_removal_of_directories(self, *args: str) -> None:
77 if any(p == "/" for p in args):
78 raise ValueError("Refusing to delete '/'")
79 self.dirs_to_remove.update(args)
82def _scan_for_standard_removals(clean_helper: CleanHelperImpl) -> None:
83 remove_files = clean_helper.files_to_remove
84 remove_dirs = clean_helper.dirs_to_remove
85 with os.scandir(".") as root_dir:
86 for child in root_dir:
87 if child.is_file(follow_symlinks=False) and child.name.endswith("-stamp"):
88 remove_files.add(child.path)
89 for current_dir, subdirs, files in os.walk("."):
90 for remove_dir in [d for d in subdirs if d in _REMOVE_DIRS]:
91 path = os.path.join(current_dir, remove_dir)
92 remove_dirs.add(path)
93 subdirs.remove(remove_dir)
94 for skip_dir in [d for d in subdirs if d in _IGNORE_DIRS]:
95 subdirs.remove(skip_dir)
97 for basename in files:
98 if (
99 basename.endswith(DELETE_FILE_EXT)
100 or basename in DELETE_FILE_BASENAMES
101 or (basename.startswith("#") and basename.endswith("#"))
102 ):
103 path = os.path.join(current_dir, basename)
104 remove_files.add(path)
107def perform_clean(
108 context: CommandContext,
109 manifest: HighLevelManifest,
110) -> None:
111 clean_helper = CleanHelperImpl()
113 build_rules = manifest.build_rules
114 if build_rules is not None:
115 if not build_rules:
116 # Defined but empty disables the auto-detected build system
117 return
118 active_packages = frozenset(manifest.active_packages)
119 condition_context = manifest.source_condition_context
120 build_context = BuildContext.from_command_context(context)
121 assign_stems(build_rules, manifest)
122 for step_no, build_rule in enumerate(build_rules):
123 step_ref = (
124 f"step {step_no} [{build_rule.auto_generated_stem}]"
125 if build_rule.name is None
126 else f"step {step_no} [{build_rule.name}]"
127 )
128 if not build_rule.is_buildsystem:
129 _debug_log(f"Skipping clean for {step_ref}: Not a build system")
130 continue
131 build_system_rule: BuildSystemRule = cast("BuildSystemRule", build_rule)
132 if build_system_rule.for_packages.isdisjoint(active_packages):
133 _info(
134 f"Skipping build for {step_ref}: None of the relevant packages are being built"
135 )
136 continue
137 manifest_condition = build_system_rule.manifest_condition
138 if manifest_condition is not None and not manifest_condition.evaluate(
139 condition_context
140 ):
141 _info(
142 f"Skipping clean for {step_ref}: The condition clause evaluated to false"
143 )
144 continue
145 _info(f"Starting clean for {step_ref}.")
146 with in_build_env(build_rule.environment):
147 try:
148 build_system_rule.run_clean(
149 build_context,
150 manifest,
151 clean_helper,
152 )
153 except (RuntimeError, AttributeError) as e:
154 if context.parsed_args.debug_mode:
155 raise e
156 _error(
157 f"An error occurred during clean at {step_ref} (defined at {build_rule.attribute_path.path}): {str(e)}"
158 )
159 _info(f"Completed clean for {step_ref}.")
160 else:
161 build_system = auto_detect_buildsystem(manifest)
162 if build_system:
163 _info(f"Auto-detected build system: {build_system.__class__.__name__}")
164 build_context = BuildContext.from_command_context(context)
165 with in_build_env(build_system.environment):
166 build_system.run_clean(
167 build_context,
168 manifest,
169 clean_helper,
170 )
171 else:
172 _info("No build system was detected from the current plugin set.")
174 dh_autoreconf_used = os.path.lexists("debian/autoreconf.before")
175 debhelper_used = False
177 if dh_autoreconf_used or _debhelper_left_overs():
178 debhelper_used = True
180 _scan_for_standard_removals(clean_helper)
182 for package in manifest.all_packages:
183 package_staging_dir = os.path.join("debian", package.name)
184 if os.path.lexists(package_staging_dir):
185 clean_helper.schedule_removal_of_directories(package_staging_dir)
187 remove_files = clean_helper.files_to_remove
188 remove_dirs = clean_helper.dirs_to_remove
189 if remove_files:
190 print_command("rm", "-f", *remove_files)
191 _remove_files_if_exists(*remove_files)
192 if remove_dirs:
193 run_build_system_command("rm", "-fr", *remove_dirs)
195 if debhelper_used:
196 _info(
197 "Noted traces of debhelper commands being used; invoking dh_clean to clean up after them"
198 )
199 if dh_autoreconf_used:
200 run_build_system_command("dh_autoreconf_clean")
201 run_build_system_command("dh_clean")
203 try:
204 run_build_system_command(
205 "dpkg-buildtree",
206 "clean",
207 raise_file_not_found_on_missing_command=True,
208 )
209 except FileNotFoundError:
210 _warn("The dpkg-buildtree command is not present. Emulating it")
211 # This is from the manpage of dpkg-buildtree for 1.22.11.
212 _remove_files_if_exists(
213 "debian/files",
214 "debian/files.new",
215 "debian/substvars",
216 "debian/substvars.new",
217 )
218 run_build_system_command("rm", "-fr", "debian/tmp")
219 # Remove debian/.debputy as a separate step. While `rm -fr` should process things in order,
220 # it will continue on error, which could cause our manifests of things to delete to be deleted
221 # while leaving things half-removed unless we do this extra step.
222 run_build_system_command("rm", "-fr", "debian/.debputy")
225def _remove_files_if_exists(*args: str) -> None:
226 for path in args:
227 try:
228 os.unlink(path)
229 except FileNotFoundError:
230 continue
231 except OSError as e:
232 if os.path.isdir(path):
233 _error(
234 f"Failed to remove {path}: It is a directory, but it should have been a non-directory."
235 " Please verify everything is as expected and, if it is, remove it manually."
236 )
237 _error(f"Failed to remove {path}: {str(e)}")