Coverage for packages/pyswig/src/pyswig/swig.py: 85%

156 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-26 21:05 +0000

1# Copyright (c) 2020 Michel Gillet 

2# SPDX-License-Identifier: MIT 

3 

4from __future__ import annotations 

5 

6import os 

7import subprocess 

8from pathlib import Path 

9 

10from pyswig.exceptions import PySwigError 

11 

12 

13class Swig: 

14 """Swig helper class""" 

15 

16 SWIG_SEARCH_ENV = ["SWIG", "ESYS_SWIG"] 

17 SWIG_EXE_NAME = ["swig.exe", "swig", "swigwin.exe"] 

18 

19 LANGUAGES = ["python", "csharp", "xml"] 

20 

21 def __init__(self) -> None: 

22 """Constructor""" 

23 self.m_path: str | None = None 

24 self.m_exe_path: str | None = None 

25 self.m_version: str | None = None 

26 self.m_verbose = False 

27 self.m_language: str | None = None 

28 self.m_process_cpp = False 

29 self.m_all_warnings = False 

30 self.m_input_file: str | None = None 

31 self.m_include_libs: list[str] | None = None 

32 self.m_threads_enabled = False 

33 

34 def set_include_libs(self, include_libs: list[str] | None) -> None: 

35 """Set the pathes where to search for Swig include libraries""" 

36 self.m_include_libs = include_libs 

37 

38 def add_include_lib(self, include_lib: str) -> None: 

39 """Add one path where to search for Swig include libraries""" 

40 if self.m_include_libs is None: 

41 self.m_include_libs = [] 

42 self.m_include_libs.append(include_lib) 

43 

44 def get_include_libs(self) -> list[str] | None: 

45 """Get the pathes where to search for Swig include libraries""" 

46 return self.m_include_libs 

47 

48 def set_input_file(self, input_file: str) -> None: 

49 self.m_input_file = input_file 

50 

51 def get_input_file(self) -> str | None: 

52 return self.m_input_file 

53 

54 def set_all_warnings(self, all_warnings: bool = True) -> None: 

55 """Turn on or off all warnings""" 

56 self.m_all_warnings = all_warnings 

57 

58 def get_all_warnings(self) -> bool: 

59 return self.m_all_warnings 

60 

61 def set_threads_enabled(self, are_threads_enabled: bool = True) -> None: 

62 """Turn on or off thread support""" 

63 self.m_threads_enabled = are_threads_enabled 

64 

65 def get_threads_enabled(self) -> bool: 

66 return self.m_threads_enabled 

67 

68 def set_process_cpp(self, process_cpp: bool = True) -> None: 

69 """Turn on of off the processing of C++""" 

70 self.m_process_cpp = process_cpp 

71 

72 def get_process_cpp(self) -> bool: 

73 return self.m_process_cpp 

74 

75 def set_language(self, language: str) -> None: 

76 self.m_language = language 

77 

78 def get_language(self) -> str | None: 

79 return self.m_language 

80 

81 def log(self, msg: str) -> None: 

82 if self.get_verbose(): 

83 print(msg) 

84 

85 def set_verbose(self, verbose: bool) -> None: 

86 self.m_verbose = verbose 

87 

88 def get_verbose(self) -> bool: 

89 return self.m_verbose 

90 

91 def set_version(self, version: str) -> None: 

92 """Set Swig version""" 

93 self.m_version = version 

94 

95 def get_version(self) -> str | None: 

96 """Get Swig version""" 

97 return self.m_version 

98 

99 def get_exe_path(self) -> str | None: 

100 """Get the path to Swig executable""" 

101 return self.m_exe_path 

102 

103 def get_path(self) -> str | None: 

104 """Get the directory where Swig was detected""" 

105 return self.m_path 

106 

107 def set_exe_path(self, exe_path: str) -> None: 

108 """Set the path to Swig executable""" 

