Coverage for src/debputy/build_support/build_logic.py: 12%
120 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 collections
2import contextlib
3import os
4from typing import (
5 Iterator,
6 Mapping,
7 List,
8 Dict,
9 Optional,
10 TYPE_CHECKING,
11)
13from debputy.build_support.build_context import BuildContext
14from debputy.build_support.buildsystem_detection import (
15 auto_detect_buildsystem,
16)
17from debputy.commands.debputy_cmd.context import CommandContext
18from debputy.manifest_parser.base_types import BuildEnvironmentDefinition
19from debputy.plugin.debputy.to_be_api_types import BuildRule
20from debputy.util import (
21 _error,
22 _info,
23 _non_verbose_info,
24)
26if TYPE_CHECKING:
27 from debputy.highlevel_manifest import HighLevelManifest
30@contextlib.contextmanager
31def in_build_env(build_env: BuildEnvironmentDefinition):
32 remove_unnecessary_env()
33 # Should possibly be per build
34 with _setup_build_env(build_env):
35 yield
38def _set_stem_if_absent(stems: List[Optional[str]], idx: int, stem: str) -> None:
39 if stems[idx] is None:
40 stems[idx] = stem
43def assign_stems(
44 build_rules: List[BuildRule],
45 manifest: "HighLevelManifest",
46) -> None:
47 if not build_rules:
48 return
49 if len(build_rules) == 1:
50 build_rules[0].auto_generated_stem = ""
51 return
53 debs = {p.name for p in manifest.all_packages if p.package_type == "deb"}
54 udebs = {p.name for p in manifest.all_packages if p.package_type == "udeb"}
55 deb_only_builds: List[int] = []
56 udeb_only_builds: List[int] = []
57 by_name_only_builds: Dict[str, List[int]] = collections.defaultdict(list)
58 stems = [rule.name for rule in build_rules]
59 reserved_stems = set(n for n in stems if n is not None)
61 for idx, rule in enumerate(build_rules):
62 stem = stems[idx]
63 if stem is not None:
64 continue
65 pkg_names = {p.name for p in rule.for_packages}
66 if pkg_names == debs:
67 deb_only_builds.append(idx)
68 elif pkg_names == udebs:
69 udeb_only_builds.append(idx)
71 if len(pkg_names) == 1:
72 pkg_name = next(iter(pkg_names))
73 by_name_only_builds[pkg_name].append(idx)
75 if "deb" not in reserved_stems and len(deb_only_builds) == 1:
76 _set_stem_if_absent(stems, deb_only_builds[0], "deb")
78 if "udeb" not in reserved_stems and len(udeb_only_builds) == 1:
79 _set_stem_if_absent(stems, udeb_only_builds[0], "udeb")
81 for pkg, idxs in by_name_only_builds.items():
82 if len(idxs) != 1 or pkg in reserved_stems:
83 continue
84 _set_stem_if_absent(stems, idxs[0], pkg)
86 for idx, rule in enumerate(build_rules):
87 stem = stems[idx]
88 if stem is None:
89 stem = f"bno_{idx}"
90 rule.auto_generated_stem = stem
91 _info(f"Assigned {rule.auto_generated_stem} [{stem}] to step {idx}")
94def perform_builds(
95 context: CommandContext,
96 manifest: "HighLevelManifest",
97) -> None:
98 build_rules = manifest.build_rules
99 if build_rules is not None:
100 if not build_rules:
101 # Defined but empty disables the auto-detected build system
102 return
103 active_packages = frozenset(manifest.active_packages)
104 condition_context = manifest.source_condition_context
105 build_context = BuildContext.from_command_context(context)
106 assign_stems(build_rules, manifest)
107 for step_no, build_rule in enumerate(build_rules):
108 step_ref = (
109 f"step {step_no} [{build_rule.auto_generated_stem}]"
110 if build_rule.name is None
111 else f"step {step_no} [{build_rule.name}]"
112 )
113 if build_rule.for_packages.isdisjoint(active_packages):
114 _info(
115 f"Skipping build for {step_ref}: None of the relevant packages are being built"
116 )
117 continue
118 manifest_condition = build_rule.manifest_condition
119 if manifest_condition is not None and not manifest_condition.evaluate(
120 condition_context
121 ):
122 _info(
123 f"Skipping build for {step_ref}: The condition clause evaluated to false"
124 )
125 continue
126 _info(f"Starting build for {step_ref}.")
127 with in_build_env(build_rule.environment):
128 try:
129 build_rule.run_build(build_context, manifest)
130 except (RuntimeError, AttributeError) as e:
131 if context.parsed_args.debug_mode:
132 raise e
133 _error(
134 f"An error occurred during build/install at {step_ref} (defined at {build_rule.attribute_path.path}): {str(e)}"
135 )
136 _info(f"Completed build for {step_ref}.")
138 else:
139 build_system = auto_detect_buildsystem(manifest)
140 if build_system:
141 _info(f"Auto-detected build system: {build_system.__class__.__name__}")
142 build_context = BuildContext.from_command_context(context)
143 with in_build_env(build_system.environment):
144 build_system.run_build(
145 build_context,
146 manifest,
147 )
149 _non_verbose_info("Upstream builds completed successfully")
150 else:
151 _info("No build system was detected from the current plugin set.")
154def remove_unnecessary_env() -> None:
155 vs = [
156 "XDG_CACHE_HOME",
157 "XDG_CONFIG_DIRS",
158 "XDG_CONFIG_HOME",
159 "XDG_DATA_HOME",
160 "XDG_DATA_DIRS",
161 "XDG_RUNTIME_DIR",
162 ]
163 for v in vs:
164 if v in os.environ:
165 del os.environ[v]
167 # FIXME: Add custom HOME + XDG_RUNTIME_DIR
170@contextlib.contextmanager
171def _setup_build_env(build_env: BuildEnvironmentDefinition) -> Iterator[None]:
172 env_backup = dict(os.environ)
173 env = dict(env_backup)
174 had_delta = False
175 build_env.update_env(env)
176 if env != env_backup:
177 _set_env(env)
178 had_delta = True
179 _info("Updated environment to match build")
180 yield
181 if had_delta or env != env_backup:
182 _set_env(env_backup)
185def _set_env(desired_env: Mapping[str, str]) -> None:
186 os_env = os.environ
187 for key in os_env.keys() | desired_env.keys():
188 desired_value = desired_env.get(key)
189 if desired_value is None:
190 try:
191 del os_env[key]
192 except KeyError:
193 pass
194 else:
195 os_env[key] = desired_value