Coverage for src / ezcompiler / adapters / base_compiler.py: 37.78%

37 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-27 06:49 +0000

1# /////////////////////////////////////////////////////////////// 

2# BASE_COMPILER - Abstract compiler interface 

3# Project: ezcompiler 

4# /////////////////////////////////////////////////////////////// 

5 

6""" 

7Base compiler - Abstract base class for EzCompiler compilers. 

8 

9This module defines the abstract base class that all compiler implementations 

10must inherit from, establishing the interface and common functionality for 

11project compilation operations. 

12 

13Protocols layer can use WARNING and ERROR log levels. 

14""" 

15 

16from __future__ import annotations 

17 

18# /////////////////////////////////////////////////////////////// 

19# IMPORTS 

20# /////////////////////////////////////////////////////////////// 

21# Standard library imports 

22from abc import ABC, abstractmethod 

23 

24# Local imports 

25from ..shared.compiler_config import CompilerConfig 

26 

27# /////////////////////////////////////////////////////////////// 

28# CLASSES 

29# /////////////////////////////////////////////////////////////// 

30 

31 

32class BaseCompiler(ABC): 

33 """ 

34 Abstract base class for project compilers. 

35 

36 Defines the interface that all compiler implementations must follow. 

37 Provides common functionality for compilation operations and enforces 

38 the contract for concrete compiler classes. 

39 

40 Attributes: 

41 _config: CompilerConfig instance with project settings 

42 _zip_needed: Whether compilation output needs to be zipped 

43 

44 Example: 

45 >>> # Cannot instantiate directly, must subclass 

46 >>> class MyCompiler(BaseCompiler): 

47 ... def compile(self, console=True) -> None: 

48 ... pass 

49 ... def get_compiler_name(self) -> str: 

50 ... return "MyCompiler" 

51 """ 

52 

53 # //////////////////////////////////////////////// 

54 # INITIALIZATION 

55 # //////////////////////////////////////////////// 

56 

57 def __init__(self, config: CompilerConfig) -> None: 

58 """ 

59 Initialize the compiler with configuration. 

60 

61 Args: 

62 config: CompilerConfig instance with project settings 

63 """ 

64 self._config = config 

65 self._zip_needed = False 

66 

67 # //////////////////////////////////////////////// 

68 # PROPERTIES 

69 # //////////////////////////////////////////////// 

70 

71 @property 

72 def config(self) -> CompilerConfig: 

73 """ 

74 Get the compiler configuration. 

75 

76 Returns: 

77 CompilerConfig: Configuration instance with project settings 

78 """ 

79 return self._config 

80 

81 @property 

82 def zip_needed(self) -> bool: 

83 """ 

84 Whether the compiled project needs to be zipped. 

85 

86 Returns: 

87 bool: True if output should be zipped, False otherwise 

88 

89 Note: 

90 Subclasses set this based on their output format. 

91 Cx_Freeze sets to True, PyInstaller sets to False. 

92 """ 

93 return self._zip_needed 

94 

95 # //////////////////////////////////////////////// 

96 # ABSTRACT METHODS 

97 # //////////////////////////////////////////////// 

98 

99 @abstractmethod 

100 def compile(self, console: bool = True) -> None: 

101 """ 

102 Compile the project. 

103 

104 This method must be implemented by all subclasses to handle 

105 the actual compilation using their respective compiler. 

106 

107 Args: 

108 console: Whether to show console window (default: True) 

109 

110 Raises: 

111 CompilationError: If compilation fails 

112 

113 Example: 

114 >>> compiler = PyInstallerCompiler(config) 

115 >>> compiler.compile(console=False) 

116 """ 

117 

118 @abstractmethod 

119 def get_compiler_name(self) -> str: 

120 """ 

121 Get the name of this compiler. 

122 

123 Returns: 

124 str: Display name of the compiler 

125 

126 Example: 

127 >>> compiler = PyInstallerCompiler(config) 

128 >>> name = compiler.get_compiler_name() 

129 >>> print(name) 

130 'PyInstaller (Empaquetée)' 

131 """ 

132 

133 # //////////////////////////////////////////////// 

134 # VALIDATION AND PREPARATION METHODS 

135 # //////////////////////////////////////////////// 

136 

137 def _validate_config(self) -> None: 

138 """ 

139 Validate configuration for this compiler. 

140 

141 Checks that all required configuration fields are present 

142 and valid (main file exists, output folder is set). 

143 

144 Raises: 

145 CompilationError: If validation fails 

146 

147 Note: 

148 Called at the start of compile() to ensure config is valid. 

149 Uses CompilerUtils internally. 

150 """ 

151 from ..utils.compiler_utils import CompilerUtils 

152 

153 CompilerUtils.validate_compiler_config(self._config) 

154 

155 def _prepare_output_directory(self) -> None: 

156 """ 

157 Prepare the output directory for compilation. 

158 

159 Creates the output directory if it doesn't exist, including 

160 any parent directories as needed. 

161 

162 Note: 

163 Called before compilation to ensure output directory is ready. 

164 Uses CompilerUtils internally. 

165 """ 

166 from ..utils.compiler_utils import CompilerUtils 

167 

168 CompilerUtils.prepare_compiler_output_directory(self._config) 

169 

170 @staticmethod 

171 def _extract_error_summary(raw_output: str) -> str: 

172 """ 

173 Extract a concise error summary from compiler subprocess output. 

174 

175 Filters out verbose INFO/WARNING lines and keeps only meaningful 

176 error information (ERROR lines, Traceback, and final exception). 

177 

178 Args: 

179 raw_output: Raw stderr or stdout from subprocess 

180 

181 Returns: 

182 str: Concise error message 

183 """ 

184 lines = raw_output.splitlines() 

185 

186 # Try to extract the last traceback + exception 

187 traceback_start = None 

188 for i, line in enumerate(lines): 

189 if line.strip().startswith("Traceback"): 

190 traceback_start = i 

191 

192 if traceback_start is not None: 

193 # Get from last Traceback to end, skip Nuitka-Reports lines 

194 tb_lines = [ 

195 line 

196 for line in lines[traceback_start:] 

197 if not line.startswith("Nuitka-Reports:") 

198 ] 

199 return "\n".join(tb_lines).strip() 

200 

201 # Fallback: extract ERROR lines 

202 error_lines = [ 

203 line for line in lines if "ERROR" in line or "error:" in line.lower() 

204 ] 

205 if error_lines: 

206 return "\n".join(error_lines).strip() 

207 

208 # Last resort: return last 5 non-empty lines 

209 non_empty = [line for line in lines if line.strip()] 

210 return "\n".join(non_empty[-5:]).strip() 

211 

212 def _get_include_files_data(self) -> list[str]: 

213 """ 

214 Get formatted include files data for compilation. 

215 

216 Combines individual files and folders from configuration, 

217 formatting folders with trailing slashes for compatibility. 

218 

219 Returns: 

220 list[str]: List of formatted include paths 

221 

222 Note: 

223 Uses CompilerUtils internally. 

224 

225 Example: 

226 >>> config.include_files = { 

227 ... "files": ["config.yaml"], 

228 ... "folders": ["lib", "assets"] 

229 ... } 

230 >>> files = compiler._get_include_files_data() 

231 >>> print(files) 

232 ['config.yaml', 'lib/', 'assets/'] 

233 """ 

234 from ..utils.compiler_utils import CompilerUtils 

235 

236 return CompilerUtils.format_include_files_data(self._config)