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

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

2# NUITKA_COMPILER - Nuitka compiler implementation 

3# Project: ezcompiler 

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

5 

6""" 

7Nuitka compiler - Nuitka compiler implementation for EzCompiler. 

8 

9This module provides a compiler implementation using Nuitka, which 

10can generate either a standalone folder or a single-file executable. 

11 

12Protocols layer can use WARNING and ERROR log levels. 

13""" 

14 

15from __future__ import annotations 

16 

17# /////////////////////////////////////////////////////////////// 

18# IMPORTS 

19# /////////////////////////////////////////////////////////////// 

20# Standard library imports 

21import shutil 

22import subprocess 

23import sys 

24from pathlib import Path 

25 

26# Local imports 

27from ..shared import CompilerConfig 

28from ..shared.exceptions import CompilationError 

29from .base_compiler import BaseCompiler 

30 

31# /////////////////////////////////////////////////////////////// 

32# CLASSES 

33# /////////////////////////////////////////////////////////////// 

34 

35 

36class NuitkaCompiler(BaseCompiler): 

37 """ 

38 Nuitka compiler implementation. 

39 

40 Handles project compilation using Nuitka, which can produce a 

41 standalone folder or a single-file executable depending on options. 

42 

43 Attributes: 

44 _config: CompilerConfig with project settings 

45 """ 

46 

47 # //////////////////////////////////////////////// 

48 # INITIALIZATION 

49 # //////////////////////////////////////////////// 

50 

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

52 """ 

53 Initialize Nuitka compiler. 

54 

55 Args: 

56 config: CompilerConfig instance with project settings 

57 """ 

58 super().__init__(config) 

59 self._zip_needed = True 

60 

61 # //////////////////////////////////////////////// 

62 # COMPILER INTERFACE METHODS 

63 # //////////////////////////////////////////////// 

64 

65 def get_compiler_name(self) -> str: 

66 """ 

67 Get the name of this compiler. 

68 

69 Returns: 

70 str: Display name "Nuitka" 

71 """ 

72 return "Nuitka" 

73 

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

75 """ 

76 Compile the project using Nuitka. 

77 

78 Validates configuration, prepares output directory, builds Nuitka 

79 options from project settings, and runs compilation. 

80 

81 Args: 

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

83 

84 Raises: 

85 CompilationError: If compilation fails 

86 """ 

87 try: 

88 # Validate and prepare 

89 self._validate_config() 

90 self._prepare_output_directory() 

91 

92 # Choose output mode 

93 from ..utils._compiler_utils import CompilerUtils 

94 

95 onefile = CompilerUtils.check_onefile_mode() 

96 self._zip_needed = not onefile 

97 

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 ] 

111 

112 if onefile: 

113 cmd.append("--onefile") 

114 else: 

115 cmd.append("--standalone") 

116 

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

122 

123 # Icon support 

124 if self._config.icon: 

125 cmd.append(f"--windows-icon-from-ico={self._config.icon}") 

126 

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

130 

131 for folder in self._config.include_files.get("folders", []): 

132 cmd.append(f"--include-data-dir={folder}={folder}") 

133 

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

138 

139 # Exclude modules 

140 for mod in self._config.excludes: 

141 cmd.append(f"--nofollow-import-to={mod}") 

142 

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

151 

152 # Advanced options 

153 if self._config.debug: 

154 cmd.append("--debug") 

155 

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

166 

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

173 

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() 

190 

191 except Exception as e: 

192 if isinstance(e, CompilationError): 

193 raise 

194 raise CompilationError(f"Nuitka compilation failed: {str(e)}") from e