Coverage for src / ezcompiler / adapters / _pyinstaller_compiler.py: 14.15%
66 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 00:22 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 00:22 +0000
1# ///////////////////////////////////////////////////////////////
2# PYINSTALLER_COMPILER - PyInstaller compiler implementation
3# Project: ezcompiler
4# ///////////////////////////////////////////////////////////////
6"""
7PyInstaller compiler - PyInstaller compiler implementation for EzCompiler.
9This module provides a compiler implementation using PyInstaller, which
10creates a single executable file with all dependencies bundled.
12Compilation is executed in a subprocess to isolate PyInstaller stdout/stderr
13from the main process (preserving DLP rendering).
15Protocols layer can use WARNING and ERROR log levels.
16"""
18from __future__ import annotations
20# ///////////////////////////////////////////////////////////////
21# IMPORTS
22# ///////////////////////////////////////////////////////////////
23# Standard library imports
24import shutil
25import subprocess
26import sys
27from pathlib import Path
29# Local imports
30from ..shared import CompilerConfig
31from ..shared.exceptions import CompilationError
32from .base_compiler import BaseCompiler
34# ///////////////////////////////////////////////////////////////
35# CLASSES
36# ///////////////////////////////////////////////////////////////
39class PyInstallerCompiler(BaseCompiler):
40 """
41 PyInstaller compiler implementation.
43 Handles project compilation using PyInstaller, which creates a
44 single executable file with all dependencies bundled. Can generate
45 either single-file or directory-based executables.
47 Compilation runs in a separate subprocess to prevent PyInstaller
48 output from interfering with the main process display (DLP).
50 Attributes:
51 _config: CompilerConfig with project settings
53 Example:
54 >>> config = CompilerConfig(...)
55 >>> compiler = PyInstallerCompiler(config)
56 >>> compiler.compile(console=True)
57 """
59 # ////////////////////////////////////////////////
60 # INITIALIZATION
61 # ////////////////////////////////////////////////
63 def __init__(self, config: CompilerConfig) -> None:
64 """
65 Initialize PyInstaller compiler.
67 Args:
68 config: CompilerConfig instance with project settings
70 Note:
71 PyInstaller creates single files, so _zip_needed is set to False.
72 """
73 super().__init__(config)
74 self._zip_needed = False # PyInstaller creates single file
76 # ////////////////////////////////////////////////
77 # COMPILER INTERFACE METHODS
78 # ////////////////////////////////////////////////
80 def get_compiler_name(self) -> str:
81 """
82 Get the name of this compiler.
84 Returns:
85 str: Display name "PyInstaller (Empaquetée)"
87 Example:
88 >>> compiler = PyInstallerCompiler(config)
89 >>> print(compiler.get_compiler_name())
90 'PyInstaller (Empaquetée)'
91 """
92 return "PyInstaller (Empaquetée)"
94 def compile(self, console: bool = True) -> None:
95 """
96 Compile the project using PyInstaller in a subprocess.
98 Validates configuration, prepares output directory, builds
99 PyInstaller command-line arguments, and runs compilation in
100 a separate process to isolate stdout/stderr from the DLP.
102 Args:
103 console: Whether to show console window (default: True)
105 Raises:
106 CompilationError: If compilation fails
108 Note:
109 Adds version file if it exists. Includes all configured
110 packages, includes, and applies excludes. Adds files and
111 folders with appropriate data paths.
113 Example:
114 >>> config = CompilerConfig(...)
115 >>> compiler = PyInstallerCompiler(config)
116 >>> compiler.compile(console=False)
117 """
118 try:
119 # Validate and prepare
120 self._validate_config()
121 self._prepare_output_directory()
123 # Determine output type and ZIP behavior
124 from ..utils._compiler_utils import CompilerUtils
126 onefile = CompilerUtils.check_onefile_mode()
127 self._zip_needed = not onefile
129 # Build PyInstaller command
130 cmd = [
131 sys.executable,
132 "-m",
133 "PyInstaller",
134 self._config.main_file,
135 "--console" if console else "--windowed",
136 "--onefile" if onefile else "--onedir",
137 "--clean",
138 "-y",
139 f"--distpath={self._config.output_folder}",
140 f"--name={self._config.project_name}",
141 ]
143 # Add version file if it exists
144 if (
145 self._config.version_filename
146 and Path(self._config.version_filename).exists()
147 ):
148 cmd.append(f"--version-file={self._config.version_filename}")
150 # Add icon if specified
151 if self._config.icon:
152 cmd.append(f"--icon={self._config.icon}")
154 # Add include files
155 for file in self._config.include_files.get("files", []):
156 cmd.append(f"--add-data={file};.")
158 # Add include folders
159 for folder in self._config.include_files.get("folders", []):
160 cmd.append(f"--add-data={folder};{folder}")
162 # Add hidden imports (packages and includes)
163 for pkg in self._config.packages + self._config.includes:
164 cmd.append(f"--hidden-import={pkg}")
166 # Add excluded modules
167 for mod in self._config.excludes:
168 cmd.append(f"--exclude-module={mod}")
170 # Advanced options
171 if self._config.optimize:
172 cmd.append("--optimize=1")
174 if self._config.strip:
175 cmd.append("--strip")
177 if self._config.debug:
178 cmd.append("--debug=all")
180 # Add compiler-specific options from config.compiler_options
181 # Format: {"option-name": "value"} -> --option-name=value
182 # {"option-flag": True} -> --option-flag
183 if self._config.compiler_options:
184 for key, value in self._config.compiler_options.items():
185 if isinstance(value, bool):
186 if value: # Only add if True
187 cmd.append(f"--{key}")
188 else:
189 cmd.append(f"--{key}={value}")
191 # Run PyInstaller in subprocess with captured output
192 result = subprocess.run( # noqa: S603
193 cmd,
194 check=False,
195 capture_output=True,
196 text=True,
197 )
199 if result.returncode != 0:
200 raw_output = result.stderr or result.stdout
201 error_detail = self._extract_error_summary(raw_output)
202 raise CompilationError(
203 f"PyInstaller compilation failed: {error_detail}"
204 )
206 # Flatten output: PyInstaller --onedir creates a subfolder
207 # named after the project inside distpath. Move its contents
208 # up to output_folder so the layout matches Cx_Freeze.
209 if not onefile:
210 nested = self._config.output_folder / self._config.project_name
211 if nested.is_dir():
212 for item in nested.iterdir():
213 dest = self._config.output_folder / item.name
214 if dest.exists():
215 if dest.is_dir():
216 shutil.rmtree(dest)
217 else:
218 dest.unlink()
219 shutil.move(str(item), str(dest))
220 nested.rmdir()
222 except Exception as e:
223 if isinstance(e, CompilationError):
224 raise
225 raise CompilationError(f"PyInstaller compilation failed: {str(e)}") from e