Coverage for src/ezcompiler/utils/_compiler_utils.py: 100.00%

33 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-16 16:37 +0000

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

2# COMPILER_UTILS - Compiler-specific utility functions 

3# Project: ezcompiler 

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

5 

6""" 

7Compiler utilities - Compiler-specific utility functions for EzCompiler. 

8 

9This module provides specialized utility functions for compiler operations, 

10including configuration validation, output directory preparation, and include 

11files formatting. Uses thematic utils (FileUtils, ValidationUtils) internally. 

12 

13Utils layer can only use DEBUG and ERROR log levels. 

14""" 

15 

16from __future__ import annotations 

17 

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

19# IMPORTS 

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

21# Standard library imports 

22from pathlib import Path 

23 

24# Local imports 

25from ..shared import CompilerConfig 

26from ..shared.exceptions.utils import ( 

27 CompilerConfigValidationError, 

28 MainFileNotFoundError, 

29 OutputDirectoryError, 

30) 

31from ._file_utils import FileUtils 

32 

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

34# CLASSES 

35# /////////////////////////////////////////////////////////////// 

36 

37 

38class CompilerUtils: 

39 """ 

40 Utility class for compiler-specific operations. 

41 

42 Provides static methods for compiler-related tasks such as configuration 

43 validation, output directory preparation, and include files formatting. 

44 Uses thematic utils (FileUtils, ValidationUtils) internally. 

45 

46 Example: 

47 >>> config = CompilerConfig(...) 

48 >>> CompilerUtils.validate_compiler_config(config) 

49 >>> CompilerUtils.prepare_compiler_output_directory(config) 

50 >>> files = CompilerUtils.format_include_files_data(config) 

51 """ 

52 

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

54 # VALIDATION METHODS 

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

56 

57 # Deliberate revalidation (defense in depth): CompilerConfig is a mutable 

58 # dataclass, so the state validated by __post_init__ may have changed between 

59 # construction and the call to compile(). This check is replayed right before 

60 # the compilation — it is not 

61 # pas une duplication morte mais un garde-fou au point d'usage. 

62 @staticmethod 

63 def validate_compiler_config(config: CompilerConfig) -> None: 

64 """ 

65 Validate configuration for compilation. 

66 

67 Checks that all required configuration fields are present 

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

69 

70 Args: 

71 config: CompilerConfig instance to validate 

72 

73 Raises: 

74 MainFileNotFoundError: If main file doesn't exist or is required but missing 

75 OutputDirectoryError: If output folder is not set 

76 

77 Note: 

78 Uses FileUtils for file existence checks. 

79 

80 Example: 

81 >>> config = CompilerConfig(...) 

82 >>> CompilerUtils.validate_compiler_config(config) 

83 """ 

84 if not config.main_file: 

85 raise CompilerConfigValidationError("Main file is required") 

86 

87 main_file_path = Path(config.main_file) 

88 if not main_file_path.exists() or not main_file_path.is_file(): 

89 raise MainFileNotFoundError(f"Main file not found: {config.main_file}") 

90 

91 if not config.output_folder: 

92 raise OutputDirectoryError("Output folder is required") 

93 

94 # //////////////////////////////////////////////// 

95 # PREPARATION METHODS 

96 # //////////////////////////////////////////////// 

97 

98 @staticmethod 

99 def prepare_compiler_output_directory(config: CompilerConfig) -> None: 

100 """ 

101 Prepare the output directory for compilation. 

102 

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

104 any parent directories as needed. 

105 

106 Args: 

107 config: CompilerConfig instance with output_folder path 

108 

109 Note: 

110 Uses FileUtils.create_directory_if_not_exists() internally. 

111 

112 Example: 

113 >>> config = CompilerConfig(..., output_folder=Path("dist")) 

114 >>> CompilerUtils.prepare_compiler_output_directory(config) 

115 """ 

116 FileUtils.create_directory_if_not_exists(config.output_folder) 

117 

118 # //////////////////////////////////////////////// 

119 # DATA FORMATTING METHODS 

120 # //////////////////////////////////////////////// 

121 

122 @staticmethod 

123 def format_include_files_data(config: CompilerConfig) -> list[str]: 

124 """ 

125 Format include files data for compilation. 

126 

127 Combines individual files and folders from configuration, 

128 formatting folders with trailing slashes for compatibility 

129 with different compilers. 

130 

131 Args: 

132 config: CompilerConfig instance with include_files 

133 

134 Returns: 

135 list[str]: List of formatted include paths 

136 

137 Note: 

138 Files are included as-is, folders are formatted with 

139 trailing slashes for Cx_Freeze compatibility. 

140 

141 Example: 

142 >>> config.include_files = { 

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

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

145 ... } 

146 >>> files = CompilerUtils.format_include_files_data(config) 

147 >>> print(files) 

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

149 """ 

150 files = config.include_files.get("files", []) 

151 folders = [f"{folder}/" for folder in config.include_files.get("folders", [])] 

152 return files + folders 

153 

154 # //////////////////////////////////////////////// 

155 # COMPILER-SPECIFIC HELPERS 

156 # //////////////////////////////////////////////// 

157 

158 @staticmethod 

159 def get_windows_base_for_console(console: bool) -> str | None: 

160 """ 

161 Get Windows base executable type based on console setting. 

162 

163 Args: 

164 console: Whether to show console window 

165 

166 Returns: 

167 str | None: "Win32GUI" if console=False on Windows, None otherwise 

168 

169 Note: 

170 Used by Cx_Freeze to determine executable base type. 

171 

172 Example: 

173 >>> base = CompilerUtils.get_windows_base_for_console(False) 

174 >>> print(base) 

175 'Win32GUI' # On Windows 

176 """ 

177 import sys 

178 

179 if sys.platform == "win32" and not console: 

180 return "Win32GUI" 

181 return None 

182 

183 @staticmethod 

184 def check_onefile_mode() -> bool: 

185 """ 

186 Check if onefile mode is requested via command-line arguments. 

187 

188 Returns: 

189 bool: True if --onefile is in sys.argv, False otherwise 

190 

191 Note: 

192 Used by PyInstaller and Nuitka to determine output format. 

193 

194 Example: 

195 >>> # When run with: python script.py --onefile 

196 >>> is_onefile = CompilerUtils.check_onefile_mode() 

197 >>> print(is_onefile) 

198 True 

199 """ 

200 import sys 

201 

202 return "--onefile" in sys.argv