Coverage for src/debputy/build_support/build_logic.py: 10%
140 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-09-07 09:27 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-09-07 09:27 +0000
1import collections
2import contextlib
3import os
4import tempfile
5from typing import (
6 Iterator,
7 Mapping,
8 List,
9 Dict,
10 Optional,
11 TYPE_CHECKING,
12 Tuple,
13 FrozenSet,
14)
16from debputy.build_support.build_context import BuildContext
17from debputy.build_support.buildsystem_detection import (
18 auto_detect_buildsystem,
19)
20from debputy.commands.debputy_cmd.context import CommandContext
21from debputy.manifest_parser.base_types import BuildEnvironmentDefinition
22from debputy.packages import BinaryPackage
23from debputy.plugins.debputy.to_be_api_types import BuildRule
24from debputy.util import (
25 _error,
26 _info,
27 _non_verbose_info,
28 generated_content_dir,
29)
31if TYPE_CHECKING:
32 from debputy.highlevel_manifest import HighLevelManifest
35@contextlib.contextmanager
36def in_build_env(
37 build_env: BuildEnvironmentDefinition,
38 *,
39 env_is_for_clean: bool = False,
40) -> Iterator[None]:
41 # Should possibly be per build
42 with _setup_build_env(build_env, env_is_for_clean=env_is_for_clean):
43 yield
46def _set_stem_if_absent(stems: List[Optional[str]], idx: int, stem: str) -> None:
47 if stems[idx] is None:
48 stems[idx] = stem
51def assign_stems(
52 build_rules: List[BuildRule],
53 manifest: "HighLevelManifest",
54) -> None:
55 if not build_rules:
56 return
57 if len(build_rules) == 1:
58 build_rules[0].auto_generated_stem = ""
59 return
61 debs = {p.name for p in manifest.all_packages if p.package_type == "deb"}
62 udebs = {p.name for p in manifest.all_packages if p.package_type == "udeb"}
63 deb_only_builds: List[int] = []
64 udeb_only_builds: List[int] = []
65 by_name_only_builds: Dict[str, List[int]] = collections.defaultdict(list)
66 stems = [rule.name for rule in build_rules]
67 reserved_stems = set(n for n in stems if n is not None)
69 for idx, rule in enumerate(build_rules):
70 stem = stems[idx]
71 if stem is not None:
72 continue
73 pkg_names = {p.name for p in rule.for_packages}
74 if pkg_names == debs:
75 deb_only_builds.append(idx)
76 elif pkg_names == udebs:
77 udeb_only_builds.append(idx)
79 if len(pkg_names) == 1:
80 pkg_name = next(iter(pkg_names))
81 by_name_only_builds[pkg_name].append(idx)
83 if "deb" not in reserved_stems and len(deb_only_builds) == 1:
84 _set_stem_if_absent(stems, deb_only_builds[0], "deb")
86 if "udeb" not in reserved_stems and len(udeb_only_builds) == 1:
87 _set_stem_if_absent(stems, udeb_only_builds[0], "udeb")
89 for pkg, idxs in by_name_only_builds.items():
90 if len(idxs) != 1 or pkg in reserved_stems:
91 continue
92 _set_stem_if_absent(stems, idxs[0], pkg)
94 for idx, rule in enumerate(build_rules):
95 stem = stems[idx]
96 if stem is None:
97 stem = f"bno_{idx}"
98 rule.auto_generated_stem = stem
99 _info(f"Assigned {rule.auto_generated_stem} [{stem}] to step {idx}")
102def perform_builds(
103 context: CommandContext,
104 manifest: "HighLevelManifest",
105 build_system_install_dirs: List[Tuple[str, FrozenSet[BinaryPackage]]],
106) -> None:
107 prune_unnecessary_env()
108 build_rules = manifest.build_rules
109 if build_rules is not None:
110 if not build_rules:
111 # Defined but empty disables the auto-detected build system
112 return
113 active_packages = frozenset(manifest.active_packages)
114 condition_context = manifest.source_condition_context
115 build_context = BuildContext.from_command_context(context)
116 assign_stems(build_rules, manifest)
117 for step_no, build_rule in enumerate(build_rules):
118 step_ref = (
119 f"step {step_no} [{build_rule.auto_generated_stem}]"
120 if build_rule.name is None
121 else f"step {step_no} [{build_rule.name}]"
122 )
123 if build_rule.for_packages.isdisjoint(active_packages):
124 _info(
125 f"Skipping build for {step_ref}: None of the relevant packages are being built"
126 )
127 continue
128 manifest_condition = build_rule.manifest_condition
129 if manifest_condition is not None and not manifest_condition.evaluate(
130 condition_context
131 ):
132 _info(
133 f"Skipping build for {step_ref}: The condition clause evaluated to false"
134 )
135 continue
136 _info(f"Starting build for {step_ref}.")
137 with in_build_env(build_rule.environment):
138 try:
139 build_rule.run_build(build_context, manifest)
140 except (RuntimeError, AttributeError) as e:
141 if context.parsed_args.debug_mode:
142 raise e
143 _error(
144 f"An error occurred during build/install at {step_ref} (defined at {build_rule.attribute_path.path}): {str(e)}"
145 )
146 dest_dir = build_rule.install_dest_dir()
147 if dest_dir is not None:
148 build_system_install_dirs.append((dest_dir, build_rule.for_packages))
150 _info(f"Completed build for {step_ref}.")
152 else:
153 build_system = auto_detect_buildsystem(manifest)
154 if build_system:
155 _info(f"Auto-detected build system: {build_system.__class__.__name__}")
156 build_context = BuildContext.from_command_context(context)
157 with in_build_env(build_system.environment):
158 build_system.run_build(
159 build_context,
160 manifest,
161 )
162 dest_dir = build_system.install_dest_dir()
163 if dest_dir is not None:
164 build_system_install_dirs.append(
165 (dest_dir, build_system.for_packages)
166 )
168 _non_verbose_info("Upstream builds completed successfully")
169 else:
170 _info("No build system was detected from the current plugin set.")
173def prune_unnecessary_env() -> None:
174 vs = [
175 "XDG_CACHE_HOME",
176 "XDG_CONFIG_DIRS",
177 "XDG_CONFIG_HOME",
178 "XDG_DATA_HOME",
179 "XDG_DATA_DIRS",
180 "XDG_RUNTIME_DIR",
181 ]
182 for v in vs:
183 if v in os.environ:
184 del os.environ[v]
185 os.environ["HOME"] = "/non-existent"
188@contextlib.contextmanager
189def _setup_build_env(
190 build_env: BuildEnvironmentDefinition,
191 *,
192 env_is_for_clean: bool = False,
193) -> Iterator[None]:
194 env_backup = dict(os.environ)
195 env = dict(env_backup)
196 had_delta = False
197 build_env.update_env(env)
198 if env != env_backup:
199 _set_env(env)
200 had_delta = True
201 _info("Updated environment to match build")
202 if env_is_for_clean:
203 yield
204 else:
205 orig_home = env_backup["HOME"]
206 env["HOME"] = generated_content_dir(subdir_key="dpty_buildtime_home")
207 with tempfile.TemporaryDirectory() as xdg_runtime_dir:
208 env["XDG_RUNTIME_DIR"] = xdg_runtime_dir
209 yield
210 try:
211 del env["XDG_RUNTIME_DIR"]
212 except KeyError:
213 pass
214 env["HOME"] = orig_home
215 if had_delta or env != env_backup:
216 _set_env(env_backup)
219def _set_env(desired_env: Mapping[str, str]) -> None:
220 os_env = os.environ
221 for key in os_env.keys() | desired_env.keys():
222 desired_value = desired_env.get(key)
223 if desired_value is None:
224 try:
225 del os_env[key]
226 except KeyError:
227 pass
228 else:
229 os_env[key] = desired_value