109 self.m_exe_path = exe_path 

110 

111 def found_dir(self, path: str) -> bool: 

112 """Called when swig was found on some path""" 

113 search_dir = Path(path).resolve() 

114 self.m_path = str(search_dir) 

115 for exe_name in Swig.SWIG_EXE_NAME: 

116 exe_path = search_dir / exe_name 

117 if exe_path.is_file(): 

118 self.set_exe_path(str(exe_path)) 

119 return True 

120 return False 

121 

122 def detect(self) -> bool: 

123 """Detect Swig""" 

124 if not self.detect_in_env(): 

125 if not self.detect_in_exe_path(): 

126 return False 

127 if self.detect_version() == 0: 

128 return True 

129 return False 

130 

131 def detect_in_env(self) -> bool: 

132 """Try to find the swig executable using environment variable""" 

133 for swig_env in Swig.SWIG_SEARCH_ENV: 

134 if swig_env in os.environ: 

135 env_path = os.environ.get(swig_env) 

136 if env_path is not None and self.found_dir(env_path): 

137 return True 

138 return False 

139 

140 def detect_in_exe_path(self) -> bool: 

141 """Try to find the swig executable hoping it's found in the path""" 

142 path_env = os.environ.get("PATH") 

143 if path_env is None: 

144 return False 

145 for path in path_env.split(os.pathsep): 

146 if self.found_dir(path): 

147 return True 

148 return False 

149 

150 def detect_version(self) -> int: 

151 """Detect swig version""" 

152 exe_path = self.get_exe_path() 

153 if exe_path is None: 

154 return -1 

155 result = subprocess.run( 

156 [exe_path, "-version"], 

157 capture_output=True, 

158 text=True, 

159 check=False, 

160 ) 

161 if result.returncode != 0: 

162 return -2 

163 output = result.stdout 

164 found = -1 

165 line: str | None = None 

166 for line in output.split("\n"): 

167 found = line.find("SWIG Version") 

168 if found != -1: 

169 break 

170 if found == -1: 

171 return -3 

172 if line is None: 

173 return -4 

174 line = line.replace("SWIG Version", "") 

175 line = line.strip() 

176 self.set_version(line) 

177 return 0 

178 

179 def _resolve_include_path(self, include_lib: str, include_base: str) -> str: 

180 include_path = Path(include_lib) 

181 if include_path.is_absolute(): 

182 return str(include_path) 

183 return str(Path(include_base) / include_path) 

184 

185 def run(self, cwd: str | None = None) -> int: 

186 """Run swig with the configured parameters""" 

187 exe_path = self.get_exe_path() 

188 if exe_path is None: 

189 raise PySwigError("SWIG executable not found; call detect() or set_exe_path()") 

190 include_base = cwd or os.getcwd() 

191 cmd = [exe_path] 

192 if self.get_all_warnings(): 

193 cmd.append("-Wall") 

194 if self.get_process_cpp(): 

195 cmd.append("-c++") 

196 language = self.get_language() 

197 if language is not None: 

198 cmd.append("-" + language) 

199 if self.get_threads_enabled(): 

200 cmd.append("-threads") 

201 include_libs = self.get_include_libs() 

202 if include_libs is not None: 

203 for include_lib in include_libs: 

204 resolved = self._resolve_include_path(include_lib, include_base) 

205 cmd.append("-I" + resolved) 

206 input_file = self.get_input_file() 

207 if input_file is not None: 

208 cmd.append(input_file) 

209 self.log(" ".join(cmd)) 

210 result = subprocess.run( 

211 cmd, 

212 capture_output=True, 

213 text=True, 

214 check=False, 

215 cwd=cwd, 

216 ) 

217 if self.get_verbose(): 

218 if result.stdout: 

219 print(result.stdout, end="") 

220 if result.stderr: 

221 print(result.stderr, end="") 

222 if result.returncode != 0: 

223 return -2 

224 return 0