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
« 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# ///////////////////////////////////////////////////////////////
6"""
7Base compiler - Abstract base class for EzCompiler compilers.
9This module defines the abstract base class that all compiler implementations
10must inherit from, establishing the interface and common functionality for
11project compilation operations.
13Protocols layer can use WARNING and ERROR log levels.
14"""
16from __future__ import annotations
18# ///////////////////////////////////////////////////////////////
19# IMPORTS
20# ///////////////////////////////////////////////////////////////
21# Standard library imports
22from abc import ABC, abstractmethod
24# Local imports
25from ..shared.compiler_config import CompilerConfig
27# ///////////////////////////////////////////////////////////////
28# CLASSES
29# ///////////////////////////////////////////////////////////////
32class BaseCompiler(ABC):
33 """
34 Abstract base class for project compilers.
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.
40 Attributes:
41 _config: CompilerConfig instance with project settings
42 _zip_needed: Whether compilation output needs to be zipped
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 """
53 # ////////////////////////////////////////////////
54 # INITIALIZATION
55 # ////////////////////////////////////////////////
57 def __init__(self, config: CompilerConfig) -> None:
58 """
59 Initialize the compiler with configuration.
61 Args:
62 config: CompilerConfig instance with project settings
63 """
64 self._config = config
65 self._zip_needed = False
67 # ////////////////////////////////////////////////
68 # PROPERTIES
69 # ////////////////////////////////////////////////
71 @property
72 def config(self) -> CompilerConfig:
73 """
74 Get the compiler configuration.
76 Returns:
77 CompilerConfig: Configuration instance with project settings
78 """
79 return self._config
81 @property
82 def zip_needed(self) -> bool:
83 """
84 Whether the compiled project needs to be zipped.
86 Returns:
87 bool: True if output should be zipped, False otherwise
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
95 # ////////////////////////////////////////////////
96 # ABSTRACT METHODS
97 # ////////////////////////////////////////////////
99 @abstractmethod
100 def compile(self, console: bool = True) -> None:
101 """
102 Compile the project.
104 This method must be implemented by all subclasses to handle
105 the actual compilation using their respective compiler.
107 Args:
108 console: Whether to show console window (default: True)
110 Raises:
111 CompilationError: If compilation fails
113 Example:
114 >>> compiler = PyInstallerCompiler(config)
115 >>> compiler.compile(console=False)
116 """
118 @abstractmethod
119 def get_compiler_name(self) -> str:
120 """
121 Get the name of this compiler.
123 Returns:
124 str: Display name of the compiler
126 Example:
127 >>> compiler = PyInstallerCompiler(config)
128 >>> name = compiler.get_compiler_name()
129 >>> print(name)
130 'PyInstaller (Empaquetée)'
131 """
133 # ////////////////////////////////////////////////
134 # VALIDATION AND PREPARATION METHODS
135 # ////////////////////////////////////////////////
137 def _validate_config(self) -> None:
138 """
139 Validate configuration for this compiler.
141 Checks that all required configuration fields are present
142 and valid (main file exists, output folder is set).
144 Raises:
145 CompilationError: If validation fails
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
153 CompilerUtils.validate_compiler_config(self._config)
155 def _prepare_output_directory(self) -> None:
156 """
157 Prepare the output directory for compilation.
159 Creates the output directory if it doesn't exist, including
160 any parent directories as needed.
162 Note:
163 Called before compilation to ensure output directory is ready.
164 Uses CompilerUtils internally.
165 """
166 from ..utils.compiler_utils import CompilerUtils
168 CompilerUtils.prepare_compiler_output_directory(self._config)
170 @staticmethod
171 def _extract_error_summary(raw_output: str) -> str:
172 """
173 Extract a concise error summary from compiler subprocess output.
175 Filters out verbose INFO/WARNING lines and keeps only meaningful
176 error information (ERROR lines, Traceback, and final exception).
178 Args:
179 raw_output: Raw stderr or stdout from subprocess
181 Returns:
182 str: Concise error message
183 """
184 lines = raw_output.splitlines()
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
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()
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()
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()
212 def _get_include_files_data(self) -> list[str]:
213 """
214 Get formatted include files data for compilation.
216 Combines individual files and folders from configuration,
217 formatting folders with trailing slashes for compatibility.
219 Returns:
220 list[str]: List of formatted include paths
222 Note:
223 Uses CompilerUtils internally.
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
236 return CompilerUtils.format_include_files_data(self._config)