Coverage for src / ezcompiler / adapters / _nuitka_compiler.py: 12.20%
77 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# NUITKA_COMPILER - Nuitka compiler implementation
3# Project: ezcompiler
4# ///////////////////////////////////////////////////////////////
6"""
7Nuitka compiler - Nuitka compiler implementation for EzCompiler.
9This module provides a compiler implementation using Nuitka, which
10can generate either a standalone folder or a single-file executable.
12Protocols layer can use WARNING and ERROR log levels.
13"""
15from __future__ import annotations
17# ///////////////////////////////////////////////////////////////
18# IMPORTS
19# ///////////////////////////////////////////////////////////////
20# Standard library imports
21import shutil
22import subprocess
23import sys
24from pathlib import Path
26# Local imports
27from ..shared import CompilerConfig
28from ..shared.exceptions import CompilationError
29from .base_compiler import BaseCompiler
31# ///////////////////////////////////////////////////////////////
32# CLASSES
33# ///////////////////////////////////////////////////////////////
36class NuitkaCompiler(BaseCompiler):
37 """
38 Nuitka compiler implementation.
40 Handles project compilation using Nuitka, which can produce a
41 standalone folder or a single-file executable depending on options.
43 Attributes:
44 _config: CompilerConfig with project settings
45 """
47 # ////////////////////////////////////////////////
48 # INITIALIZATION
49 # ////////////////////////////////////////////////
51 def __init__(self, config: CompilerConfig) -> None:
52 """
53 Initialize Nuitka compiler.
55 Args:
56 config: CompilerConfig instance with project settings
57 """
58 super().__init__(config)
59 self._zip_needed = True
61 # ////////////////////////////////////////////////
62 # COMPILER INTERFACE METHODS
63 # ////////////////////////////////////////////////
65 def get_compiler_name(self) -> str:
66 """
67 Get the name of this compiler.
69 Returns:
70 str: Display name "Nuitka"
71 """
72 return "Nuitka"
74 def compile(self, console: bool = True) -> None:
75 """
76 Compile the project using Nuitka.
78 Validates configuration, prepares output directory, builds Nuitka
79 options from project settings, and runs compilation.
81 Args:
82 console: Whether to show console window (default: True)
84 Raises:
85 CompilationError: If compilation fails
86 """
87 try:
88 # Validate and prepare
89 self._validate_config()
90 self._prepare_output_directory()
92 # Choose output mode
93 from ..utils._compiler_utils import CompilerUtils
95 onefile = CompilerUtils.check_onefile_mode()
96 self._zip_needed = not onefile
98 # Build Nuitka command
99 output_dir = str(self._config.output_folder)
100 output_name = self._config.project_name
101 cmd = [
102 sys.executable,
103 "-m",
104 "nuitka",
105 self._config.main_file,
106 "--assume-yes-for-downloads",
107 "--remove-output",
108 f"--output-dir={output_dir}",
109 f"--output-filename={output_name}",
110 ]
112 if onefile:
113 cmd.append("--onefile")
114 else:
115 cmd.append("--standalone")
117 # Windows: use MSVC backend (MinGW64 not supported on Python 3.13+)
118 if sys.platform == "win32":
119 cmd.append("--msvc=latest")
120 if not console:
121 cmd.append("--windows-disable-console")
123 # Icon support
124 if self._config.icon:
125 cmd.append(f"--windows-icon-from-ico={self._config.icon}")
127 # Include data files
128 for file in self._config.include_files.get("files", []):
129 cmd.append(f"--include-data-file={file}={Path(file).name}")
131 for folder in self._config.include_files.get("folders", []):
132 cmd.append(f"--include-data-dir={folder}={folder}")
134 # Include packages and modules (both use --include-module to avoid
135 # crashes with stdlib built-in modules that have no __path__)
136 for mod in self._config.packages + self._config.includes:
137 cmd.append(f"--include-module={mod}")
139 # Exclude modules
140 for mod in self._config.excludes:
141 cmd.append(f"--nofollow-import-to={mod}")
143 # Windows metadata
144 if sys.platform == "win32":
145 cmd.append(f"--product-name={self._config.project_name}")
146 cmd.append(f"--product-version={self._config.version}")
147 if self._config.company_name:
148 cmd.append(f"--company-name={self._config.company_name}")
149 if self._config.project_description:
150 cmd.append(f"--file-description={self._config.project_description}")
152 # Advanced options
153 if self._config.debug:
154 cmd.append("--debug")
156 # Add compiler-specific options from config.compiler_options
157 # Format: {"option-name": "value"} -> --option-name=value
158 # {"option-flag": True} -> --option-flag
159 if self._config.compiler_options:
160 for key, value in self._config.compiler_options.items():
161 if isinstance(value, bool):
162 if value: # Only add if True
163 cmd.append(f"--{key}")
164 else:
165 cmd.append(f"--{key}={value}")
167 # Run Nuitka
168 result = subprocess.run(cmd, check=False, capture_output=True, text=True) # noqa: S603
169 if result.returncode != 0:
170 raw_output = result.stderr or result.stdout
171 error_detail = self._extract_error_summary(raw_output)
172 raise CompilationError(f"Nuitka compilation failed: {error_detail}")
174 # Flatten output: Nuitka --standalone creates a subfolder
175 # named "{main_stem}.dist" inside output-dir. Move its contents
176 # up to output_folder so the layout matches Cx_Freeze.
177 if not onefile:
178 main_stem = Path(self._config.main_file).stem
179 nested = self._config.output_folder / f"{main_stem}.dist"
180 if nested.is_dir():
181 for item in nested.iterdir():
182 dest = self._config.output_folder / item.name
183 if dest.exists():
184 if dest.is_dir():
185 shutil.rmtree(dest)
186 else:
187 dest.unlink()
188 shutil.move(str(item), str(dest))
189 nested.rmdir()
191 except Exception as e:
192 if isinstance(e, CompilationError):
193 raise
194 raise CompilationError(f"Nuitka compilation failed: {str(e)}") from e