From e589c2ae78ffcdc59148914b3ba6cf2075d205e3 Mon Sep 17 00:00:00 2001 From: Thies Moeller Date: Fri, 31 Jan 2025 14:47:20 +0100 Subject: [PATCH 1/4] port to swig4.3 --- pyproject.toml | 22 +++++++-------- src/genicam/genicam.i | 28 +++++++++---------- src/pylon/DeviceFactory.i | 2 +- src/pylon/GigETransportLayer.i | 2 +- src/pylon/GrabResultData.i | 6 ++-- src/pylon/PylonDataComponent.i | 6 ++-- src/pylon/TlFactory.i | 4 +-- src/pylon/TransportLayer.i | 2 +- src/pylondataprocessing/pylondataprocessing.i | 2 +- 9 files changed, 39 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3f11bf..b63a7a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=42,<72", "swig>=4.2,<4.3", "wheel"] +requires = ["setuptools>=42,<72", "swig==4.3", "wheel"] build-backend = "setuptools.build_meta" [tool.cibuildwheel] @@ -7,26 +7,26 @@ skip = "pp* cp36-* cp37-* cp38-*" build-verbosity = "0" test-requires = "pytest numpy" +[tool.pytest.ini_options] +testpaths = [ + "tests/genicam_tests", + "tests/pylon_tests/emulated", + "tests/pylondataprocessing_tests", +] + [tool.cibuildwheel.windows] archs = "AMD64" build = "cp39-win_*" before-all = 'echo "Building: %CIBW_BUILD%"' -test-command = [ - ''' - pytest {project}/tests/genicam_tests {project}/tests/pylon_tests/emulated {project}/tests/pylondataprocessing_tests - ''' - ] [tool.cibuildwheel.macos] archs = "x86_64 arm64" build = "cp39-macosx_*" before-all = 'echo "Building: $CIBW_BUILD"' repair-wheel-command = "" -test-command = [ - ''' - pytest {project}/tests/genicam_tests {project}/tests/pylon_tests/emulated - ''' - ] +test-skip = [ + "tests/pylondataprocessing_tests" +] [[tool.cibuildwheel.overrides]] select = "*-macosx_arm64" diff --git a/src/genicam/genicam.i b/src/genicam/genicam.i index e32ad5b..ed1d13b 100644 --- a/src/genicam/genicam.i +++ b/src/genicam/genicam.i @@ -295,7 +295,7 @@ import warnings -%typemap(argout,fragment="t_output_helper") (uint8_t *pBuffer, int64_t Length), (void *pBuffer, int64_t Length), (char *pBuffer, int64_t Length) +%typemap(argout) (uint8_t *pBuffer, int64_t Length), (void *pBuffer, int64_t Length), (char *pBuffer, int64_t Length) { PyObject *o; if ( ($2 < 0) || ($2 > INT_MAX) ) { @@ -303,10 +303,10 @@ import warnings SWIG_fail; } o = PyBytes_FromStringAndSize((const char*)$1,(int)$2); - $result = t_output_helper($result,o); + $result = SWIG_AppendOutput($result,o); } -%typemap(freearg,fragment="t_output_helper") (uint8_t *pBuffer, int64_t Length), (void *pBuffer, int64_t Length), (char *pBuffer, int64_t Length) +%typemap(freearg) (uint8_t *pBuffer, int64_t Length), (void *pBuffer, int64_t Length), (char *pBuffer, int64_t Length) { delete [] (char*)$1; } @@ -437,9 +437,9 @@ namespace GENICAM_NAMESPACE { %typemap(argout) const GENICAM_NAMESPACE::gcstring & {} %typemap(argout) GENICAM_NAMESPACE::gcstring & { %#if PY_VERSION_HEX >= 0x03000000 - $result = t_output_helper($result,PyUnicode_FromStringAndSize($1->c_str(),$1->length())); + $result = SWIG_AppendOutput($result,PyUnicode_FromStringAndSize($1->c_str(),$1->length())); %#else - $result = t_output_helper($result,PyString_FromStringAndSize($1->c_str(),$1->length())); + $result = SWIG_AppendOutput($result,PyString_FromStringAndSize($1->c_str(),$1->length())); %#endif } @@ -491,7 +491,7 @@ namespace GENICAM_NAMESPACE { $1 = new NodeList_t(); } -%typemap(argout,fragment="t_output_helper") GENAPI_NAMESPACE::NodeList_t & { +%typemap(argout) GENAPI_NAMESPACE::NodeList_t & { PyObject *o = PyTuple_New($1->size()); for( unsigned int i = 0; i < $1->size(); i++){ PyObject *o_item; @@ -551,7 +551,7 @@ namespace GENICAM_NAMESPACE { o_item = SWIG_NewPointerObj(outptr, outtype, 0); PyTuple_SetItem(o,i,o_item); } - $result = t_output_helper($result,o); + $result = SWIG_AppendOutput($result,o); delete $1; } @@ -562,7 +562,7 @@ namespace GENICAM_NAMESPACE { o_item = PyLong_FromLongLong($1[i]); PyTuple_SetItem(o,i,o_item); } - $result = t_output_helper($result,o); + $result = SWIG_AppendOutput($result,o); } %typemap(out) double_autovector_t { @@ -572,7 +572,7 @@ namespace GENICAM_NAMESPACE { o_item = PyFloat_FromDouble($1[i]); PyTuple_SetItem(o,i,o_item); } - $result = t_output_helper($result,o); + $result = SWIG_AppendOutput($result,o); } @@ -580,7 +580,7 @@ namespace GENICAM_NAMESPACE { $1 = new FeatureList_t(); } -%typemap(argout,fragment="t_output_helper") GENAPI_NAMESPACE::FeatureList_t & { +%typemap(argout) GENAPI_NAMESPACE::FeatureList_t & { PyObject *o = PyTuple_New($1->size()); for( unsigned int i = 0; i < $1->size(); i++){ PyObject *o_item; @@ -640,7 +640,7 @@ namespace GENICAM_NAMESPACE { o_item = SWIG_NewPointerObj(outptr, outtype, 0); PyTuple_SetItem(o,i,o_item); } - $result = t_output_helper($result,o); + $result = SWIG_AppendOutput($result,o); delete $1; } @@ -648,7 +648,7 @@ namespace GENICAM_NAMESPACE { $1 = new GENICAM_NAMESPACE::gcstring_vector(); } -%typemap(argout,fragment="t_output_helper") GENICAM_NAMESPACE::gcstring_vector & { +%typemap(argout) GENICAM_NAMESPACE::gcstring_vector & { PyObject *o = PyTuple_New($1->size()); for( unsigned int i = 0; i < $1->size(); i++){ PyObject *o_item; @@ -659,12 +659,12 @@ namespace GENICAM_NAMESPACE { %#endif PyTuple_SetItem(o,i,o_item); } - $result = t_output_helper($result,o); + $result = SWIG_AppendOutput($result,o); delete $1; } // Make sure the above typemap is no applied on const references -%typemap(argout,fragment="t_output_helper") const GENICAM_NAMESPACE::gcstring_vector & {} +%typemap(argout) const GENICAM_NAMESPACE::gcstring_vector & {} // typemaps for chunk handling diff --git a/src/pylon/DeviceFactory.i b/src/pylon/DeviceFactory.i index b75e5b5..2985415 100644 --- a/src/pylon/DeviceFactory.i +++ b/src/pylon/DeviceFactory.i @@ -7,7 +7,7 @@ $1 = new DeviceInfoList_t(); } -%typemap(argout,fragment="t_output_helper") Pylon::DeviceInfoList_t & { +%typemap(argout) Pylon::DeviceInfoList_t & { Py_DECREF($result); PyObject *tpl = PyTuple_New($1->size()); for (unsigned int i = 0; i < $1->size(); i++) { diff --git a/src/pylon/GigETransportLayer.i b/src/pylon/GigETransportLayer.i index d54743d..121fbcd 100644 --- a/src/pylon/GigETransportLayer.i +++ b/src/pylon/GigETransportLayer.i @@ -204,7 +204,7 @@ } -%typemap(argout, fragment="t_output_helper") +%typemap(argout) (uint32_t *pNumResults, Pylon::GigEActionCommandResult *results) { uint32_t cnt = *$1; diff --git a/src/pylon/GrabResultData.i b/src/pylon/GrabResultData.i index 0cc1f7e..24d223f 100644 --- a/src/pylon/GrabResultData.i +++ b/src/pylon/GrabResultData.i @@ -213,11 +213,13 @@ PyObject * result = 0; PyObject * data = (dst) ? PyByteArray_FromStringAndSize((const char *) dst, new_size) : Py_None; - result = SWIG_Python_AppendOutput(result, data); + // workaround for using AppendOutput outside of template for swig >= 4.3 + const int IS_VOID = 1; + result = SWIG_Python_AppendOutput(result, data, IS_VOID); delete [] dst; PyObject * tp = PyInt_FromLong((long) ret_pt); - result = SWIG_Python_AppendOutput(result, tp); + result = SWIG_Python_AppendOutput(result, tp, IS_VOID); return result; } diff --git a/src/pylon/PylonDataComponent.i b/src/pylon/PylonDataComponent.i index 1998190..b2efecb 100644 --- a/src/pylon/PylonDataComponent.i +++ b/src/pylon/PylonDataComponent.i @@ -300,11 +300,13 @@ PyObject * result = 0; PyObject * data = (dst) ? PyByteArray_FromStringAndSize((const char *) dst, new_size) : Py_None; - result = SWIG_Python_AppendOutput(result, data); + // workaround for using AppendOutput outside of template for swig >= 4.3 + const int IS_VOID = 1; + result = SWIG_Python_AppendOutput(result, data, IS_VOID); delete [] dst; PyObject * tp = PyInt_FromLong((long) ret_pt); - result = SWIG_Python_AppendOutput(result, tp); + result = SWIG_Python_AppendOutput(result, tp, IS_VOID); return result; } diff --git a/src/pylon/TlFactory.i b/src/pylon/TlFactory.i index d63ef0c..eb8aebb 100644 --- a/src/pylon/TlFactory.i +++ b/src/pylon/TlFactory.i @@ -15,7 +15,7 @@ $1 = new Pylon::TlInfoList_t(); } -%typemap(argout,fragment="t_output_helper") Pylon::TlInfoList_t & { +%typemap(argout) Pylon::TlInfoList_t & { Py_DECREF($result); PyObject *tpl = PyTuple_New($1->size()); for (unsigned int i = 0; i < $1->size(); i++) { @@ -41,7 +41,7 @@ $1 = new DeviceInfoList_t(); } -%typemap(argout,fragment="t_output_helper") Pylon::DeviceInfoList_t & { +%typemap(argout) Pylon::DeviceInfoList_t & { Py_DECREF($result); PyObject *tpl = PyTuple_New($1->size()); for (unsigned int i = 0; i < $1->size(); i++) { diff --git a/src/pylon/TransportLayer.i b/src/pylon/TransportLayer.i index 3c63760..8be1a9a 100644 --- a/src/pylon/TransportLayer.i +++ b/src/pylon/TransportLayer.i @@ -55,7 +55,7 @@ $2 = false; } -%typemap(argout,fragment="t_output_helper") Pylon::InterfaceInfoList_t & +%typemap(argout) Pylon::InterfaceInfoList_t & { Py_DECREF($result); PyObject *tpl = PyTuple_New($1->size()); diff --git a/src/pylondataprocessing/pylondataprocessing.i b/src/pylondataprocessing/pylondataprocessing.i index dd9aebc..240622f 100644 --- a/src/pylondataprocessing/pylondataprocessing.i +++ b/src/pylondataprocessing/pylondataprocessing.i @@ -615,7 +615,7 @@ Pylon::DataProcessing::CVariantContainer value Py_XDECREF(stringObject); Py_XDECREF(variantObject); } - $result = SWIG_Python_AppendOutput($result, obj); + $result = SWIG_Python_AppendOutput($result, obj,$isvoid); %} //////////////////////////////////////////////////////////////////////////////// From b9b46d903fa57bf524641a530c4b590354257326 Mon Sep 17 00:00:00 2001 From: Thies Moeller Date: Fri, 31 Jan 2025 14:47:42 +0100 Subject: [PATCH 2/4] swig file formatter --- scripts/format_swig.py | 263 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100755 scripts/format_swig.py diff --git a/scripts/format_swig.py b/scripts/format_swig.py new file mode 100755 index 0000000..0cac52d --- /dev/null +++ b/scripts/format_swig.py @@ -0,0 +1,263 @@ +#! /bin/env python3 +import re +import sys +import subprocess +import tempfile +import argparse +from dataclasses import dataclass +from enum import Enum, auto +from typing import List + +class CodeType(Enum): + CPP = auto() + PYTHON = auto() + SWIG = auto() + PYTHON_INLINE = auto() # For inline Python code + +@dataclass +class CodeSection: + type: CodeType + content: str + start_line: int + end_line: int + directive: str = "" + +def format_cpp(code: str, debug: bool = False) -> str: + """Format C++ code using clang-format with an explicit style.""" + if debug: + print("[DEBUG] Formatting C++ code with clang-format:\n", code) + with tempfile.NamedTemporaryFile(mode='w+', suffix='.cpp') as tmp: + tmp.write(code) + tmp.flush() + try: + # Specify a style for clarity; you can change 'llvm' to 'google', etc. + result = subprocess.run( + ['clang-format', '--style=llvm', tmp.name], + capture_output=True, + text=True, + check=True + ) + if debug: + print("[DEBUG] clang-format output:\n", result.stdout) + return result.stdout + except subprocess.CalledProcessError as e: + if debug: + print("[DEBUG] clang-format failed. Returning original code.") + print("[DEBUG] Error:", e.stderr) + return code + +def format_python(code: str, debug: bool = False) -> str: + """Format Python code using black.""" + if debug: + print("[DEBUG] Formatting Python code with black:\n", code) + try: + result = subprocess.run( + ['black', '-', '-q'], + input=code, + capture_output=True, + text=True, + check=True + ) + if debug: + print("[DEBUG] black output:\n", result.stdout) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + if debug: + print("[DEBUG] black formatting failed. Returning original code.") + print("[DEBUG] Error:", e.stderr) + return code + +def parse_swig_file(content: str, debug: bool = False) -> List[CodeSection]: + """Parse SWIG interface file into sections.""" + lines = content.split('\n') + sections = [] + current_type = CodeType.SWIG + current_content = [] + start_line = 0 + current_directive = "" + + i = 0 + while i < len(lines): + line = lines[i] + stripped_line = line.strip() + + # Handle inline %pythoncode + if '%pythoncode' in stripped_line and '%{' not in stripped_line: + if current_content: + sections.append(CodeSection( + type=current_type, + content='\n'.join(current_content), + start_line=start_line, + end_line=i-1, + directive=current_directive + )) + + # Add the inline Python code as its own section + sections.append(CodeSection( + type=CodeType.PYTHON_INLINE, + content=line, + start_line=i, + end_line=i, + directive="" + )) + + current_content = [] + start_line = i + 1 + i += 1 + continue + + # Check for C++ sections + elif stripped_line.startswith('%{'): + if current_content: + sections.append(CodeSection( + type=current_type, + content='\n'.join(current_content), + start_line=start_line, + end_line=i-1, + directive=current_directive + )) + current_type = CodeType.CPP + current_content = [line] + start_line = i + current_directive = line + + # Check for Python block sections + elif stripped_line.startswith('%pythoncode') and '%{' in stripped_line: + if current_content: + sections.append(CodeSection( + type=current_type, + content='\n'.join(current_content), + start_line=start_line, + end_line=i-1, + directive=current_directive + )) + current_type = CodeType.PYTHON + current_content = [line] + start_line = i + current_directive = line + + elif stripped_line == '%}' and (current_type == CodeType.CPP or current_type == CodeType.PYTHON): + current_content.append(line) + sections.append(CodeSection( + type=current_type, + content='\n'.join(current_content), + start_line=start_line, + end_line=i, + directive=current_directive + )) + current_type = CodeType.SWIG + current_content = [] + start_line = i + 1 + current_directive = "" + + else: + current_content.append(line) + + i += 1 + + # Add final section + if current_content: + sections.append(CodeSection( + type=current_type, + content='\n'.join(current_content), + start_line=start_line, + end_line=len(lines)-1, + directive=current_directive + )) + + if debug: + for sec in sections: + print(f"[DEBUG] Section from line {sec.start_line} to {sec.end_line}, type={sec.type}, directive={sec.directive}") + + return sections + +def format_section(section: CodeSection, debug: bool = False) -> str: + """Format a single section of code.""" + if debug: + print(f"[DEBUG] Formatting section (type={section.type}). Lines {section.start_line}-{section.end_line}") + if section.type == CodeType.CPP: + lines = section.content.split('\n') + # Remove the %{ ... %} wrapper to format only the C++ inside + inner_code = '\n'.join(lines[1:-1]) + formatted_inner = format_cpp(inner_code, debug=debug) + return f"{lines[0]}\n{formatted_inner.rstrip()}\n{lines[-1]}" + elif section.type == CodeType.PYTHON: + lines = section.content.split('\n') + # Remove the %pythoncode %{ ... %} wrapper to format only the Python inside + inner_code = '\n'.join(lines[1:-1]) + formatted_inner = format_python(inner_code, debug=debug) + return f"{lines[0]}\n{formatted_inner.rstrip()}\n{lines[-1]}" + elif section.type == CodeType.PYTHON_INLINE: + # For inline Python code, preserve exactly as is + return section.content + else: + # SWIG or unknown sections remain unchanged + return section.content + +def format_swig_file(filepath: str, in_place: bool = False, create_backup: bool = True, debug: bool = False) -> str: + """Format a SWIG interface file. + + Args: + filepath: Path to the SWIG interface file + in_place: If True, modify the file in place. + create_backup: If True, create a .bak backup of the original file. + debug: If True, print debugging information to stdout. + + Returns: + The formatted content as a string. + """ + if debug: + print(f"[DEBUG] Reading from {filepath}") + with open(filepath, 'r') as f: + content = f.read() + + sections = parse_swig_file(content, debug=debug) + formatted_sections = [format_section(section, debug=debug) for section in sections] + formatted_content = '\n'.join(formatted_sections) + + if debug: + print("[DEBUG] Finished formatting. Checking in_place option...") + if in_place: + if create_backup: + backup_path = filepath + '.bak' + if debug: + print(f"[DEBUG] Creating backup at {backup_path}") + with open(backup_path, 'w') as f: + f.write(content) + + if debug: + print(f"[DEBUG] Writing changes back to {filepath}") + with open(filepath, 'w') as f: + f.write(formatted_content) + + return formatted_content + +def main(): + parser = argparse.ArgumentParser(description='Format SWIG interface files.') + parser.add_argument('file', help='The SWIG interface file to format') + parser.add_argument('-i', '--in-place', action='store_true', + help='Modify the file in place (creates a .bak backup by default)') + parser.add_argument('--no-backup', action='store_true', + help='Do not create backup files when using --in-place') + parser.add_argument('--debug', action='store_true', + help='Print debug information') + + args = parser.parse_args() + + try: + formatted = format_swig_file( + args.file, + in_place=args.in_place, + create_backup=not args.no_backup, + debug=args.debug + ) + if args.in_place: + print(f"Formatted {args.file}") + else: + print(formatted) + except Exception as e: + print(f"Error formatting file: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == '__main__': + main() From 112ecd6523ce8e388b52851af9095d0c5e5f1b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thies=20M=C3=B6ller?= Date: Mon, 3 Feb 2025 00:19:36 +0100 Subject: [PATCH 3/4] Swig 4.3 update --- scripts/build/Dockerfile.debian | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/Dockerfile.debian b/scripts/build/Dockerfile.debian index c37aa8c..a181cda 100644 --- a/scripts/build/Dockerfile.debian +++ b/scripts/build/Dockerfile.debian @@ -21,7 +21,7 @@ RUN if cat /etc/debian_version | grep -q "8\." ; then \ RUN pip install wheel 'auditwheel<=5.1.2' # install swig from pypi -RUN pip install "swig>=4.2,<4.3" +RUN pip install "swig==4.3" # install setuptools RUN pip install "setuptools<72" --upgrade From ba24c5f6f82533ef6c7525858c99a171f850d428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thies=20M=C3=B6ller?= Date: Mon, 3 Feb 2025 00:20:21 +0100 Subject: [PATCH 4/4] Swig 4.3 --- scripts/build/Dockerfile.manylinux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/Dockerfile.manylinux b/scripts/build/Dockerfile.manylinux index caf04b6..e90cd61 100644 --- a/scripts/build/Dockerfile.manylinux +++ b/scripts/build/Dockerfile.manylinux @@ -9,7 +9,7 @@ COPY --from=qemu /usr/bin/* /usr/bin/ # install pip from pypi -RUN pip install "swig>=4.2,<4.3" +RUN pip install "swig==4.3" # install setuptools RUN pip install "setuptools<72" --upgrade