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

1import collections 

2import contextlib 

3import os 

4from typing import ( 

5 Iterator, 

6 Mapping, 

7 List, 

8 Dict, 

9 Optional, 

10 TYPE_CHECKING, 

11) 

12 

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) 

25 

26if TYPE_CHECKING: 

27 from debputy.highlevel_manifest import HighLevelManifest 

28 

29 

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 

36 

37 

38def _set_stem_if_absent(stems: List[Optional[str]], idx: int, stem: str) -> None: 

39 if stems[idx] is None: 

40 stems[idx] = stem 

41 

42 

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 

52 

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) 

60 

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) 

70 

71 if len(pkg_names) == 1: 

72 pkg_name = next(iter(pkg_names)) 

73 by_name_only_builds[pkg_name].append(idx) 

74 

75 if "deb" not in reserved_stems and len(deb_only_builds) == 1: 

76 _set_stem_if_absent(stems, deb_only_builds[0], "deb") 

77 

78 if "udeb" not in reserved_stems and len(udeb_only_builds) == 1: 

79 _set_stem_if_absent(stems, udeb_only_builds[0], "udeb") 

80 

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) 

85 

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}") 

92 

93 

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}.") 

137 

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 ) 

148 

149 _non_verbose_info("Upstream builds completed successfully") 

150 else: 

151 _info("No build system was detected from the current plugin set.") 

152 

153 

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] 

166 

167 # FIXME: Add custom HOME + XDG_RUNTIME_DIR 

168 

169 

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) 

183 

184 

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