From af0f31242f8fc41ebf396142d13c7bef84d3fad5 Mon Sep 17 00:00:00 2001 From: nrontsis Date: Wed, 4 Oct 2023 15:28:22 +0300 Subject: [PATCH] Create python package and publish wheels. --- .github/workflows/buildwheels.yml | 58 ++++++++++++ pyiec61850/CMakeLists.txt | 19 +--- pyiec61850/__init__.py | 1 + pyiec61850/examples/dispServerStruct.py | 2 +- pyiec61850/examples/rcbSubscriptionExample.py | 2 +- pyiec61850/iec61850.i | 2 +- pyiec61850/test_pyiec61850.py | 2 +- pyiec61850/tutorial.md | 2 +- setup.py | 91 +++++++++++++++++++ 9 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/buildwheels.yml create mode 100644 pyiec61850/__init__.py create mode 100644 setup.py diff --git a/.github/workflows/buildwheels.yml b/.github/workflows/buildwheels.yml new file mode 100644 index 00000000..38061eb9 --- /dev/null +++ b/.github/workflows/buildwheels.yml @@ -0,0 +1,58 @@ +name: Build + +on: + push: + tags: + - 'python-release-*' + + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04] + + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.2 + env: + # CIBW_SKIP: cp36-* # Exclude + CIBW_BUILD: cp*manylinux* + # CIBW_BUILD_VERBOSITY: 1 # To debug issues + CIBW_ARCHS_LINUX: auto aarch64 + + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + upload_pypi: + needs: [build_wheels] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + # if: github.event_name == 'release' && github.event.action == 'published' + # or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this) + # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v3 + with: + # unpacks default artifact into dist/ + # if `name: artifact` is omitted, the action will create extra parent dir + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + verify-metadata: false diff --git a/pyiec61850/CMakeLists.txt b/pyiec61850/CMakeLists.txt index 159ef4bc..2493140c 100644 --- a/pyiec61850/CMakeLists.txt +++ b/pyiec61850/CMakeLists.txt @@ -2,13 +2,11 @@ # are not available in CMake versions earlier than 3.8 # cmake_minimum_required(VERSION 3.8) +# We use explicitly passed PYTHON_INCLUDE_DIRS/PYTHON_EXECUTABLE because find_package finds the wrong python dist find_package(SWIG REQUIRED) include(${SWIG_USE_FILE}) -find_package(PythonInterp ${BUILD_PYTHON_VERSION} REQUIRED) -find_package(PythonLibs ${PYTHON_VERSION_STRING} EXACT REQUIRED) - -include_directories(${PYTHON_INCLUDE_PATH}) +include_directories(${PYTHON_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) set(CMAKE_SWIG_FLAGS "") @@ -29,16 +27,5 @@ else() ) endif() -swig_link_libraries(iec61850 ${PYTHON_LIBRARIES} ${LIBS}) - -# Finding python modules install path -execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c - "from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib())" - OUTPUT_VARIABLE PYTHON_SITE_DIR -) - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/iec61850.py DESTINATION ${PYTHON_SITE_DIR}) -install(TARGETS _iec61850 LIBRARY DESTINATION ${PYTHON_SITE_DIR}) - +swig_link_libraries(iec61850 ${LIBS}) # Note: not linking against python https://github.com/pypa/cibuildwheel/issues/727#issuecomment-866278052 add_test(test_pyiec61850 ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/test_pyiec61850.py) diff --git a/pyiec61850/__init__.py b/pyiec61850/__init__.py new file mode 100644 index 00000000..8833102a --- /dev/null +++ b/pyiec61850/__init__.py @@ -0,0 +1 @@ +from .iec61850 import * \ No newline at end of file diff --git a/pyiec61850/examples/dispServerStruct.py b/pyiec61850/examples/dispServerStruct.py index aea02b4f..2b49479b 100755 --- a/pyiec61850/examples/dispServerStruct.py +++ b/pyiec61850/examples/dispServerStruct.py @@ -1,6 +1,6 @@ #!/usr/bin/python import os,sys -import iec61850 +import pyiec61850 as iec61850 if __name__=="__main__": hostname = "localhost"; tcpPort = 102 diff --git a/pyiec61850/examples/rcbSubscriptionExample.py b/pyiec61850/examples/rcbSubscriptionExample.py index d4d05aa5..7d1243be 100644 --- a/pyiec61850/examples/rcbSubscriptionExample.py +++ b/pyiec61850/examples/rcbSubscriptionExample.py @@ -27,7 +27,7 @@ The user needs to: import time import sys -import iec61850 +import pyiec61850 as iec61850 def open_connection(ip_address, mms_port): diff --git a/pyiec61850/iec61850.i b/pyiec61850/iec61850.i index 254fec64..6e746be2 100644 --- a/pyiec61850/iec61850.i +++ b/pyiec61850/iec61850.i @@ -111,7 +111,7 @@ void GooseSubscriber_setDstMac(GooseSubscriber subscriber, #include "eventHandlers/gooseHandler.hpp" #include "eventHandlers/commandTermHandler.hpp" #include "eventHandlers/controlActionHandler.hpp" -std::map< std::string, EventSubscriber*> EventSubscriber::m_subscriber_map = {}; +std::map< std::string, EventSubscriber*> EventSubscriber::m_subscriber_map; %} %include "eventHandlers/eventHandler.hpp" diff --git a/pyiec61850/test_pyiec61850.py b/pyiec61850/test_pyiec61850.py index aceb3fdb..6f6d8cdd 100755 --- a/pyiec61850/test_pyiec61850.py +++ b/pyiec61850/test_pyiec61850.py @@ -6,7 +6,7 @@ import traceback import signal import sys sys.path.append('.') -import iec61850 +import pyiec61850 as iec61850 def signal_handler(signal, frame): global running running =0 diff --git a/pyiec61850/tutorial.md b/pyiec61850/tutorial.md index ce766cab..b6965f5d 100644 --- a/pyiec61850/tutorial.md +++ b/pyiec61850/tutorial.md @@ -8,7 +8,7 @@ Then compile the library and install it. CMake and swig will automatically detec pyiec61850 library is to be imported calling ```python -import iec61850 +import pyiec61850 as iec61850 ``` # Client tutorial diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..b6d1cf39 --- /dev/null +++ b/setup.py @@ -0,0 +1,91 @@ +import os +import shutil +import subprocess +import sys +import sysconfig + +from setuptools import Extension, find_packages, setup +from setuptools.command.build_ext import build_ext +from setuptools.command.build_py import build_py as _build_py + + +class CMakeExtension(Extension): + def __init__(self, name): + Extension.__init__(self, name, sources=["."]) + self.sourcedir = os.path.abspath(".") + + +class CMakeBuild(build_ext): + def run(self): + try: + subprocess.check_output(["cmake", "--version"]) + except OSError: + raise RuntimeError( + "CMake must be installed to build the following extensions: " + + ", ".join(e.name for e in self.extensions) + ) + + for ext in self.extensions: + self.build_extension(ext) + + def build_extension(self, ext): + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + cfg = "Debug" if self.debug else "Release" + + cmake_args = [ + "-DPYTHON_EXECUTABLE=" + sys.executable, + "-DPYTHON_INCLUDE_DIRS=" + sysconfig.get_paths()["include"], + "-DBUILD_PYTHON_BINDINGS=ON", + "-DCMAKE_BUILD_TYPE=" + cfg, + ] + + build_args = ["--config", cfg] + env = os.environ.copy() + env["CXXFLAGS"] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get("CXXFLAGS", ""), self.distribution.get_version()) + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + if not os.path.exists(extdir): + os.makedirs(extdir) + + subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) + subprocess.check_call(["cmake", "--build", "."] + build_args, cwd=self.build_temp) + + source_dir = os.path.join(self.build_temp, "pyiec61850") + for file in os.listdir(source_dir): + if file == "iec61850.py" or "_iec61850." in file: + shutil.copy(os.path.join(source_dir, file), extdir) + + +class build_py(_build_py): + # See github.com/yanqd0/swig-python-demo + # Run first build_ext, as Swig will generate new python files that will have to be detected and included + def run(self): + self.run_command("build_ext") + return super().run() + + +long_description = """ +============ +pyiec61850 +============ + +Compiled, packaged python bindings of https://github.com/mz-automation/libiec61850. + +Wheels are built with the CI of the fork https://github.com/nrontsis/libiec61850. + +License same as mz-automation/libiec61850. + +Use at your own risk. +""" + +setup( + name="pyiec61850", + version="1.5.2a1", + packages=find_packages(), + description="Python bindings of https://github.com/mz-automation/libiec61850.", + long_description=long_description, + ext_modules=[CMakeExtension("pyiec61850/_iec61850")], + cmdclass={"build_py": build_py, "build_ext": CMakeBuild}, + zip_safe=False, + install_requires=[], +)