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
« 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
4from __future__ import annotations
6import os
7import subprocess
8from pathlib import Path
10from pyswig.exceptions import PySwigError
13class Swig:
14 """Swig helper class"""
16 SWIG_SEARCH_ENV = ["SWIG", "ESYS_SWIG"]
17 SWIG_EXE_NAME = ["swig.exe", "swig", "swigwin.exe"]
19 LANGUAGES = ["python", "csharp", "xml"]
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
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
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)
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
48 def set_input_file(self, input_file: str) -> None:
49 self.m_input_file = input_file
51 def get_input_file(self) -> str | None:
52 return self.m_input_file
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
58 def get_all_warnings(self) -> bool:
59 return self.m_all_warnings
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
65 def get_threads_enabled(self) -> bool:
66 return self.m_threads_enabled
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
72 def get_process_cpp(self) -> bool:
73 return self.m_process_cpp
75 def set_language(self, language: str) -> None:
76 self.m_language = language
78 def get_language(self) -> str | None:
79 return self.m_language
81 def log(self, msg: str) -> None:
82 if self.get_verbose():
83 print(msg)
85 def set_verbose(self, verbose: bool) -> None:
86 self.m_verbose = verbose
88 def get_verbose(self) -> bool:
89 return self.m_verbose
91 def set_version(self, version: str) -> None:
92 """Set Swig version"""
93 self.m_version = version
95 def get_version(self) -> str | None:
96 """Get Swig version"""
97 return self.m_version
99 def get_exe_path(self) -> str | None:
100 """Get the path to Swig executable"""
101 return self.m_exe_path
103 def get_path(self) -> str | None:
104 """Get the directory where Swig was detected"""
105 return self.m_path
107 def set_exe_path(self, exe_path: str) -> None:
108 """Set the path to Swig executable"""
109 self.m_exe_path = exe_path
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
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
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
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
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
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)
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