From 34c04f43a3979f2211ee4afaf3b5ceb22c2d5606 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Fri, 23 Aug 2024 13:30:14 +0200 Subject: [PATCH 01/34] Added cibuildwheel configuration --- CMakeLists.txt | 27 ++++--- pre_build_manylinux.sh | 178 +++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 61 +++++++++++++- 3 files changed, 255 insertions(+), 11 deletions(-) create mode 100644 pre_build_manylinux.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index c8e6321..a85601c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,11 @@ include_directories(${CMAKE_SOURCE_DIR}/include) file(GLOB_RECURSE headers "${CMAKE_CURRENT_SOURCE_DIR}/include/*") file(GLOB_RECURSE sources "${CMAKE_CURRENT_SOURCE_DIR}/src/*") -add_library(${PROJECT_NAME} SHARED ${headers} ${sources}) +if(SKBUILD) + python_add_library(${PROJECT_NAME} MODULE ${headers} ${sources}} WITH_SOABI) +else() + add_library(${PROJECT_NAME} SHARED ${headers} ${sources}) +endif() target_link_libraries(${PROJECT_NAME} PRIVATE ChimeraTK::ChimeraTK-DeviceAccess @@ -110,13 +114,18 @@ endif() # ==============================================================================# -# install Python modules to correct platform-dependent directory (if installing to system prefix) -if("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr" OR "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") - set(install_path ${Python_SITEARCH}) +if(SKBUILD) + install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${SKBUILD_PROJECT_NAME}) + install(FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION ${SKBUILD_PROJECT_NAME}) + install(TARGETS ${PROJECT_NAME} DESTINATION ${SKBUILD_PROJECT_NAME}) else() - set(install_path "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") + # install Python modules to correct platform-dependent directory (if installing to system prefix) + if("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr" OR "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") + set(install_path ${Python_SITEARCH}) + else() + set(install_path "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") + endif() + install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${install_path}) + install(FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION ${install_path}) + install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${install_path}) endif() - -install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${install_path}) -install(FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION ${install_path}) -install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${install_path}) diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh new file mode 100644 index 0000000..df0c431 --- /dev/null +++ b/pre_build_manylinux.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash + +# For manylinux_2_28 + +export PROJECT_PWD=`pwd` +export PROCS=`nproc` + +# Creating local build directories +export BUILD_DIR="${HOME}/build" +rm -rf ${BUILD_DIR} +mkdir ${BUILD_DIR} +cd ${BUILD_DIR} +mkdir ${BUILD_DIR}/local +mkdir ${BUILD_DIR}/local/bin +mkdir ${BUILD_DIR}/local/include +mkdir ${BUILD_DIR}/local/lib + +# Helper function to build meson-based projects +build_ninja () { + cd $1 + meson setup build --buildtype=release --prefix=${BUILD_DIR}/local/ --libdir=lib --includedir=lib/include + ninja -j${PROCS} -C build + ninja install -C build + cd ${BUILD_DIR} +} + +# Helper function to build cmake-based projects +build_cmake () { + cd $1 + mkdir builddir + cd builddir/ + cmake -DCMAKE_BUILD_TYPE=Release .. + make -j${PROCS} + make install + cd ${BUILD_DIR} +} + +# Install required system packages +dnf install -y epel-release +dnf update -y +dnf upgrade -y +dnf groupinstall -y "Development Tools" +dnf -y install clang zeromq-devel readline-devel opencv-devel openldap-devel wget libssh2-devel boost boost-devel libxml++-devel doxygen patchelf perl git libmodbus-devel rsync cmake gcc-toolset-12-libatomic-devel + +pipx install meson ninja sphinx + +# Dependencies versions +export YAJL_VERSION="2.1.0" +export EPICS_VERSION="R7.0.8" +export CHIMERATK_EPICS_VERSION="01.00.00" +export OPEN62541_VERSION="v1.3.1" +export LIBTIRPC_VERSION="1.3.4" +export TINE_VERSION="08ca83228932c3b08caf3b4c6578f1da8e65f5c5" +export DOOCS_VERSION="DOOCSVERSION_24_3_1" +export DOOCS_SERVER_TEST_HELPER_VERSION="01.07.00" +export CPPEXT_VERSION="01.05.02" +export EXPRTK_VERSION="01.04.01" +export NLOHMANN_JSON_VERSION="v3.7.3" +export DEVICEACCESS_VERSION="03.15.02" +export DEVICEACCESS_EPICS_VERSION="01.00.00" +export DEVICEACCESS_OPCUA_VERSION="01.03.00" +export DEVICEACCESS_DOOCS_VERSION="01.09.01" +export DEVICEACCESS_MODBUS_VERSION="01.05.00" + +# Add the build directory to the environment variables +export PKG_CONFIG_PATH="${BUILD_DIR}/local/lib/pkgconfig/:${BUILD_DIR}/EPICS/epics-base/lib/pkgconfig/:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" +export LD_LIBRARY_PATH="${BUILD_DIR}/local/lib/:${BUILD_DIR}/EPICS/epics-base/lib/linux-x86_64/:${LD_LIBRARY_PATH}" +export PATH="${BUILD_DIR}/local/bin/:${PATH}" + +# Install EPICS +mkdir ${BUILD_DIR}/EPICS +cd ${BUILD_DIR}/EPICS +git clone --recursive --depth 1 --branch ${EPICS_VERSION} https://github.com/epics-base/epics-base.git +cd epics-base/ +make -j${PROCS} +export EPICS_BASE=${BUILD_DIR}/EPICS/epics-base +export EPICS_HOST_ARCH=$(${EPICS_BASE}/startup/EpicsHostArch) +export PATH=${EPICS_BASE}/bin/${EPICS_HOST_ARCH}:${PATH} +cd ${BUILD_DIR} + +# Install libtirpc-1.3 +wget https://downloads.sourceforge.net/libtirpc/libtirpc-${LIBTIRPC_VERSION}.tar.bz2 +tar -xvf libtirpc-${LIBTIRPC_VERSION}.tar.bz2 +cd libtirpc-${LIBTIRPC_VERSION}/ +./configure --prefix=${BUILD_DIR}/local +make -j${PROCS} +make install +cd ${BUILD_DIR} + +# Install TINE +git clone --recursive http://doocs-git.desy.de/cgit/vendor/desy/mcs/tine/tine-package.git +cd tine-package/ +git checkout ${TINE_VERSION} +cd doocs/ +./prepare +cd build.LINUX/ +make -j${PROCS} +make install + +# Ugly workaround for cp errors +cp ./src/*.so /usr/local/lib/ +cd ${BUILD_DIR} + +# Install GUL +git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/gul.git +build_ninja gul + +# Install DOOCS clientlib +export ENSHOST=ldap://xfelens1.desy.de +git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/clientlib.git +build_ninja clientlib + +# Install DOOCS serverlib +git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/serverlib.git +build_ninja serverlib + +# Install DOOCSServerTestHelper +git clone --recursive --depth 1 --branch ${DOOCS_SERVER_TEST_HELPER_VERSION} https://github.com/ChimeraTK/DoocsServerTestHelper.git +build_cmake DoocsServerTestHelper + +#Install ChimeraTK-EPICS +git clone --recursive --depth 1 --branch ${CHIMERATK_EPICS_VERSION} https://github.com/ChimeraTK/EPICS-Interface.git +cd EPICS-Interface/ +mkdir builddir +cd builddir/ +cmake -DCMAKE_BUILD_TYPE=Release -DEPICS_VERSION=7 .. +make -j${PROCS} +make install +cd ${BUILD_DIR} + +# Install yajl +git clone --recursive --depth 1 --branch ${YAJL_VERSION} https://github.com/lloyd/yajl.git +build_cmake yajl + +#Install open62541 +git clone --recursive --depth 1 --branch ${OPEN62541_VERSION} https://github.com/open62541/open62541.git +build_cmake open62541 + +# Install cppext +git clone --recursive --depth 1 --branch ${CPPEXT_VERSION} https://github.com/ChimeraTK/cppext.git +build_cmake cppext + +# Install exprtk +git clone --recursive --depth 1 --branch ${EXPRTK_VERSION} https://github.com/ChimeraTK/exprtk-interface.git +build_cmake exprtk-interface + +# Install nlohmann-json +git clone --recursive --depth 1 --branch ${NLOHMANN_JSON_VERSION} https://github.com/nlohmann/json.git +build_cmake json + +# Install ChimeraTK-DeviceAccess +git clone --recursive --depth 1 --branch ${DEVICEACCESS_VERSION} https://github.com/ChimeraTK/DeviceAccess.git +build_cmake DeviceAccess + +# Install ChimeraTK-DeviceAccess-EpicsBackend +git clone --recursive --depth 1 --branch ${DEVICEACCESS_EPICS_VERSION} https://github.com/ChimeraTK/DeviceAccess-EpicsBackend.git +build_cmake DeviceAccess-EpicsBackend + +# Install ChimeraTK-DeviceAccess-OpcUaBackend +git clone --recursive --depth 1 --branch ${DEVICEACCESS_OPCUA_VERSION} https://github.com/ChimeraTK/DeviceAccess-OpcUaBackend.git +cd DeviceAccess-OpcUaBackend +mkdir builddir +cd builddir/ +cmake -DCMAKE_BUILD_TYPE=Release .. +make -j${PROCS} ChimeraTK-DeviceAccess-OPC-UA-Backend +make install/fast +cd ${BUILD_DIR} + +# Install ChimeraTK-DeviceAccess-DoocsBackend +git clone --recursive --depth 1 --branch ${DEVICEACCESS_DOOCS_VERSION} https://github.com/ChimeraTK/DeviceAccess-DoocsBackend.git +build_cmake DeviceAccess-DoocsBackend + +# Install ChimeraTK-DeviceAccess-ModbusBackend +git clone --recursive --depth 1 --branch ${DEVICEACCESS_MODBUS_VERSION} https://github.com/ChimeraTK/DeviceAccess-ModbusBackend.git +build_cmake DeviceAccess-ModbusBackend + +cd ${PROJECT_PWD} + diff --git a/pyproject.toml b/pyproject.toml index 6dffe81..da3b6cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,59 @@ -[tool.autopep8] -max_line_length = 120 \ No newline at end of file +[build-system] +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[project] +name = "chimeratk-deviceaccess" +version = "03.03.02" +dependencies = ["numpy"] +description = "Python bindings for the ChimeraTK's deviceaccess library" +long_description = "The package provides binding for the ChimeraTK's deviceaccess library" +requires-python = ">=3.8" +readme = "README.md" +license = {file = "LICENSE"} + +classifiers = [ +"Topic :: Software Development :: Libraries :: Python Modules", +"Development Status :: 5 - Production/Stable", +"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", +"Intended Audience :: Developers", "Programming Language :: Python :: 3", +"Programming Language :: Python :: 3", +"Programming Language :: Python :: 3.8", +"Programming Language :: Python :: 3.9", +"Programming Language :: Python :: 3.10", +"Programming Language :: Python :: 3.11", +"Programming Language :: Python :: 3.12", +"Programming Language :: Python :: 3.13", +"Programming Language :: Python :: Implementation :: CPython", +"Topic :: Scientific/Engineering" +] + +[project.urls] +Homepage = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" +Documentation = "https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html/" +Repository = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" + +[tool.scikit-build] +# Protect the configuration against future changes in scikit-build-core +minimum-version = "0.4" + +# Setuptools-style build caching in a local directory +build-dir = "build/{wheel_tag}" + +# Build stable ABI wheels for CPython 3.12+ +wheel.py-api = "cp312" + +[tool.cibuildwheel] +before-all = "bash pre_build_manylinux.sh" +skip = "cp36-* cp37-* *-musllinux* pp* *-win32" +manylinux-x86_64-image = "manylinux_2_28" +manylinux-i686-image = "manylinux2_2_28" +manylinux-aarch64-image = "manylinux2_2_28" + +# Necessary to see build output from the actual compilation +build-verbosity = 1 + +## Run pytest to ensure that the package was correctly built + +#test-command = "python3 -m unittest discover tests" + From 7ac2b58d9944b096f39ec90068089a92d643ffd6 Mon Sep 17 00:00:00 2001 From: bellandi Date: Fri, 23 Aug 2024 14:38:01 +0200 Subject: [PATCH 02/34] working pyproject.toml --- CMakeLists.txt | 4 ++-- deviceaccess.py | 10 ++++++++-- mtca4u.py | 6 +++++- pyproject.toml | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a85601c..63fed07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ file(GLOB_RECURSE headers "${CMAKE_CURRENT_SOURCE_DIR}/include/*") file(GLOB_RECURSE sources "${CMAKE_CURRENT_SOURCE_DIR}/src/*") if(SKBUILD) - python_add_library(${PROJECT_NAME} MODULE ${headers} ${sources}} WITH_SOABI) + python_add_library(${PROJECT_NAME} MODULE ${headers} ${sources} WITH_SOABI) else() add_library(${PROJECT_NAME} SHARED ${headers} ${sources}) endif() @@ -115,7 +115,7 @@ endif() # ==============================================================================# if(SKBUILD) - install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${SKBUILD_PROJECT_NAME}) + install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${SKBUILD_PROJECT_NAME} RENAME __init__.py) install(FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION ${SKBUILD_PROJECT_NAME}) install(TARGETS ${PROJECT_NAME} DESTINATION ${SKBUILD_PROJECT_NAME}) else() diff --git a/deviceaccess.py b/deviceaccess.py index f3f54cd..3759c9a 100644 --- a/deviceaccess.py +++ b/deviceaccess.py @@ -16,9 +16,15 @@ from __future__ import annotations from typing import Sequence, Union -import _da_python_bindings as pb import numpy as np -from _da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType + +try: + import _da_python_bindings as pb + from _da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType +except ModuleNotFoundError: + from . import _da_python_bindings as pb + from ._da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType + import abc import functools diff --git a/mtca4u.py b/mtca4u.py index 3d8e4eb..608ad21 100644 --- a/mtca4u.py +++ b/mtca4u.py @@ -1,7 +1,11 @@ # SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project # SPDX-License-Identifier: LGPL-3.0-or-later -import _da_python_bindings as mtca4udeviceaccess # alias quick fix +try: + import _da_python_bindings as mtca4udeviceaccess # alias quick fix +except ModuleNotFoundError: + from . import _da_python_bindings as mtca4udeviceaccess # alias quick fix + import numpy import sys diff --git a/pyproject.toml b/pyproject.toml index da3b6cc..8c4aeb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,9 @@ [build-system] -requires = ["scikit-build-core"] +requires = ["scikit-build-core", "numpy"] build-backend = "scikit_build_core.build" [project] -name = "chimeratk-deviceaccess" +name = "deviceaccess" version = "03.03.02" dependencies = ["numpy"] description = "Python bindings for the ChimeraTK's deviceaccess library" From 6bd1c69bda6596ccbcd775ab2e3476da5739c290 Mon Sep 17 00:00:00 2001 From: bellandi Date: Fri, 23 Aug 2024 15:04:49 +0200 Subject: [PATCH 03/34] restructured mtca4u --- CMakeLists.txt | 5 ++-- deviceaccess.py | 6 ++--- mtca4u.py => mtca4u/mtca4u/__init__.py | 4 +-- mtca4u/pyproject.toml | 35 ++++++++++++++++++++++++++ pyproject.toml | 2 +- 5 files changed, 43 insertions(+), 9 deletions(-) rename mtca4u.py => mtca4u/mtca4u/__init__.py (99%) create mode 100644 mtca4u/pyproject.toml diff --git a/CMakeLists.txt b/CMakeLists.txt index 63fed07..c168306 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,8 +65,8 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" OUTPUT_NAME "_da_python_bindings") # Copy Python part to build directory (required for tests to run) -file(COPY mtca4u.py DESTINATION ${PROJECT_BINARY_DIR}) -file(COPY deviceaccess.py DESTINATION ${PROJECT_BINARY_DIR}) +file(COPY ${PROJECT_SOURCE_DIR}/mtca4u/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR} RENAME mtca4u.py) +file(COPY ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${PROJECT_BINARY_DIR}) # ==============================================================================# ENABLE_TESTING() @@ -116,7 +116,6 @@ endif() if(SKBUILD) install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${SKBUILD_PROJECT_NAME} RENAME __init__.py) - install(FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION ${SKBUILD_PROJECT_NAME}) install(TARGETS ${PROJECT_NAME} DESTINATION ${SKBUILD_PROJECT_NAME}) else() # install Python modules to correct platform-dependent directory (if installing to system prefix) diff --git a/deviceaccess.py b/deviceaccess.py index 3759c9a..e0b5a5a 100644 --- a/deviceaccess.py +++ b/deviceaccess.py @@ -19,11 +19,11 @@ import numpy as np try: - import _da_python_bindings as pb - from _da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType -except ModuleNotFoundError: from . import _da_python_bindings as pb from ._da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType +except ModuleNotFoundError: + import _da_python_bindings as pb + from _da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType import abc import functools diff --git a/mtca4u.py b/mtca4u/mtca4u/__init__.py similarity index 99% rename from mtca4u.py rename to mtca4u/mtca4u/__init__.py index 608ad21..0f14048 100644 --- a/mtca4u.py +++ b/mtca4u/mtca4u/__init__.py @@ -2,9 +2,9 @@ # SPDX-License-Identifier: LGPL-3.0-or-later try: - import _da_python_bindings as mtca4udeviceaccess # alias quick fix + from deviceaccess import _da_python_bindings as mtca4udeviceaccess except ModuleNotFoundError: - from . import _da_python_bindings as mtca4udeviceaccess # alias quick fix + import _da_python_bindings as mtca4udeviceaccess # alias quick fix import numpy import sys diff --git a/mtca4u/pyproject.toml b/mtca4u/pyproject.toml new file mode 100644 index 0000000..cd5c338 --- /dev/null +++ b/mtca4u/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "mtca4u" +dynamic = ["version"] +dependencies = ["deviceaccess >= 03.03.02"] +description = "Legacy ChimeraTK access library" +long_description = "The package provides binding for the ChimeraTK's mtca4u library" +requires-python = ">=3.8" +readme = "../README.md" +license = {file = "../LICENSE"} + +classifiers = [ +"Topic :: Software Development :: Libraries :: Python Modules", +"Development Status :: 5 - Production/Stable", +"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", +"Intended Audience :: Developers", +"Programming Language :: Python :: 3", +"Programming Language :: Python :: 3.8", +"Programming Language :: Python :: 3.9", +"Programming Language :: Python :: 3.10", +"Programming Language :: Python :: 3.11", +"Programming Language :: Python :: 3.12", +"Programming Language :: Python :: 3.13", +"Programming Language :: Python :: Implementation :: CPython", +"Topic :: Scientific/Engineering" +] + +[project.urls] +Homepage = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" +Documentation = "https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html/" +Repository = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings/mtca4u" + diff --git a/pyproject.toml b/pyproject.toml index 8c4aeb1..0771c4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", -"Intended Audience :: Developers", "Programming Language :: Python :: 3", +"Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", From b11281d280275e4aff8d56fd856878e68cc776c3 Mon Sep 17 00:00:00 2001 From: bellandi Date: Fri, 23 Aug 2024 15:16:18 +0200 Subject: [PATCH 04/34] working pyproject for mtca4u package --- CMakeLists.txt | 2 +- mtca4u/mtca4u/__init__.py | 2 +- mtca4u/pyproject.toml | 10 +++++----- pyproject.toml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c168306..a61be91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" OUTPUT_NAME "_da_python_bindings") # Copy Python part to build directory (required for tests to run) -file(COPY ${PROJECT_SOURCE_DIR}/mtca4u/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR} RENAME mtca4u.py) +file(COPY ${PROJECT_SOURCE_DIR}/mtca4u/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/mtca4u.py) file(COPY ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${PROJECT_BINARY_DIR}) # ==============================================================================# diff --git a/mtca4u/mtca4u/__init__.py b/mtca4u/mtca4u/__init__.py index 0f14048..5ccf142 100644 --- a/mtca4u/mtca4u/__init__.py +++ b/mtca4u/mtca4u/__init__.py @@ -9,7 +9,7 @@ import numpy import sys -__version__ = "02.03.00" +__version__ = "2.3.0" # http://stackoverflow.com/questions/4219717/how-to-assert-output-with-nosetest-unittest-in-python diff --git a/mtca4u/pyproject.toml b/mtca4u/pyproject.toml index cd5c338..9051805 100644 --- a/mtca4u/pyproject.toml +++ b/mtca4u/pyproject.toml @@ -1,13 +1,11 @@ [build-system] -requires = ["setuptools", "setuptools-scm"] +requires = ["setuptools>=46.4.0", "setuptools-scm"] build-backend = "setuptools.build_meta" [project] name = "mtca4u" -dynamic = ["version"] -dependencies = ["deviceaccess >= 03.03.02"] -description = "Legacy ChimeraTK access library" -long_description = "The package provides binding for the ChimeraTK's mtca4u library" +dependencies = ["deviceaccess >= 3.3.2"] +description = "Legacy ChimeraTK mtca4u access library" requires-python = ">=3.8" readme = "../README.md" license = {file = "../LICENSE"} @@ -33,3 +31,5 @@ Homepage = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" Documentation = "https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html/" Repository = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings/mtca4u" +[metadata] +version = attr: package.__version__ diff --git a/pyproject.toml b/pyproject.toml index 0771c4c..d3d7cea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "deviceaccess" -version = "03.03.02" +version = "3.3.2" dependencies = ["numpy"] description = "Python bindings for the ChimeraTK's deviceaccess library" long_description = "The package provides binding for the ChimeraTK's deviceaccess library" From a660e07b63c4c706f934642340dc375264940a39 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Fri, 23 Aug 2024 16:52:41 +0200 Subject: [PATCH 05/34] updates --- CMakeLists.txt | 6 ++++-- pre_build_manylinux.sh | 6 ------ pyproject.toml | 8 ++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) mode change 100644 => 100755 pre_build_manylinux.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index a61be91..b931192 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,8 +65,10 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" OUTPUT_NAME "_da_python_bindings") # Copy Python part to build directory (required for tests to run) -file(COPY ${PROJECT_SOURCE_DIR}/mtca4u/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/mtca4u.py) -file(COPY ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${PROJECT_BINARY_DIR}) +if(NOT SKBUILD) + file(COPY ${PROJECT_SOURCE_DIR}/mtca4u/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/mtca4u.py) + file(COPY ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${PROJECT_BINARY_DIR}) +endif() # ==============================================================================# ENABLE_TESTING() diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh old mode 100644 new mode 100755 index df0c431..3ec0e51 --- a/pre_build_manylinux.sh +++ b/pre_build_manylinux.sh @@ -6,7 +6,6 @@ export PROJECT_PWD=`pwd` export PROCS=`nproc` # Creating local build directories -export BUILD_DIR="${HOME}/build" rm -rf ${BUILD_DIR} mkdir ${BUILD_DIR} cd ${BUILD_DIR} @@ -62,11 +61,6 @@ export DEVICEACCESS_OPCUA_VERSION="01.03.00" export DEVICEACCESS_DOOCS_VERSION="01.09.01" export DEVICEACCESS_MODBUS_VERSION="01.05.00" -# Add the build directory to the environment variables -export PKG_CONFIG_PATH="${BUILD_DIR}/local/lib/pkgconfig/:${BUILD_DIR}/EPICS/epics-base/lib/pkgconfig/:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" -export LD_LIBRARY_PATH="${BUILD_DIR}/local/lib/:${BUILD_DIR}/EPICS/epics-base/lib/linux-x86_64/:${LD_LIBRARY_PATH}" -export PATH="${BUILD_DIR}/local/bin/:${PATH}" - # Install EPICS mkdir ${BUILD_DIR}/EPICS cd ${BUILD_DIR}/EPICS diff --git a/pyproject.toml b/pyproject.toml index d3d7cea..3dfe739 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,4 +56,12 @@ build-verbosity = 1 ## Run pytest to ensure that the package was correctly built #test-command = "python3 -m unittest discover tests" +test-skip = "*" # Do not test at the moment + +# Add the build directory to the environment variables +[tool.cibuildwheel.linux.environment] +BUILD_DIR="${HOME}/build" +PKG_CONFIG_PATH="${BUILD_DIR}/local/lib/pkgconfig/:${BUILD_DIR}/EPICS/epics-base/lib/pkgconfig/:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" +LD_LIBRARY_PATH="${BUILD_DIR}/local/lib/:${BUILD_DIR}/EPICS/epics-base/lib/linux-x86_64/:${LD_LIBRARY_PATH}" +PATH="${BUILD_DIR}/local/bin/:${PATH}" From ab7d3a625b5d3cc24cbeb537352f2c4ceca71a5f Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Fri, 23 Aug 2024 17:03:22 +0200 Subject: [PATCH 06/34] Added switch for SKBUILD findpython --- CMakeLists.txt | 6 +++++- pyproject.toml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b931192..6636733 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,11 @@ include(cmake/enable_code_style_check.cmake) # Find dependencies find_package(ChimeraTK-DeviceAccess 03.14 REQUIRED) -find_package(Python 3 REQUIRED COMPONENTS Interpreter Development NumPy) +if(SKBUILD) + find_package(Python REQUIRED COMPONENTS Interpreter Development.Module Numpy) +else() + find_package(Python 3 REQUIRED COMPONENTS Interpreter Development NumPy) +endif() # Convert Python version into the format we need for BOOST component names set(python_version_no_dot "${Python_VERSION_MAJOR}${Python_VERSION_MINOR}") diff --git a/pyproject.toml b/pyproject.toml index 3dfe739..7da2aa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ wheel.py-api = "cp312" [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" +before-build = "pip install numpy" skip = "cp36-* cp37-* *-musllinux* pp* *-win32" manylinux-x86_64-image = "manylinux_2_28" manylinux-i686-image = "manylinux2_2_28" From df991d42567038a9e0be668090a9c9847461dbfa Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sun, 25 Aug 2024 15:43:13 +0200 Subject: [PATCH 07/34] working cibuildwheel configuration --- CMakeLists.txt | 38 +--- README.md | 2 +- deviceaccess.py => deviceaccess_/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 69725 bytes mtca4u_/LICENSE | 165 ++++++++++++++ mtca4u_/README.md | 2 + {mtca4u => mtca4u_}/mtca4u/__init__.py | 0 {mtca4u => mtca4u_}/pyproject.toml | 11 +- pre_build_manylinux.sh | 214 ++++++++---------- pyproject.toml | 41 +++- 10 files changed, 318 insertions(+), 155 deletions(-) rename deviceaccess.py => deviceaccess_/__init__.py (100%) create mode 100644 deviceaccess_/__pycache__/__init__.cpython-311.pyc create mode 100644 mtca4u_/LICENSE create mode 100644 mtca4u_/README.md rename {mtca4u => mtca4u_}/mtca4u/__init__.py (100%) rename {mtca4u => mtca4u_}/pyproject.toml (85%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6636733..3bae54d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,24 +11,14 @@ include(cmake/set_version_numbers.cmake) # set default compiler flags (C++) include(cmake/set_default_build_to_release.cmake) include(cmake/set_default_flags.cmake) - include(cmake/enable_code_style_check.cmake) # ==============================================================================# # Find dependencies find_package(ChimeraTK-DeviceAccess 03.14 REQUIRED) - -if(SKBUILD) - find_package(Python REQUIRED COMPONENTS Interpreter Development.Module Numpy) -else() - find_package(Python 3 REQUIRED COMPONENTS Interpreter Development NumPy) -endif() - -# Convert Python version into the format we need for BOOST component names -set(python_version_no_dot "${Python_VERSION_MAJOR}${Python_VERSION_MINOR}") - -find_package(Boost REQUIRED COMPONENTS python${python_version_no_dot} numpy${python_version_no_dot}) +find_package(Python3 COMPONENTS Interpreter Development.Module NumPy REQUIRED) +find_package(Boost COMPONENTS python3 numpy3 REQUIRED) # ==============================================================================# @@ -48,19 +38,15 @@ include_directories(${CMAKE_SOURCE_DIR}/include) file(GLOB_RECURSE headers "${CMAKE_CURRENT_SOURCE_DIR}/include/*") file(GLOB_RECURSE sources "${CMAKE_CURRENT_SOURCE_DIR}/src/*") -if(SKBUILD) - python_add_library(${PROJECT_NAME} MODULE ${headers} ${sources} WITH_SOABI) -else() - add_library(${PROJECT_NAME} SHARED ${headers} ${sources}) -endif() +add_library(${PROJECT_NAME} SHARED ${headers} ${sources}) target_link_libraries(${PROJECT_NAME} PRIVATE ChimeraTK::ChimeraTK-DeviceAccess - PRIVATE Python::NumPy - PRIVATE Python::Module + PRIVATE Python3::NumPy + PRIVATE Python3::Module PRIVATE ${Boost_LIBRARIES} - PRIVATE Boost::python${python_version_no_dot} - PRIVATE Boost::numpy${python_version_no_dot}) + PRIVATE Boost::python3 + PRIVATE Boost::numpy3) # do not remove runtime path to libmtca-deviceaccess location from ${boost_python_core_module} when installing set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) @@ -70,8 +56,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" OUTPUT_NAME "_da_pyth # Copy Python part to build directory (required for tests to run) if(NOT SKBUILD) - file(COPY ${PROJECT_SOURCE_DIR}/mtca4u/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/mtca4u.py) - file(COPY ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${PROJECT_BINARY_DIR}) + file(COPY ${PROJECT_SOURCE_DIR}/deviceaccess_/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/deviceacces.py) + file(COPY ${PROJECT_SOURCE_DIR}/mtca4u_/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/mtca4u.py) endif() # ==============================================================================# @@ -121,7 +107,7 @@ endif() # ==============================================================================# if(SKBUILD) - install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${SKBUILD_PROJECT_NAME} RENAME __init__.py) + install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess_/__init__.py DESTINATION ${SKBUILD_PROJECT_NAME}) install(TARGETS ${PROJECT_NAME} DESTINATION ${SKBUILD_PROJECT_NAME}) else() # install Python modules to correct platform-dependent directory (if installing to system prefix) @@ -130,7 +116,7 @@ else() else() set(install_path "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") endif() - install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py DESTINATION ${install_path}) - install(FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION ${install_path}) + install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess_/__init__.py DESTINATION ${install_path} RENAME deviceaccess.py) + install(FILES ${PROJECT_SOURCE_DIR}/mtca4u_/mtca4u/__init__.py DESTINATION ${install_path} RENAME mtca4u.py) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${install_path}) endif() diff --git a/README.md b/README.md index 99a6da9..42102c0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# Bindings for ChimeraTK DeviceAcess Library +# Bindings for ChimeraTK DeviceAccess Library Find API documentation [here](https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html) diff --git a/deviceaccess.py b/deviceaccess_/__init__.py similarity index 100% rename from deviceaccess.py rename to deviceaccess_/__init__.py diff --git a/deviceaccess_/__pycache__/__init__.cpython-311.pyc b/deviceaccess_/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9821f24b74eeb1a5fe36930651e372765214096b GIT binary patch literal 69725 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/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 nncm#FM4v7I%a*To^`FP_If9YTNya@k) + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/mtca4u_/README.md b/mtca4u_/README.md new file mode 100644 index 0000000..fea5207 --- /dev/null +++ b/mtca4u_/README.md @@ -0,0 +1,2 @@ +# Legacy mtca4u bindings for ChimeraTK DeviceAccess Library +Find API documentation [here](https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html) diff --git a/mtca4u/mtca4u/__init__.py b/mtca4u_/mtca4u/__init__.py similarity index 100% rename from mtca4u/mtca4u/__init__.py rename to mtca4u_/mtca4u/__init__.py diff --git a/mtca4u/pyproject.toml b/mtca4u_/pyproject.toml similarity index 85% rename from mtca4u/pyproject.toml rename to mtca4u_/pyproject.toml index 9051805..0488a85 100644 --- a/mtca4u/pyproject.toml +++ b/mtca4u_/pyproject.toml @@ -4,17 +4,18 @@ build-backend = "setuptools.build_meta" [project] name = "mtca4u" -dependencies = ["deviceaccess >= 3.3.2"] +dependencies = ["deviceaccess"] description = "Legacy ChimeraTK mtca4u access library" requires-python = ">=3.8" -readme = "../README.md" -license = {file = "../LICENSE"} +readme = "README.md" +license = {file = "LICENSE"} +version = "2.3.0" classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", -"Intended Audience :: Developers", +"Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -31,5 +32,3 @@ Homepage = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" Documentation = "https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html/" Repository = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings/mtca4u" -[metadata] -version = attr: package.__version__ diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh index 3ec0e51..bbf8cd7 100755 --- a/pre_build_manylinux.sh +++ b/pre_build_manylinux.sh @@ -9,17 +9,16 @@ export PROCS=`nproc` rm -rf ${BUILD_DIR} mkdir ${BUILD_DIR} cd ${BUILD_DIR} -mkdir ${BUILD_DIR}/local -mkdir ${BUILD_DIR}/local/bin -mkdir ${BUILD_DIR}/local/include -mkdir ${BUILD_DIR}/local/lib +mkdir ${BUILD_DIR}/local/ +mkdir ${BUILD_DIR}/local/bin/ +mkdir ${BUILD_DIR}/local/include/ +mkdir ${BUILD_DIR}/local/lib/ # Helper function to build meson-based projects build_ninja () { cd $1 meson setup build --buildtype=release --prefix=${BUILD_DIR}/local/ --libdir=lib --includedir=lib/include - ninja -j${PROCS} -C build - ninja install -C build + ninja install -j${PROCS} -C build cd ${BUILD_DIR} } @@ -29,8 +28,7 @@ build_cmake () { mkdir builddir cd builddir/ cmake -DCMAKE_BUILD_TYPE=Release .. - make -j${PROCS} - make install + make install -j${PROCS} cd ${BUILD_DIR} } @@ -39,97 +37,10 @@ dnf install -y epel-release dnf update -y dnf upgrade -y dnf groupinstall -y "Development Tools" -dnf -y install clang zeromq-devel readline-devel opencv-devel openldap-devel wget libssh2-devel boost boost-devel libxml++-devel doxygen patchelf perl git libmodbus-devel rsync cmake gcc-toolset-12-libatomic-devel +dnf -y install clang zeromq-devel readline-devel openldap-devel wget libssh2-devel libxml++-devel doxygen patchelf perl git libmodbus-devel rsync cmake gcc-toolset-12-libatomic-devel boost1.78 boost1.78-devel boost1.78-python3-devel pipx install meson ninja sphinx -# Dependencies versions -export YAJL_VERSION="2.1.0" -export EPICS_VERSION="R7.0.8" -export CHIMERATK_EPICS_VERSION="01.00.00" -export OPEN62541_VERSION="v1.3.1" -export LIBTIRPC_VERSION="1.3.4" -export TINE_VERSION="08ca83228932c3b08caf3b4c6578f1da8e65f5c5" -export DOOCS_VERSION="DOOCSVERSION_24_3_1" -export DOOCS_SERVER_TEST_HELPER_VERSION="01.07.00" -export CPPEXT_VERSION="01.05.02" -export EXPRTK_VERSION="01.04.01" -export NLOHMANN_JSON_VERSION="v3.7.3" -export DEVICEACCESS_VERSION="03.15.02" -export DEVICEACCESS_EPICS_VERSION="01.00.00" -export DEVICEACCESS_OPCUA_VERSION="01.03.00" -export DEVICEACCESS_DOOCS_VERSION="01.09.01" -export DEVICEACCESS_MODBUS_VERSION="01.05.00" - -# Install EPICS -mkdir ${BUILD_DIR}/EPICS -cd ${BUILD_DIR}/EPICS -git clone --recursive --depth 1 --branch ${EPICS_VERSION} https://github.com/epics-base/epics-base.git -cd epics-base/ -make -j${PROCS} -export EPICS_BASE=${BUILD_DIR}/EPICS/epics-base -export EPICS_HOST_ARCH=$(${EPICS_BASE}/startup/EpicsHostArch) -export PATH=${EPICS_BASE}/bin/${EPICS_HOST_ARCH}:${PATH} -cd ${BUILD_DIR} - -# Install libtirpc-1.3 -wget https://downloads.sourceforge.net/libtirpc/libtirpc-${LIBTIRPC_VERSION}.tar.bz2 -tar -xvf libtirpc-${LIBTIRPC_VERSION}.tar.bz2 -cd libtirpc-${LIBTIRPC_VERSION}/ -./configure --prefix=${BUILD_DIR}/local -make -j${PROCS} -make install -cd ${BUILD_DIR} - -# Install TINE -git clone --recursive http://doocs-git.desy.de/cgit/vendor/desy/mcs/tine/tine-package.git -cd tine-package/ -git checkout ${TINE_VERSION} -cd doocs/ -./prepare -cd build.LINUX/ -make -j${PROCS} -make install - -# Ugly workaround for cp errors -cp ./src/*.so /usr/local/lib/ -cd ${BUILD_DIR} - -# Install GUL -git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/gul.git -build_ninja gul - -# Install DOOCS clientlib -export ENSHOST=ldap://xfelens1.desy.de -git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/clientlib.git -build_ninja clientlib - -# Install DOOCS serverlib -git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/serverlib.git -build_ninja serverlib - -# Install DOOCSServerTestHelper -git clone --recursive --depth 1 --branch ${DOOCS_SERVER_TEST_HELPER_VERSION} https://github.com/ChimeraTK/DoocsServerTestHelper.git -build_cmake DoocsServerTestHelper - -#Install ChimeraTK-EPICS -git clone --recursive --depth 1 --branch ${CHIMERATK_EPICS_VERSION} https://github.com/ChimeraTK/EPICS-Interface.git -cd EPICS-Interface/ -mkdir builddir -cd builddir/ -cmake -DCMAKE_BUILD_TYPE=Release -DEPICS_VERSION=7 .. -make -j${PROCS} -make install -cd ${BUILD_DIR} - -# Install yajl -git clone --recursive --depth 1 --branch ${YAJL_VERSION} https://github.com/lloyd/yajl.git -build_cmake yajl - -#Install open62541 -git clone --recursive --depth 1 --branch ${OPEN62541_VERSION} https://github.com/open62541/open62541.git -build_cmake open62541 - # Install cppext git clone --recursive --depth 1 --branch ${CPPEXT_VERSION} https://github.com/ChimeraTK/cppext.git build_cmake cppext @@ -146,27 +57,100 @@ build_cmake json git clone --recursive --depth 1 --branch ${DEVICEACCESS_VERSION} https://github.com/ChimeraTK/DeviceAccess.git build_cmake DeviceAccess -# Install ChimeraTK-DeviceAccess-EpicsBackend -git clone --recursive --depth 1 --branch ${DEVICEACCESS_EPICS_VERSION} https://github.com/ChimeraTK/DeviceAccess-EpicsBackend.git -build_cmake DeviceAccess-EpicsBackend - -# Install ChimeraTK-DeviceAccess-OpcUaBackend -git clone --recursive --depth 1 --branch ${DEVICEACCESS_OPCUA_VERSION} https://github.com/ChimeraTK/DeviceAccess-OpcUaBackend.git -cd DeviceAccess-OpcUaBackend -mkdir builddir -cd builddir/ -cmake -DCMAKE_BUILD_TYPE=Release .. -make -j${PROCS} ChimeraTK-DeviceAccess-OPC-UA-Backend -make install/fast -cd ${BUILD_DIR} +if [ $DEVICEACCESS_BACKEND_ENABLE != "0"] +then + + # Install EPICS + mkdir ${BUILD_DIR}/EPICS + cd ${BUILD_DIR}/EPICS + git clone --recursive --depth 1 --branch ${EPICS_VERSION} https://github.com/epics-base/epics-base.git + cd epics-base/ + make -j${PROCS} + export EPICS_BASE=${BUILD_DIR}/EPICS/epics-base + export EPICS_HOST_ARCH=$(${EPICS_BASE}/startup/EpicsHostArch) + export PATH=${EPICS_BASE}/bin/${EPICS_HOST_ARCH}:${PATH} + cd ${BUILD_DIR} + + # Install libtirpc-1.3 + wget https://downloads.sourceforge.net/libtirpc/libtirpc-${LIBTIRPC_VERSION}.tar.bz2 + tar -xvf libtirpc-${LIBTIRPC_VERSION}.tar.bz2 + cd libtirpc-${LIBTIRPC_VERSION}/ + ./configure --prefix=${BUILD_DIR}/local + make -j${PROCS} + make install + cd ${BUILD_DIR} + + # Install TINE + git clone --recursive http://doocs-git.desy.de/cgit/vendor/desy/mcs/tine/tine-package.git + cd tine-package/ + git checkout ${TINE_VERSION} + cd doocs/ + ./prepare + cd build.LINUX/ + make -j${PROCS} + make install + + # Ugly workaround for cp errors + cp ./src/*.so /usr/local/lib/ + cd ${BUILD_DIR} + + # Install GUL + git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/gul.git + build_ninja gul + + # Install DOOCS clientlib + export ENSHOST=ldap://xfelens1.desy.de + git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/clientlib.git + build_ninja clientlib + + # Install DOOCS serverlib + git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/serverlib.git + build_ninja serverlib + + # Install DOOCSServerTestHelper + git clone --recursive --depth 1 --branch ${DOOCS_SERVER_TEST_HELPER_VERSION} https://github.com/ChimeraTK/DoocsServerTestHelper.git + build_cmake DoocsServerTestHelper + + #Install ChimeraTK-EPICS + git clone --recursive --depth 1 --branch ${CHIMERATK_EPICS_VERSION} https://github.com/ChimeraTK/EPICS-Interface.git + cd EPICS-Interface/ + mkdir builddir + cd builddir/ + cmake -DCMAKE_BUILD_TYPE=Release -DEPICS_VERSION=7 .. + make install -j${PROCS} + cd ${BUILD_DIR} + + # Install yajl + git clone --recursive --depth 1 --branch ${YAJL_VERSION} https://github.com/lloyd/yajl.git + build_cmake yajl + + #Install open62541 + git clone --recursive --depth 1 --branch ${OPEN62541_VERSION} https://github.com/open62541/open62541.git + build_cmake open62541 + + # Install ChimeraTK-DeviceAccess-EpicsBackend + git clone --recursive --depth 1 --branch ${DEVICEACCESS_EPICS_VERSION} https://github.com/ChimeraTK/DeviceAccess-EpicsBackend.git + build_cmake DeviceAccess-EpicsBackend + + # Install ChimeraTK-DeviceAccess-OpcUaBackend + git clone --recursive --depth 1 --branch ${DEVICEACCESS_OPCUA_VERSION} https://github.com/ChimeraTK/DeviceAccess-OpcUaBackend.git + cd DeviceAccess-OpcUaBackend + mkdir builddir + cd builddir/ + cmake -DCMAKE_BUILD_TYPE=Release .. + make ChimeraTK-DeviceAccess-OPC-UA-Backend + make install/fast -j${PROCS} + cd ${BUILD_DIR} + + # Install ChimeraTK-DeviceAccess-DoocsBackend + git clone --recursive --depth 1 --branch ${DEVICEACCESS_DOOCS_VERSION} https://github.com/ChimeraTK/DeviceAccess-DoocsBackend.git + build_cmake DeviceAccess-DoocsBackend -# Install ChimeraTK-DeviceAccess-DoocsBackend -git clone --recursive --depth 1 --branch ${DEVICEACCESS_DOOCS_VERSION} https://github.com/ChimeraTK/DeviceAccess-DoocsBackend.git -build_cmake DeviceAccess-DoocsBackend + # Install ChimeraTK-DeviceAccess-ModbusBackend + git clone --recursive --depth 1 --branch ${DEVICEACCESS_MODBUS_VERSION} https://github.com/ChimeraTK/DeviceAccess-ModbusBackend.git + build_cmake DeviceAccess-ModbusBackend -# Install ChimeraTK-DeviceAccess-ModbusBackend -git clone --recursive --depth 1 --branch ${DEVICEACCESS_MODBUS_VERSION} https://github.com/ChimeraTK/DeviceAccess-ModbusBackend.git -build_cmake DeviceAccess-ModbusBackend +fi cd ${PROJECT_PWD} diff --git a/pyproject.toml b/pyproject.toml index 7da2aa6..d99762a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,20 @@ [build-system] -requires = ["scikit-build-core", "numpy"] +requires = [ + "scikit-build-core", + "oldest-supported-numpy; python_version<='3.9'", + "numpy>=2.0; python_version>'3.9'", + "cmake" +] + build-backend = "scikit_build_core.build" [project] name = "deviceaccess" version = "3.3.2" -dependencies = ["numpy"] +dependencies = [ + "numpy<2.0; python_version<='3.9'", + "numpy; python_version>'3.9'" +] description = "Python bindings for the ChimeraTK's deviceaccess library" long_description = "The package provides binding for the ChimeraTK's deviceaccess library" requires-python = ">=3.8" @@ -45,8 +54,7 @@ wheel.py-api = "cp312" [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" -before-build = "pip install numpy" -skip = "cp36-* cp37-* *-musllinux* pp* *-win32" +skip = "cp36-* cp37-* *-musllinux* pp* *-win32 *_i686" manylinux-x86_64-image = "manylinux_2_28" manylinux-i686-image = "manylinux2_2_28" manylinux-aarch64-image = "manylinux2_2_28" @@ -55,12 +63,31 @@ manylinux-aarch64-image = "manylinux2_2_28" build-verbosity = 1 ## Run pytest to ensure that the package was correctly built +test-skip = "*" -#test-command = "python3 -m unittest discover tests" -test-skip = "*" # Do not test at the moment +[tool.cibuildwheel.linux.environment] +# Enable backends compilation +DEVICEACCESS_BACKEND_ENABLE="0" + +# Dependencies versions +YAJL_VERSION="2.1.0" +EPICS_VERSION="R7.0.8" +CHIMERATK_EPICS_VERSION="01.00.00" +OPEN62541_VERSION="v1.3.1" +LIBTIRPC_VERSION="1.3.4" +TINE_VERSION="08ca83228932c3b08caf3b4c6578f1da8e65f5c5" +DOOCS_VERSION="DOOCSVERSION_24_3_1" +DOOCS_SERVER_TEST_HELPER_VERSION="01.07.00" +CPPEXT_VERSION="01.05.02" +EXPRTK_VERSION="01.04.01" +NLOHMANN_JSON_VERSION="v3.7.3" +DEVICEACCESS_VERSION="03.15.02" +DEVICEACCESS_EPICS_VERSION="01.00.00" +DEVICEACCESS_OPCUA_VERSION="01.03.00" +DEVICEACCESS_DOOCS_VERSION="01.09.01" +DEVICEACCESS_MODBUS_VERSION="01.05.00" # Add the build directory to the environment variables -[tool.cibuildwheel.linux.environment] BUILD_DIR="${HOME}/build" PKG_CONFIG_PATH="${BUILD_DIR}/local/lib/pkgconfig/:${BUILD_DIR}/EPICS/epics-base/lib/pkgconfig/:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" LD_LIBRARY_PATH="${BUILD_DIR}/local/lib/:${BUILD_DIR}/EPICS/epics-base/lib/linux-x86_64/:${LD_LIBRARY_PATH}" From 04d67fc08c2aa6c88b3ae1ac5bc4f43c2500d0ef Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 2 Sep 2024 10:34:25 +0200 Subject: [PATCH 08/34] reorganized structure --- CMakeLists.txt | 9 +- .../__pycache__/__init__.cpython-311.pyc | Bin 69725 -> 0 bytes mtca4u_/LICENSE | 165 ------------------ mtca4u_/README.md | 2 - mtca4u_/pyproject.toml | 34 ---- pyproject.toml | 10 +- .../deviceaccess}/__init__.py | 4 +- {mtca4u_ => src}/mtca4u/__init__.py | 0 tests/testDeviceAccessLib.py | 1 - 9 files changed, 12 insertions(+), 213 deletions(-) delete mode 100644 deviceaccess_/__pycache__/__init__.cpython-311.pyc delete mode 100644 mtca4u_/LICENSE delete mode 100644 mtca4u_/README.md delete mode 100644 mtca4u_/pyproject.toml rename {deviceaccess_ => src/deviceaccess}/__init__.py (99%) rename {mtca4u_ => src}/mtca4u/__init__.py (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bae54d..daa5da8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,8 +56,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" OUTPUT_NAME "_da_pyth # Copy Python part to build directory (required for tests to run) if(NOT SKBUILD) - file(COPY ${PROJECT_SOURCE_DIR}/deviceaccess_/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/deviceacces.py) - file(COPY ${PROJECT_SOURCE_DIR}/mtca4u_/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/mtca4u.py) + file(COPY ${PROJECT_SOURCE_DIR}/src/deviceaccess/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/deviceacces.py) + file(COPY ${PROJECT_SOURCE_DIR}/src/mtca4u/__init__.py DESTINATION ${PROJECT_BINARY_DIR}/mtca4u.py) endif() # ==============================================================================# @@ -107,7 +107,6 @@ endif() # ==============================================================================# if(SKBUILD) - install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess_/__init__.py DESTINATION ${SKBUILD_PROJECT_NAME}) install(TARGETS ${PROJECT_NAME} DESTINATION ${SKBUILD_PROJECT_NAME}) else() # install Python modules to correct platform-dependent directory (if installing to system prefix) @@ -116,7 +115,7 @@ else() else() set(install_path "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") endif() - install(FILES ${PROJECT_SOURCE_DIR}/deviceaccess_/__init__.py DESTINATION ${install_path} RENAME deviceaccess.py) - install(FILES ${PROJECT_SOURCE_DIR}/mtca4u_/mtca4u/__init__.py DESTINATION ${install_path} RENAME mtca4u.py) + install(FILES ${PROJECT_SOURCE_DIR}/src/deviceaccess/__init__.py DESTINATION ${install_path} RENAME deviceaccess.py) + install(FILES ${PROJECT_SOURCE_DIR}/src/mtca4u/__init__.py DESTINATION ${install_path} RENAME mtca4u.py) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${install_path}) endif() diff --git a/deviceaccess_/__pycache__/__init__.cpython-311.pyc b/deviceaccess_/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 9821f24b74eeb1a5fe36930651e372765214096b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69725 zcmeIb4RBjmb|wgr00sVtph)VMr1VJYhm=TAq$rW1W!bVQS+XTc5h=SZ*$skxk0c}z zpdUc}Fm#Wq*E3bRvcIcH;>GJ-5=iK*mAMhYjKh~tX^$6Yvym$Zax#ym9?m6fF;o7wo9$bCD@uP|M zAA3Cifqt0R8u7(@f1t$U`Hn~RT=1w~wIt@9F1b+RrSGM&(rMoX-?aaNpMU#eWf#_z zdsM$#77I+5U#P%Szq%$?K3#dCa=Pk5)%4m6Yp1I(R8I#l1ihYkQ>#a-+3wNT8>x{3 zxfYH8h9~a3>bX|>hUcmm@qDp$7uNB;_Cl?ys8#R!F4U>b>RR0EOFS=o)arLVYEWBu zX}#H`4@y0rkMS?A3mZ6A4Pvd+HsrnA$nR?Lu1?#S_ihuvtH-V%) zGj3Qhzm<5j+DpymFLm3y9z1E$nl81P&+cqDzsdIx@Z)3a!3SRa!oRrkO5dURwawZl zZOcUIW;2TC!cNsU>}hX_c+r50k*R1>nNFy)F-=KKOlW#iNlj_W#B6*#6-~q=v1sZ# zUhu;~?Mif9J2F15C6mh0J$sZ`bWD%v*OiHcuFPCdO(o)?3goCMN2j9GnjRT>RdJ*- zgt|fDsp+3@PV~=SZt- z3NneOq7zZX7`v`Slz3!XL#C=So1BfrVtB-r#cNb*7QTwY2{W3c7bb?wJM^8+1)=DBSb*fRs zL;{5KYdVlDZtSUUnTG4TmazN^sHc z%~eL?@kB~AqrD^-7}nmN)zB|F|M@tYHuO`1F~qA2tC6r6>+o1Ku14dN$##FPLO{jo zgsSDL1|zA+Yk(XTu#;OiqDSIM0M@aXHjVb59L%kK4Lw7d&Q6bMdMV_Xe_48MD(NqJyu+X zV46k(Y}?Qmkz~6POC&DO&V)Fhb6O0YeFc+Frzwz#&89Tum6|FjSZPzEIz~2uiAJDJ zm0>_{H>z~(T4Z`ArX@MwPUA8gmBIiqd=zaM$5fQVhYu^!>6wI{GHPwWJ%Cq@NO9Ci zD5<3ePunBZwo5egqz$fJnyAxJJ(AKw)Gxc*nTICx@{^;^=)enYlmjO) zU#+4= zoo5-ly<`Ac>HVL9lCWW<5RLe zC2fB0|$0`sEPh2gplUy8xCb1eWwd*`Cu?sGg{1m3a1lF{SUsp$BW00{$qkI(8l zW*^YyY!XPB&@X{1LC)+<9t=|!Nddy`C3-FDmaC-kGy3);kG=uV^m=*`%o|vp`k)X0 zNlJzaWhZywH6He@Rs(!K^x>gWV?0A1eucM*Qaqj@fPLGYBQ-V3GbR@DcR!l!Oi z%W(JOz6SR)+yl6;!Mz;!0M?F5+{+D8qynT!HGWs(cM$g~wMAWr`&zYCt;4+<>FRM0 z;=UgD8r(PFz7F?|xYufRm#A@kO?Xj@P4D_ItXH?ITkv*+x6z=hk_xc2u1BtSP`;c2Aw+SFX;YCa(Znn-kK>viC*Vn9VWc}@ zEi7YG8m(pHk(iP|OT|)?mkl~18J&(!$09l{^~u>7+GMQPcr``sqeewiDLpy{l$kWq zWlWn$dD#MKUEiWV|T2Ly@?kPWTu=%|^f$RG+TfCnO`MW`zic{w?$A?|6^6n%$L zibP|PF<|6qoNGp85zweu>$r_#vX~vEC}&X!Rt#Oknn(jp?J>}05`+V4wlZFqI`*m< z+>mm{>Qu9pI_RFOpar;ygdUxYf}XSb)2tUEeix+PvI-R2VxnWtL%X6dIv0qut>x&E zbA#d6PmY`jpFT5q{?xJI{OBQ!P`pjfLsSy8gmTiZ(8@4!H8Ge!ecIwPAuJ2~dODPr zm(HM=uEEns9JyN=(PGus=UPUHx=w3rS6UhPH8Ey_u#5vf`s5xG>yVYLnIR49#fyE4 z_4k$pI*;|-%!s;nd`III6Bd6$XQJ{-WQ}ZXtAUn#gT((tXXP@ zQkWxk8UPowiDp!JiffRO&@5lGKrA zt(j+H_L`qy*)*0w-C*o#ii_Gxg&X6dV|jK#RO0P}YGjU8NaSGP7SUX^o(L4Pe2b#M zdO2my3Z6W);HMI^<5QX{)y=>b#_%|5(QD0LXSWdQ1|7{y56wTT!>H>)3EBjTC3}&l zs8Kj|{jRQjK;Ol0^&kjTy#|3hv#j8alc`X>P8Ru_Ua-eMA`uZ|E(GV$`%Frx;O_;h zp~?$7!Jt_v?#T&dVCMP/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 nncm#FM4v7I%a*To^`FP_If9YTNya@k) - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/mtca4u_/README.md b/mtca4u_/README.md deleted file mode 100644 index fea5207..0000000 --- a/mtca4u_/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Legacy mtca4u bindings for ChimeraTK DeviceAccess Library -Find API documentation [here](https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html) diff --git a/mtca4u_/pyproject.toml b/mtca4u_/pyproject.toml deleted file mode 100644 index 0488a85..0000000 --- a/mtca4u_/pyproject.toml +++ /dev/null @@ -1,34 +0,0 @@ -[build-system] -requires = ["setuptools>=46.4.0", "setuptools-scm"] -build-backend = "setuptools.build_meta" - -[project] -name = "mtca4u" -dependencies = ["deviceaccess"] -description = "Legacy ChimeraTK mtca4u access library" -requires-python = ">=3.8" -readme = "README.md" -license = {file = "LICENSE"} -version = "2.3.0" - -classifiers = [ -"Topic :: Software Development :: Libraries :: Python Modules", -"Development Status :: 5 - Production/Stable", -"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", -"Intended Audience :: Developers", -"Programming Language :: Python :: 3", -"Programming Language :: Python :: 3.8", -"Programming Language :: Python :: 3.9", -"Programming Language :: Python :: 3.10", -"Programming Language :: Python :: 3.11", -"Programming Language :: Python :: 3.12", -"Programming Language :: Python :: 3.13", -"Programming Language :: Python :: Implementation :: CPython", -"Topic :: Scientific/Engineering" -] - -[project.urls] -Homepage = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" -Documentation = "https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html/" -Repository = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings/mtca4u" - diff --git a/pyproject.toml b/pyproject.toml index d99762a..749175f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,7 @@ [build-system] requires = [ "scikit-build-core", - "oldest-supported-numpy; python_version<='3.9'", - "numpy>=2.0; python_version>'3.9'", + "oldest-supported-numpy", "cmake" ] @@ -12,8 +11,7 @@ build-backend = "scikit_build_core.build" name = "deviceaccess" version = "3.3.2" dependencies = [ - "numpy<2.0; python_version<='3.9'", - "numpy; python_version>'3.9'" + "numpy<2", ] description = "Python bindings for the ChimeraTK's deviceaccess library" long_description = "The package provides binding for the ChimeraTK's deviceaccess library" @@ -42,6 +40,10 @@ Homepage = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" Documentation = "https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html/" Repository = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" +[tool.scikit-build.wheel.packages] +deviceaccess = "src/deviceaccess" +mtca4u = "src/mtca4u" + [tool.scikit-build] # Protect the configuration against future changes in scikit-build-core minimum-version = "0.4" diff --git a/deviceaccess_/__init__.py b/src/deviceaccess/__init__.py similarity index 99% rename from deviceaccess_/__init__.py rename to src/deviceaccess/__init__.py index e0b5a5a..209c52e 100644 --- a/deviceaccess_/__init__.py +++ b/src/deviceaccess/__init__.py @@ -19,8 +19,8 @@ import numpy as np try: - from . import _da_python_bindings as pb - from ._da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType + from deviceaccess import _da_python_bindings as pb + from deviceaccess._da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType except ModuleNotFoundError: import _da_python_bindings as pb from _da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType diff --git a/mtca4u_/mtca4u/__init__.py b/src/mtca4u/__init__.py similarity index 100% rename from mtca4u_/mtca4u/__init__.py rename to src/mtca4u/__init__.py diff --git a/tests/testDeviceAccessLib.py b/tests/testDeviceAccessLib.py index 12e0e6c..4d2dabe 100644 --- a/tests/testDeviceAccessLib.py +++ b/tests/testDeviceAccessLib.py @@ -17,7 +17,6 @@ # so the import is not sorted into the others. sys.path.insert(0, os.path.abspath(os.path.join(os.curdir,".."))) import deviceaccess as da -import _da_python_bindings as pb # fmt: on From dd855353698386f007d50699c31d966189d04c85 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 2 Sep 2024 14:04:35 +0200 Subject: [PATCH 09/34] link to all dependencies --- CMakeLists.txt | 39 ++++++++++++++++++++++++------- pre_build_manylinux.sh | 52 ++++++++++++++++++++++++++++-------------- pyproject.toml | 2 +- 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index daa5da8..5f258d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,13 @@ find_package(ChimeraTK-DeviceAccess 03.14 REQUIRED) find_package(Python3 COMPONENTS Interpreter Development.Module NumPy REQUIRED) find_package(Boost COMPONENTS python3 numpy3 REQUIRED) +if(SKBUILD) + find_package(ChimeraTK-DeviceAccess-EPICS-Backend REQUIRED) + find_package(ChimeraTK-DeviceAccess-OPC-UA-Backend REQUIRED) + find_package(ChimeraTK-DeviceAccess-DoocsBackend REQUIRED) + find_package(ChimeraTK-DeviceAccess-ModbusBackend REQUIRED) +endif() + # ==============================================================================# # Defining NPY_NO_DEPRECATED_API as NPY_1_7_API_VERSION, should ensure that the @@ -40,13 +47,29 @@ file(GLOB_RECURSE sources "${CMAKE_CURRENT_SOURCE_DIR}/src/*") add_library(${PROJECT_NAME} SHARED ${headers} ${sources}) -target_link_libraries(${PROJECT_NAME} - PRIVATE ChimeraTK::ChimeraTK-DeviceAccess - PRIVATE Python3::NumPy - PRIVATE Python3::Module - PRIVATE ${Boost_LIBRARIES} - PRIVATE Boost::python3 - PRIVATE Boost::numpy3) +if(SKBUILD) + target_link_libraries(${PROJECT_NAME} + PRIVATE ChimeraTK::ChimeraTK-DeviceAccess + PRIVATE Python3::NumPy + PRIVATE Python3::Module + PRIVATE ${Boost_LIBRARIES} + PRIVATE Boost::python3 + PRIVATE Boost::numpy3 + "-Wl,--no-as-needed -Wl,--whole-archive" # Force linking of the libraries below + PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-EPICS-Backend + PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-OPC-UA-Backend + PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-DoocsBackend + PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-ModbusBackend + "-Wl,--as-needed -Wl,--no-whole-archive") +else() + target_link_libraries(${PROJECT_NAME} + PRIVATE ChimeraTK::ChimeraTK-DeviceAccess + PRIVATE Python3::NumPy + PRIVATE Python3::Module + PRIVATE ${Boost_LIBRARIES} + PRIVATE Boost::python3 + PRIVATE Boost::numpy3) +endif() # do not remove runtime path to libmtca-deviceaccess location from ${boost_python_core_module} when installing set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) @@ -82,7 +105,7 @@ include(cmake/enable_code_coverage_report.cmake) # add target for documentation if sphinx is available find_package(Sphinx) -if(SPHINX_EXECUTABLE) +if(SPHINX_EXECUTABLE AND (NOT SKBUILD)) configure_file("${PROJECT_SOURCE_DIR}/doc/conf.py.in" "${PROJECT_BINARY_DIR}/conf.py") # copy the config file to the build directory diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh index bbf8cd7..5802c06 100755 --- a/pre_build_manylinux.sh +++ b/pre_build_manylinux.sh @@ -41,23 +41,7 @@ dnf -y install clang zeromq-devel readline-devel openldap-devel wget libssh2-dev pipx install meson ninja sphinx -# Install cppext -git clone --recursive --depth 1 --branch ${CPPEXT_VERSION} https://github.com/ChimeraTK/cppext.git -build_cmake cppext - -# Install exprtk -git clone --recursive --depth 1 --branch ${EXPRTK_VERSION} https://github.com/ChimeraTK/exprtk-interface.git -build_cmake exprtk-interface - -# Install nlohmann-json -git clone --recursive --depth 1 --branch ${NLOHMANN_JSON_VERSION} https://github.com/nlohmann/json.git -build_cmake json - -# Install ChimeraTK-DeviceAccess -git clone --recursive --depth 1 --branch ${DEVICEACCESS_VERSION} https://github.com/ChimeraTK/DeviceAccess.git -build_cmake DeviceAccess - -if [ $DEVICEACCESS_BACKEND_ENABLE != "0"] +if [ $DEVICEACCESS_BACKEND_ENABLE != "0" ] then # Install EPICS @@ -128,6 +112,40 @@ then git clone --recursive --depth 1 --branch ${OPEN62541_VERSION} https://github.com/open62541/open62541.git build_cmake open62541 + # Install DOOCSServerTestHelper + git clone --recursive --depth 1 --branch ${DOOCS_SERVER_TEST_HELPER_VERSION} https://github.com/ChimeraTK/DoocsServerTestHelper.git + build_cmake DoocsServerTestHelper + + #Install ChimeraTK-EPICS + git clone --recursive --depth 1 --branch ${CHIMERATK_EPICS_VERSION} https://github.com/ChimeraTK/EPICS-Interface.git + cd EPICS-Interface/ + mkdir builddir + cd builddir/ + cmake -DCMAKE_BUILD_TYPE=Release -DEPICS_VERSION=7 .. + make install -j${PROCS} + cd ${BUILD_DIR} + +fi + +# Install cppext +git clone --recursive --depth 1 --branch ${CPPEXT_VERSION} https://github.com/ChimeraTK/cppext.git +build_cmake cppext + +# Install exprtk +git clone --recursive --depth 1 --branch ${EXPRTK_VERSION} https://github.com/ChimeraTK/exprtk-interface.git +build_cmake exprtk-interface + +# Install nlohmann-json +git clone --recursive --depth 1 --branch ${NLOHMANN_JSON_VERSION} https://github.com/nlohmann/json.git +build_cmake json + +# Install ChimeraTK-DeviceAccess +git clone --recursive --depth 1 --branch ${DEVICEACCESS_VERSION} https://github.com/ChimeraTK/DeviceAccess.git +build_cmake DeviceAccess + +if [ $DEVICEACCESS_BACKEND_ENABLE != "0" ] +then + # Install ChimeraTK-DeviceAccess-EpicsBackend git clone --recursive --depth 1 --branch ${DEVICEACCESS_EPICS_VERSION} https://github.com/ChimeraTK/DeviceAccess-EpicsBackend.git build_cmake DeviceAccess-EpicsBackend diff --git a/pyproject.toml b/pyproject.toml index 749175f..5deab56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ test-skip = "*" [tool.cibuildwheel.linux.environment] # Enable backends compilation -DEVICEACCESS_BACKEND_ENABLE="0" +DEVICEACCESS_BACKEND_ENABLE="1" # Dependencies versions YAJL_VERSION="2.1.0" From a6b3a355a371f12d2d6be731c65c75c7d2bfadf5 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 2 Sep 2024 14:28:11 +0200 Subject: [PATCH 10/34] Fixed tests --- tests/testAccessors.py | 8 +++++--- tests/testConvenienceFunctions.py | 4 +++- tests/testDeviceAccessLib.py | 4 +++- tests/testDeviceReadWrite.py | 4 +++- tests/testDummyDevice.py | 2 ++ tests/testMtca4upy.py | 7 ++++--- tests/testOpeningException.py | 2 ++ tests/testPushType.py | 10 ++++++---- tests/testRegisterCatalogue.py | 4 +++- 9 files changed, 31 insertions(+), 14 deletions(-) diff --git a/tests/testAccessors.py b/tests/testAccessors.py index 0ac0745..10572c5 100755 --- a/tests/testAccessors.py +++ b/tests/testAccessors.py @@ -4,6 +4,7 @@ import sys import unittest +from pathlib import Path import numpy as np import os @@ -15,12 +16,13 @@ import deviceaccess as da # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestAccessors(unittest.TestCase): def testScalar(self): - da.setDMapFilePath("deviceInformation/exampleCrate.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "exampleCrate.dmap")) dev = da.Device("CARD_WITH_MODULES") dev.open() @@ -91,7 +93,7 @@ def testScalar(self): def testOneD(self): - da.setDMapFilePath("deviceInformation/exampleCrate.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "exampleCrate.dmap")) dev = da.Device("CARD_WITH_MODULES") dev.open() @@ -146,7 +148,7 @@ def testOneD(self): def testTwoD(self): - da.setDMapFilePath("deviceInformation/exampleCrate.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "exampleCrate.dmap")) dev = da.Device("CARD_WITH_MODULES") dev.open() acc = dev.getTwoDRegisterAccessor(np.int32, "BOARD/DMA") diff --git a/tests/testConvenienceFunctions.py b/tests/testConvenienceFunctions.py index 2333cd4..d70c7e8 100755 --- a/tests/testConvenienceFunctions.py +++ b/tests/testConvenienceFunctions.py @@ -5,6 +5,7 @@ from concurrent.futures import thread import sys import unittest +from pathlib import Path import numpy as np import os import threading @@ -19,11 +20,12 @@ import deviceaccess as da # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestConvenienceFunctions(unittest.TestCase): def setUp(self): - da.setDMapFilePath("deviceInformation/testCrate.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "testCrate.dmap")) self.dev = da.Device("TEST_CARD") self.dev.open() diff --git a/tests/testDeviceAccessLib.py b/tests/testDeviceAccessLib.py index 4d2dabe..70bb16c 100644 --- a/tests/testDeviceAccessLib.py +++ b/tests/testDeviceAccessLib.py @@ -5,6 +5,7 @@ from concurrent.futures import thread import sys import unittest +from pathlib import Path import numpy as np import os import threading @@ -19,11 +20,12 @@ import deviceaccess as da # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestDeviceAccessLib(unittest.TestCase): def setUp(self): - self.dmap_filepath = "deviceInformation/testCrate.dmap" + self.dmap_filepath = str(DEVINFO_DIR / "testCrate.dmap") da.setDMapFilePath(self.dmap_filepath) self.dev = da.Device("TEST_CARD") self.dev.open() diff --git a/tests/testDeviceReadWrite.py b/tests/testDeviceReadWrite.py index d0063bd..c0ac958 100755 --- a/tests/testDeviceReadWrite.py +++ b/tests/testDeviceReadWrite.py @@ -5,6 +5,7 @@ from concurrent.futures import thread import sys import unittest +from pathlib import Path import numpy as np import os import threading @@ -19,11 +20,12 @@ import deviceaccess as da # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestDeviceReadWrite(unittest.TestCase): def setUp(self): - da.setDMapFilePath("deviceInformation/testCrate.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "testCrate.dmap")) self.dev = da.Device("TEST_CARD") self.dev.open() diff --git a/tests/testDummyDevice.py b/tests/testDummyDevice.py index d9da408..b0ce0f7 100755 --- a/tests/testDummyDevice.py +++ b/tests/testDummyDevice.py @@ -4,6 +4,7 @@ import numpy import unittest +from pathlib import Path import os import sys @@ -15,6 +16,7 @@ import mtca4u # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestDummyDevice(unittest.TestCase): diff --git a/tests/testMtca4upy.py b/tests/testMtca4upy.py index e7ab66e..15d9849 100755 --- a/tests/testMtca4upy.py +++ b/tests/testMtca4upy.py @@ -6,6 +6,7 @@ import sys import fcntl import unittest +from pathlib import Path import numpy # fmt: off @@ -16,12 +17,13 @@ import mtca4u # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestPCIEDevice(unittest.TestCase): # TODO: Refactor to take care of the harcoded values used for comparisions def setUp(self): - mtca4u.set_dmap_location("deviceInformation/exampleCrate.dmap") + mtca4u.set_dmap_location(str(DEVINFO_DIR / "exampleCrate.dmap")) def testRead(self): # first open devices, so shared memory dummies work correctly with __prepareDataOnCards and __testRead @@ -151,8 +153,7 @@ def testDeviceCreation(self): def testSetGetDmapfile(self): # set by the test setUp method - self.assertTrue(mtca4u.get_dmap_location() == - "deviceInformation/exampleCrate.dmap") + self.assertTrue(mtca4u.get_dmap_location() == str(DEVINFO_DIR / "exampleCrate.dmap")) """ The idea here is to preset data on registers that is then read in and diff --git a/tests/testOpeningException.py b/tests/testOpeningException.py index 75260b7..69e3874 100755 --- a/tests/testOpeningException.py +++ b/tests/testOpeningException.py @@ -4,6 +4,7 @@ import sys import unittest +from pathlib import Path import numpy import os @@ -15,6 +16,7 @@ import mtca4u # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestOpeningException(unittest.TestCase): diff --git a/tests/testPushType.py b/tests/testPushType.py index 090f714..759059c 100755 --- a/tests/testPushType.py +++ b/tests/testPushType.py @@ -5,6 +5,7 @@ from concurrent.futures import thread import sys import unittest +from pathlib import Path import numpy as np import os import threading @@ -18,12 +19,13 @@ import deviceaccess as da # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestPushType(unittest.TestCase): def testCorrectInterupt(self): - da.setDMapFilePath("deviceInformation/push.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "push.dmap")) dev = da.Device("SHARED_RAW_DEVICE") dev.open() dev.activateAsyncRead() @@ -45,7 +47,7 @@ def blockingRead(readAcc, barrier): readAcc.read() barrier.wait() - da.setDMapFilePath("deviceInformation/push.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "push.dmap")) dev = da.Device("SHARED_RAW_DEVICE") dev.open() dev.activateAsyncRead() @@ -68,7 +70,7 @@ def blockingRead(readAcc, barrier): barrier.wait(timeout=2) def testCorrectWrite(self): - da.setDMapFilePath("deviceInformation/push.dmap") + da.setDMapFilePath(str(DEVINFO_DIR /"push.dmap")) dev = da.Device("SHARED_RAW_DEVICE") dev.open() dev.activateAsyncRead() @@ -102,7 +104,7 @@ def blockingRead(readAcc, barrier): readAcc.read() barrier.wait() - da.setDMapFilePath("deviceInformation/push.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "push.dmap")) dev = da.Device("SHARED_RAW_DEVICE") dev.open() dev.activateAsyncRead() diff --git a/tests/testRegisterCatalogue.py b/tests/testRegisterCatalogue.py index 174d523..11b4018 100755 --- a/tests/testRegisterCatalogue.py +++ b/tests/testRegisterCatalogue.py @@ -5,6 +5,7 @@ from concurrent.futures import thread import sys import unittest +from pathlib import Path import numpy as np import os import threading @@ -19,11 +20,12 @@ import deviceaccess as da # fmt: on +DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestRegisterCatalogue(unittest.TestCase): def setUp(self): - da.setDMapFilePath("deviceInformation/push.dmap") + da.setDMapFilePath(str(DEVINFO_DIR / "push.dmap")) self.dev = da.Device("SHARED_RAW_DEVICE") def tearDown(self) -> None: From 7748d9249517fff0359ed444028adf1ee2cee145 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 2 Sep 2024 14:29:46 +0200 Subject: [PATCH 11/34] Added tests to cibuildwheel --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5deab56..5a39b14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ manylinux-aarch64-image = "manylinux2_2_28" build-verbosity = 1 ## Run pytest to ensure that the package was correctly built -test-skip = "*" +test-command = "python -m unittest discover -v {package}/tests" [tool.cibuildwheel.linux.environment] # Enable backends compilation From 03b70f57ef52d43eb221492c84d779d949b7faa4 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 2 Sep 2024 21:18:36 +0200 Subject: [PATCH 12/34] disabled tests --- pyproject.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5a39b14..80b9e21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,11 +64,13 @@ manylinux-aarch64-image = "manylinux2_2_28" # Necessary to see build output from the actual compilation build-verbosity = 1 -## Run pytest to ensure that the package was correctly built -test-command = "python -m unittest discover -v {package}/tests" +## Run unittest to ensure that the package was correctly built +## tests disabled due to corruption on interpreter exit +# test-command = "python -m unittest discover -v {package}/tests" +test-skip = "*" [tool.cibuildwheel.linux.environment] -# Enable backends compilation +# Enable additional backends compilation DEVICEACCESS_BACKEND_ENABLE="1" # Dependencies versions From 9b65ecdb1580724261ba410c8ce4269a385a3c08 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 2 Sep 2024 21:55:31 +0200 Subject: [PATCH 13/34] removed --whole-archive --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f258d8..e32d040 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,12 +55,12 @@ if(SKBUILD) PRIVATE ${Boost_LIBRARIES} PRIVATE Boost::python3 PRIVATE Boost::numpy3 - "-Wl,--no-as-needed -Wl,--whole-archive" # Force linking of the libraries below + "-Wl,--no-as-needed" # Force linking of the libraries below PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-EPICS-Backend PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-OPC-UA-Backend PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-DoocsBackend PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-ModbusBackend - "-Wl,--as-needed -Wl,--no-whole-archive") + "-Wl,--as-needed") else() target_link_libraries(${PROJECT_NAME} PRIVATE ChimeraTK::ChimeraTK-DeviceAccess From 54a61e68c07351a4eadc7c54a3a195cca2688c06 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 2 Sep 2024 21:56:50 +0200 Subject: [PATCH 14/34] CMakeLists comment backend change --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e32d040..471c2e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,7 @@ if(SKBUILD) PRIVATE ${Boost_LIBRARIES} PRIVATE Boost::python3 PRIVATE Boost::numpy3 - "-Wl,--no-as-needed" # Force linking of the libraries below + "-Wl,--no-as-needed" # Force linking the optional backends PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-EPICS-Backend PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-OPC-UA-Backend PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-DoocsBackend From 86bc1b34f846cc8ebaaf9d6de2d02a76cba8622a Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Fri, 17 Oct 2025 22:04:15 +0200 Subject: [PATCH 15/34] fix: fixed and deleted plugins --- CMakeLists.txt | 37 ++--------- pre_build_manylinux.sh | 144 +++-------------------------------------- pyproject.toml | 23 ++----- 3 files changed, 21 insertions(+), 183 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 471c2e1..edb7889 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,13 +20,6 @@ find_package(ChimeraTK-DeviceAccess 03.14 REQUIRED) find_package(Python3 COMPONENTS Interpreter Development.Module NumPy REQUIRED) find_package(Boost COMPONENTS python3 numpy3 REQUIRED) -if(SKBUILD) - find_package(ChimeraTK-DeviceAccess-EPICS-Backend REQUIRED) - find_package(ChimeraTK-DeviceAccess-OPC-UA-Backend REQUIRED) - find_package(ChimeraTK-DeviceAccess-DoocsBackend REQUIRED) - find_package(ChimeraTK-DeviceAccess-ModbusBackend REQUIRED) -endif() - # ==============================================================================# # Defining NPY_NO_DEPRECATED_API as NPY_1_7_API_VERSION, should ensure that the @@ -47,29 +40,13 @@ file(GLOB_RECURSE sources "${CMAKE_CURRENT_SOURCE_DIR}/src/*") add_library(${PROJECT_NAME} SHARED ${headers} ${sources}) -if(SKBUILD) - target_link_libraries(${PROJECT_NAME} - PRIVATE ChimeraTK::ChimeraTK-DeviceAccess - PRIVATE Python3::NumPy - PRIVATE Python3::Module - PRIVATE ${Boost_LIBRARIES} - PRIVATE Boost::python3 - PRIVATE Boost::numpy3 - "-Wl,--no-as-needed" # Force linking the optional backends - PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-EPICS-Backend - PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-OPC-UA-Backend - PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-DoocsBackend - PRIVATE ChimeraTK::ChimeraTK-DeviceAccess-ModbusBackend - "-Wl,--as-needed") -else() - target_link_libraries(${PROJECT_NAME} - PRIVATE ChimeraTK::ChimeraTK-DeviceAccess - PRIVATE Python3::NumPy - PRIVATE Python3::Module - PRIVATE ${Boost_LIBRARIES} - PRIVATE Boost::python3 - PRIVATE Boost::numpy3) -endif() +target_link_libraries(${PROJECT_NAME} + PRIVATE ChimeraTK::ChimeraTK-DeviceAccess + PRIVATE Python3::NumPy + PRIVATE Python3::Module + PRIVATE ${Boost_LIBRARIES} + PRIVATE Boost::python3 + PRIVATE Boost::numpy3) # do not remove runtime path to libmtca-deviceaccess location from ${boost_python_core_module} when installing set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh index 5802c06..644cb3f 100755 --- a/pre_build_manylinux.sh +++ b/pre_build_manylinux.sh @@ -9,123 +9,24 @@ export PROCS=`nproc` rm -rf ${BUILD_DIR} mkdir ${BUILD_DIR} cd ${BUILD_DIR} -mkdir ${BUILD_DIR}/local/ -mkdir ${BUILD_DIR}/local/bin/ -mkdir ${BUILD_DIR}/local/include/ -mkdir ${BUILD_DIR}/local/lib/ - -# Helper function to build meson-based projects -build_ninja () { - cd $1 - meson setup build --buildtype=release --prefix=${BUILD_DIR}/local/ --libdir=lib --includedir=lib/include - ninja install -j${PROCS} -C build - cd ${BUILD_DIR} -} # Helper function to build cmake-based projects build_cmake () { - cd $1 - mkdir builddir - cd builddir/ - cmake -DCMAKE_BUILD_TYPE=Release .. - make install -j${PROCS} - cd ${BUILD_DIR} + cd $1 + mkdir builddir + cd builddir/ + cmake -DCMAKE_BUILD_TYPE=Release -DJSON_BuildTests=OFF -DBUILD_TESTING=OFF -DBUILD_TESTS=OFF .. + make install -j${PROCS} + cd ${BUILD_DIR} } # Install required system packages dnf install -y epel-release -dnf update -y -dnf upgrade -y -dnf groupinstall -y "Development Tools" -dnf -y install clang zeromq-devel readline-devel openldap-devel wget libssh2-devel libxml++-devel doxygen patchelf perl git libmodbus-devel rsync cmake gcc-toolset-12-libatomic-devel boost1.78 boost1.78-devel boost1.78-python3-devel - -pipx install meson ninja sphinx - -if [ $DEVICEACCESS_BACKEND_ENABLE != "0" ] -then - - # Install EPICS - mkdir ${BUILD_DIR}/EPICS - cd ${BUILD_DIR}/EPICS - git clone --recursive --depth 1 --branch ${EPICS_VERSION} https://github.com/epics-base/epics-base.git - cd epics-base/ - make -j${PROCS} - export EPICS_BASE=${BUILD_DIR}/EPICS/epics-base - export EPICS_HOST_ARCH=$(${EPICS_BASE}/startup/EpicsHostArch) - export PATH=${EPICS_BASE}/bin/${EPICS_HOST_ARCH}:${PATH} - cd ${BUILD_DIR} - - # Install libtirpc-1.3 - wget https://downloads.sourceforge.net/libtirpc/libtirpc-${LIBTIRPC_VERSION}.tar.bz2 - tar -xvf libtirpc-${LIBTIRPC_VERSION}.tar.bz2 - cd libtirpc-${LIBTIRPC_VERSION}/ - ./configure --prefix=${BUILD_DIR}/local - make -j${PROCS} - make install - cd ${BUILD_DIR} - - # Install TINE - git clone --recursive http://doocs-git.desy.de/cgit/vendor/desy/mcs/tine/tine-package.git - cd tine-package/ - git checkout ${TINE_VERSION} - cd doocs/ - ./prepare - cd build.LINUX/ - make -j${PROCS} - make install - - # Ugly workaround for cp errors - cp ./src/*.so /usr/local/lib/ - cd ${BUILD_DIR} - - # Install GUL - git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/gul.git - build_ninja gul - - # Install DOOCS clientlib - export ENSHOST=ldap://xfelens1.desy.de - git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/clientlib.git - build_ninja clientlib - - # Install DOOCS serverlib - git clone --recursive --depth 1 --branch ${DOOCS_VERSION} https://mcs-gitlab.desy.de/doocs/doocs-core-libraries/serverlib.git - build_ninja serverlib +dnf -y install gcc-c++ gcc-toolset-14-libatomic-devel libxml++-devel boost1.78 boost1.78-devel boost1.78-python3-devel - # Install DOOCSServerTestHelper - git clone --recursive --depth 1 --branch ${DOOCS_SERVER_TEST_HELPER_VERSION} https://github.com/ChimeraTK/DoocsServerTestHelper.git - build_cmake DoocsServerTestHelper +g++ --version - #Install ChimeraTK-EPICS - git clone --recursive --depth 1 --branch ${CHIMERATK_EPICS_VERSION} https://github.com/ChimeraTK/EPICS-Interface.git - cd EPICS-Interface/ - mkdir builddir - cd builddir/ - cmake -DCMAKE_BUILD_TYPE=Release -DEPICS_VERSION=7 .. - make install -j${PROCS} - cd ${BUILD_DIR} - - # Install yajl - git clone --recursive --depth 1 --branch ${YAJL_VERSION} https://github.com/lloyd/yajl.git - build_cmake yajl - - #Install open62541 - git clone --recursive --depth 1 --branch ${OPEN62541_VERSION} https://github.com/open62541/open62541.git - build_cmake open62541 - - # Install DOOCSServerTestHelper - git clone --recursive --depth 1 --branch ${DOOCS_SERVER_TEST_HELPER_VERSION} https://github.com/ChimeraTK/DoocsServerTestHelper.git - build_cmake DoocsServerTestHelper - - #Install ChimeraTK-EPICS - git clone --recursive --depth 1 --branch ${CHIMERATK_EPICS_VERSION} https://github.com/ChimeraTK/EPICS-Interface.git - cd EPICS-Interface/ - mkdir builddir - cd builddir/ - cmake -DCMAKE_BUILD_TYPE=Release -DEPICS_VERSION=7 .. - make install -j${PROCS} - cd ${BUILD_DIR} - -fi +pipx install --force cmake==3.18 # Install cppext git clone --recursive --depth 1 --branch ${CPPEXT_VERSION} https://github.com/ChimeraTK/cppext.git @@ -143,32 +44,5 @@ build_cmake json git clone --recursive --depth 1 --branch ${DEVICEACCESS_VERSION} https://github.com/ChimeraTK/DeviceAccess.git build_cmake DeviceAccess -if [ $DEVICEACCESS_BACKEND_ENABLE != "0" ] -then - - # Install ChimeraTK-DeviceAccess-EpicsBackend - git clone --recursive --depth 1 --branch ${DEVICEACCESS_EPICS_VERSION} https://github.com/ChimeraTK/DeviceAccess-EpicsBackend.git - build_cmake DeviceAccess-EpicsBackend - - # Install ChimeraTK-DeviceAccess-OpcUaBackend - git clone --recursive --depth 1 --branch ${DEVICEACCESS_OPCUA_VERSION} https://github.com/ChimeraTK/DeviceAccess-OpcUaBackend.git - cd DeviceAccess-OpcUaBackend - mkdir builddir - cd builddir/ - cmake -DCMAKE_BUILD_TYPE=Release .. - make ChimeraTK-DeviceAccess-OPC-UA-Backend - make install/fast -j${PROCS} - cd ${BUILD_DIR} - - # Install ChimeraTK-DeviceAccess-DoocsBackend - git clone --recursive --depth 1 --branch ${DEVICEACCESS_DOOCS_VERSION} https://github.com/ChimeraTK/DeviceAccess-DoocsBackend.git - build_cmake DeviceAccess-DoocsBackend - - # Install ChimeraTK-DeviceAccess-ModbusBackend - git clone --recursive --depth 1 --branch ${DEVICEACCESS_MODBUS_VERSION} https://github.com/ChimeraTK/DeviceAccess-ModbusBackend.git - build_cmake DeviceAccess-ModbusBackend - -fi - cd ${PROJECT_PWD} diff --git a/pyproject.toml b/pyproject.toml index 80b9e21..29d485b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "scikit-build-core", "oldest-supported-numpy", - "cmake" + "cibuildwheel>=2.19" ] build-backend = "scikit_build_core.build" @@ -25,12 +25,12 @@ classifiers = [ "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Intended Audience :: Developers", "Programming Language :: Python :: 3", -"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", +"Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering" ] @@ -56,9 +56,8 @@ wheel.py-api = "cp312" [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" -skip = "cp36-* cp37-* *-musllinux* pp* *-win32 *_i686" +skip = "cp36-* cp37-* cp38-* cp314t-* *-musllinux* *-win32 *_i686" manylinux-x86_64-image = "manylinux_2_28" -manylinux-i686-image = "manylinux2_2_28" manylinux-aarch64-image = "manylinux2_2_28" # Necessary to see build output from the actual compilation @@ -74,26 +73,14 @@ test-skip = "*" DEVICEACCESS_BACKEND_ENABLE="1" # Dependencies versions -YAJL_VERSION="2.1.0" -EPICS_VERSION="R7.0.8" -CHIMERATK_EPICS_VERSION="01.00.00" -OPEN62541_VERSION="v1.3.1" -LIBTIRPC_VERSION="1.3.4" -TINE_VERSION="08ca83228932c3b08caf3b4c6578f1da8e65f5c5" -DOOCS_VERSION="DOOCSVERSION_24_3_1" -DOOCS_SERVER_TEST_HELPER_VERSION="01.07.00" CPPEXT_VERSION="01.05.02" EXPRTK_VERSION="01.04.01" NLOHMANN_JSON_VERSION="v3.7.3" DEVICEACCESS_VERSION="03.15.02" -DEVICEACCESS_EPICS_VERSION="01.00.00" -DEVICEACCESS_OPCUA_VERSION="01.03.00" -DEVICEACCESS_DOOCS_VERSION="01.09.01" -DEVICEACCESS_MODBUS_VERSION="01.05.00" # Add the build directory to the environment variables BUILD_DIR="${HOME}/build" -PKG_CONFIG_PATH="${BUILD_DIR}/local/lib/pkgconfig/:${BUILD_DIR}/EPICS/epics-base/lib/pkgconfig/:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" -LD_LIBRARY_PATH="${BUILD_DIR}/local/lib/:${BUILD_DIR}/EPICS/epics-base/lib/linux-x86_64/:${LD_LIBRARY_PATH}" +PKG_CONFIG_PATH="${BUILD_DIR}/local/lib/pkgconfig/:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" +LD_LIBRARY_PATH="${BUILD_DIR}/local/lib/:${LD_LIBRARY_PATH}" PATH="${BUILD_DIR}/local/bin/:${PATH}" From c578a6929def9933f53bdbfb86ddc065c8032b85 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Fri, 17 Oct 2025 22:16:13 +0200 Subject: [PATCH 16/34] feat: added deviceaccess c++ dependencies to pre_build_manylinux.sh --- pre_build_manylinux.sh | 6 ++++++ pyproject.toml | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh index 644cb3f..c15d9f2 100755 --- a/pre_build_manylinux.sh +++ b/pre_build_manylinux.sh @@ -2,6 +2,12 @@ # For manylinux_2_28 +# Dependencies versions +export CPPEXT_VERSION="01.05.02" +export EXPRTK_VERSION="01.04.01" +export NLOHMANN_JSON_VERSION="v3.7.3" +export DEVICEACCESS_VERSION="03.15.02" + export PROJECT_PWD=`pwd` export PROCS=`nproc` diff --git a/pyproject.toml b/pyproject.toml index 29d485b..45ead16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,12 +72,6 @@ test-skip = "*" # Enable additional backends compilation DEVICEACCESS_BACKEND_ENABLE="1" -# Dependencies versions -CPPEXT_VERSION="01.05.02" -EXPRTK_VERSION="01.04.01" -NLOHMANN_JSON_VERSION="v3.7.3" -DEVICEACCESS_VERSION="03.15.02" - # Add the build directory to the environment variables BUILD_DIR="${HOME}/build" PKG_CONFIG_PATH="${BUILD_DIR}/local/lib/pkgconfig/:/usr/lib/pkgconfig:${PKG_CONFIG_PATH}" From 690e3624496b982e38283194b7da9c8977223cf8 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sat, 18 Oct 2025 22:25:29 +0200 Subject: [PATCH 17/34] fix: pyprojects aarch64 cibuildwheel image name --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 45ead16..396c9d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ wheel.py-api = "cp312" before-all = "bash pre_build_manylinux.sh" skip = "cp36-* cp37-* cp38-* cp314t-* *-musllinux* *-win32 *_i686" manylinux-x86_64-image = "manylinux_2_28" -manylinux-aarch64-image = "manylinux2_2_28" +manylinux-aarch64-image = "manylinux_2_28" # Necessary to see build output from the actual compilation build-verbosity = 1 From 75f069cb25dddd676f94dbebd2d9889a8ada63f6 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sun, 19 Oct 2025 00:55:11 +0200 Subject: [PATCH 18/34] feat: advanced version --- pre_build_manylinux.sh | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh index 8897b86..e221dee 100755 --- a/pre_build_manylinux.sh +++ b/pre_build_manylinux.sh @@ -6,7 +6,7 @@ export CPPEXT_VERSION="01.05.02" export EXPRTK_VERSION="01.04.01" export NLOHMANN_JSON_VERSION="v3.12.0" -export DEVICEACCESS_VERSION="03.20.02" +export DEVICEACCESS_VERSION="03.19.00" export PROJECT_PWD=`pwd` export PROCS=`nproc` @@ -28,7 +28,7 @@ build_cmake () { # Install required system packages dnf install -y epel-release -dnf -y install gcc-c++ gcc-toolset-14-libatomic-devel libxml++-devel boost1.78 boost1.78-devel boost1.78-python3-devel +dnf -y install gcc-c++ gcc-toolset-14-libatomic-devel libxml++-devel boost1.78 boost1.78-system boost1.78-thread boost1.78-chrono boost1.78-filesystem boost1.78-python3-devel g++ --version diff --git a/pyproject.toml b/pyproject.toml index 396c9d3..c02d57c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = "scikit_build_core.build" [project] name = "deviceaccess" -version = "3.3.2" +version = "3.5.2" dependencies = [ "numpy<2", ] From 5da0f399e7a7ed401fafe8f7063cce5c5ddd9cf8 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sun, 19 Oct 2025 16:07:14 +0200 Subject: [PATCH 19/34] fix: using the latest version of cppext allows using the latest version of deviceaccess --- pre_build_manylinux.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh index e221dee..8a5d2b7 100755 --- a/pre_build_manylinux.sh +++ b/pre_build_manylinux.sh @@ -3,10 +3,10 @@ # For manylinux_2_28 # Dependencies versions -export CPPEXT_VERSION="01.05.02" -export EXPRTK_VERSION="01.04.01" +export CPPEXT_VERSION="01.07.00" +export EXPRTK_VERSION="01.04.02" export NLOHMANN_JSON_VERSION="v3.12.0" -export DEVICEACCESS_VERSION="03.19.00" +export DEVICEACCESS_VERSION="03.24.01" export PROJECT_PWD=`pwd` export PROCS=`nproc` From 5e279b9e5c68da5f7db3a00945132d971ecad238 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sun, 19 Oct 2025 18:23:25 +0200 Subject: [PATCH 20/34] fix: removed old files --- pyproject.toml | 20 +- src/deviceaccess/__init__.py | 1495 ---------------------------------- src/mtca4u/__init__.py | 651 --------------- 3 files changed, 9 insertions(+), 2157 deletions(-) delete mode 100644 src/deviceaccess/__init__.py delete mode 100644 src/mtca4u/__init__.py diff --git a/pyproject.toml b/pyproject.toml index eac20c8..20a2681 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,9 @@ [build-system] requires = [ "scikit-build-core", - "oldest-supported-numpy", - "cibuildwheel>=2.19", - "pybind11>=3.0" + "numpy", + "pybind11>=3.0", + "mypy>=1.0" ] build-backend = "scikit_build_core.build" @@ -12,7 +12,8 @@ build-backend = "scikit_build_core.build" name = "deviceaccess" version = "4.0.1" dependencies = [ - "numpy<2", + "numpy", + "mypy>=1.0" ] description = "Python bindings for the ChimeraTK's deviceaccess library" long_description = "The package provides binding for the ChimeraTK's deviceaccess library" @@ -26,6 +27,7 @@ classifiers = [ "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Intended Audience :: Developers", "Programming Language :: Python :: 3", +"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -33,6 +35,8 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", +"Programming Language :: Python :: Implementation :: GraalPy", +"Programming Language :: Python :: Implementation :: PyPy", "Topic :: Scientific/Engineering" ] @@ -41,10 +45,6 @@ Homepage = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" Documentation = "https://chimeratk.github.io/ChimeraTK-DeviceAccess-PythonBindings/head/html/" Repository = "https://github.com/ChimeraTK/DeviceAccess-PythonBindings" -[tool.scikit-build.wheel.packages] -deviceaccess = "src/deviceaccess" -mtca4u = "src/mtca4u" - [tool.scikit-build] # Protect the configuration against future changes in scikit-build-core minimum-version = "0.4" @@ -57,7 +57,7 @@ wheel.py-api = "cp312" [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" -skip = "cp36-* cp37-* cp38-* cp314t-* *-musllinux* *-win32 *_i686" +skip = "cp36-* cp37-* *-win32 *_i686" manylinux-x86_64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" @@ -70,8 +70,6 @@ build-verbosity = 1 test-skip = "*" [tool.cibuildwheel.linux.environment] -# Enable additional backends compilation -DEVICEACCESS_BACKEND_ENABLE="1" # Add the build directory to the environment variables BUILD_DIR="${HOME}/build" diff --git a/src/deviceaccess/__init__.py b/src/deviceaccess/__init__.py deleted file mode 100644 index dbb6559..0000000 --- a/src/deviceaccess/__init__.py +++ /dev/null @@ -1,1495 +0,0 @@ -# SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project -# SPDX-License-Identifier: LGPL-3.0-or-later -""" -This module offers the functionality of the DeviceAccess C++ library for python. - -The ChimeraTK DeviceAccess library provides an abstract interface for register -based devices. Registers are identified by a name and usually accessed though -an accessor object. Since this library also allows access to other control -system applications, it can be understood as the client library of the -ChimeraTK framework. - -More information on ChimeraTK can be found at the project's -`github.io `_. -""" - -from __future__ import annotations - -from typing import Sequence, Union -import numpy as np - -try: - from deviceaccess import _da_python_bindings as pb - from deviceaccess._da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType -except ModuleNotFoundError: - import _da_python_bindings as pb - from _da_python_bindings import AccessMode, DataValidity, TransferElementID, VersionNumber, FundamentalType - -import abc -import functools - -######################################################################################################################## - - -def setDMapFilePath(dmapFilePath: str) -> None: - """ - Set the location of the dmap file. - - The library will parse this dmap file for the device(alias) lookup. - Relative or absolute path of the dmap file (directory and file name). - - Examples - -------- - Setting the location of the dmap file - >>> import deviceaccess as da - >>> da.setDMapFilePath('deviceInformation/exampleCrate.dmap') - >>> dmap_path = da.getDMapFilePath() - >>> print(dmap_path) - deviceInformation/exampleCrate.dmap - """ - pb.setDmapFile(dmapFilePath) - -######################################################################################################################## - - -def getDMapFilePath() -> str: - """ - Returns the dmap file name which the library currently uses for looking up device(alias) names. - """ - return pb.getDmapFile() - -######################################################################################################################## -######################################################################################################################## - - -class GeneralRegisterAccessor(abc.ABC): - """ - This is a super class to avoid code duplication. It contains - methods that are common for the inheriting accessors. - - .. note:: As all accessors inherit from numpy's ndarray, the - behaviour concerning slicing and mathematical operations - is simimlar. Result accessors share the attributes of the left - operand, hence they are shallow copies of it. This leads to - functionality that is not available in the C++ implementation. - Please refer to the examples below. - - Examples - -------- - Slicing and writing. Operations are shared with the original accessor. - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> originAcc = dev.getTwoDRegisterAccessor(np.int32, "BOARD/DMA") - >>> originAcc.set(7) - >>> originAcc.write() # all elements are now 7. - >>> print(originAcc) - [[7 7 7 7 7 7] - [7 7 7 7 7 7] - [7 7 7 7 7 7] - [7 7 7 7 7 7]] - >>> channels = originAcc.getNChannels() - >>> elementsPerChannel = originAcc.getNElementsPerChannel() - >>> print(channels, elementsPerChannel) # there are 4 channels, each with 6 elements - 4 6 - >>> slicedAcc = originAcc[:][1] # the second element of every channel - >>> slicedAcc.set(21) # set these to 21 - >>> slicedAcc.write() - >>> print(originAcc) # originAcc is changed as well - [[ 7 7 7 7 7 7] - [21 21 21 21 21 21] - [ 7 7 7 7 7 7] - [ 7 7 7 7 7 7]] - - Results from mathematical operations are shallow copies of the left operand. - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> oneAcc = dev.getScalarRegisterAccessor(np.uint32, "ADC/WORD_CLK_CNT") - >>> oneAcc.set(72) - >>> oneAcc.write() # oneAcc is now 72 - >>> otherAcc = dev.getScalarRegisterAccessor(np.uint32, "ADC/WORD_CLK_CNT_1") - >>> otherAcc.set(47) - >>> otherAcc.write() # otherAcc is now 47. - >>> resultAcc = oneAcc + otherAcc # resultAcc's numpy buffer is now 119 - >>> print(resultAcc) - [119] - >>> resultAcc.write() # write() will also write into the register of oneAcc - >>> oneAcc.read() - >>> print(oneAcc) - [119] - >>> otherAcc.read() # the buffer's and registers of the right operand are not touched - >>> print(otherAcc) - [47] - >>> resultAcc.getName() # the resultAcc is a shallow copy of the left operand - '/ADC/WORD_CLK_CNT' - """ - - def read(self) -> None: - """ - Read the data from the device. - - If :py:obj:`AccessMode.wait_for_new_data` was set, this function - will block until new data has arrived. Otherwise, it still might block - for a short time until the data transfer is complete. - - Examples - -------- - Reading from a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.read() - >>> acc - ScalarRegisterAccessor([99], dtype=int32) - - """ - raise Exception('Not implemented in base class') - - def readLatest(self) -> bool: - """ - Read the latest value, discarding any other update since the last read if present. - - Otherwise, this function is identical to :py:func:`readNonBlocking`, - i.e. it will never wait for new values, and it will return - whether a new value was available if - :py:obj:`AccessMode.wait_for_new_data` is set. - """ - raise Exception('Not implemented in base class') - - def readNonBlocking(self) -> bool: - """ - Read the next value, if available in the input buffer. - - If :py:obj:`AccessMode.wait_for_new_data` was set, this function returns - immediately and the return value indicated if a new value was - available (`True`) or not (`False`). - - If :py:obj:`AccessMode.wait_for_new_data` was not set, this function is - identical to :py:meth:`.read` , which will still return quickly. Depending on - the actual transfer implementation, the backend might need to - transfer data to obtain the current value before returning. Also - this function is not guaranteed to be lock free. The return value - will be always true in this mode. - """ - raise Exception('Not implemented in base class') - - def write(self) -> bool: - """ - Write the data to device. - - The return value is true, old data was lost on the write transfer - (e.g. due to an buffer overflow). In case of an unbuffered write - transfer, the return value will always be false. - - Examples - -------- - Writing to a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> reference = [199] - >>> acc.set(reference) - >>> acc.write() - ScalarRegisterAccessor([199], dtype=int32) - """ - raise Exception('Not implemented in base class') - - def writeDestructively(self) -> bool: - """ - Just like :py:meth:`.write`, but allows the implementation - to destroy the content of the user buffer in the process. - - The application must expect the user buffer of the - TransferElement to contain undefined data after calling this function. - """ - raise Exception('Not implemented in base class') - - def getName(self) -> str: - """ - Returns the name that identifies the process variable. - - Examples - -------- - Getting the name of a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.getName() - '/ADC/WORD_CLK_CNT_1' - - """ - return self._accessor.getName() - - def getUnit(self) -> str: - """ - Returns the engineering unit. - - If none was specified, it will default to "n./a." - - Examples - -------- - Getting the engineering unit of a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.getUnit() - 'n./a.' - - """ - return self._accessor.getUnit() - - def getValueType(self) -> UserType: - """ - Returns the type for the userType of this transfer element, that - was given at the initialization of the accessor. - - Examples - -------- - Getting the userType of a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.getValueType() - numpy.int32 - - """ - return self.userType - - def getDescription(self) -> str: - """ - Returns the description of this variable/register, if there is any. - - Examples - -------- - Getting the description of a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.getDescription() - '' - - """ - return self._accessor.getDescription() - - def getAccessModeFlags(self) -> Sequence[AccessMode]: - """ - Returns the access modes flags, that - were given at the initialization of the accessor. - - Examples - -------- - Getting the access modes flags of a OneDRegisterAccessor with the wait_for_new_data flag: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor( - np.int32, "MODULE1/TEST_AREA_PUSH", 0, 0, [da.AccessMode.wait_for_new_data]) - >>> acc.getAccessModeFlags() - [da.AccessMode.wait_for_new_data] - - """ - accessModeFlagStrings = self._accessor.getAccessModeFlagsString() - flags = [] - for flag in accessModeFlagStrings.split(","): - if flag == 'wait_for_new_data': - flags.append(AccessMode.wait_for_new_data) - if flag == 'raw': - flags.append(AccessMode.raw) - - return flags - - def getVersionNumber(self) -> VersionNumber: - """ - Returns the version number that is associated with the last transfer - (i.e. last read or write). See :py:class:`VersionNumber` for details. - - Examples - -------- - Getting the version number of a OneDRegisterAccessor: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor( - np.int32, "MODULE1/TEST_AREA_PUSH", 0, 0, [da.AccessMode.wait_for_new_data]) - >>> acc.getVersionNumber() - <_da_python_bindings.VersionNumber at 0x7f52b5f8a740> - - """ - return self._accessor.getVersionNumber() - - def isReadOnly(self) -> bool: - """ - Check if transfer element is read only, i.e. - it is readable but not writeable. - - Examples - -------- - Getting the readOnly status of a OneDRegisterAccessor: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor( - np.int32, "MODULE1/TEST_AREA_PUSH", 0, 0, [da.AccessMode.wait_for_new_data]) - >>> acc.isReadOnly() - True - - """ - return self._accessor.isReadOnly() - - def isReadable(self) -> bool: - """ - Check if transfer element is readable. - - It throws an exception if you try to read and :py:meth:`isReadable` is not True. - - Examples - -------- - Getting the readable status of a OneDRegisterAccessor: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor( - np.int32, "MODULE1/TEST_AREA_PUSH", 0, 0, [da.AccessMode.wait_for_new_data]) - >>> acc.isReadable() - True - - """ - return self._accessor.isReadable() - - def isWriteable(self) -> bool: - """ - Check if transfer element is writeable. - - It throws an exception if you try to write and :py:meth:`isWriteable` is not True. - - Examples - -------- - Getting the writeable status of a OneDRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor( - np.int32, "MODULE1/TEST_AREA_PUSH", 0, 0, [da.AccessMode.wait_for_new_data]) - >>> acc.isReadable() - False - - """ - return self._accessor.isWriteable() - - def isInitialised(self) -> bool: - """ - Return if the accessor is properly initialized. - - It is initialized if it was constructed passing the - pointer to an implementation, it is not - initialized if it was constructed only using the placeholder - constructor without arguments. Which should currently not happen, - as the registerPath is a required argument for this module, but might - be true for other implementations. - - Examples - -------- - Getting the initialized status of a OneDRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor( - np.int32, "MODULE1/TEST_AREA_PUSH", 0, 0, [da.AccessMode.wait_for_new_data]) - >>> acc.isInitialised() - True - - """ - return self._accessor.isInitialised() - - def setDataValidity(self, valid=DataValidity.ok) -> None: - """ - Associate a persistent data storage object to be updated - on each write operation of this ProcessArray. - - If no persistent data storage as associated previously, the - value from the persistent storage is read and send to the receiver. - - .. note:: A call to this function will be ignored, if the - TransferElement does not support persistent data storage - (e.g. read-only variables or device registers) - - Parameters - ---------- - valid: DataValidity - DataValidity.ok or DataValidity.faulty - - """ - self._accessor.setDataValidity(valid) - - def dataValidity(self) -> DataValidity: - """ - Return current validity of the data. - - Will always return :py:obj:`DataValidity.ok` if the backend does not support it - - """ - return self._accessor.dataValidity() - - def getId(self) -> TransferElementID: - """ - Obtain unique ID for the actual implementation of this TransferElement. - - This means that e.g. two instances of ScalarRegisterAccessor - created by the same call to :py:meth:`Device.getScalarRegisterAccessor` - will have the same ID, while two instances obtained by to - difference calls to :py:meth:`Device.getScalarRegisterAccessor` - will have a different ID even when accessing the very same register. - - Examples - -------- - Getting the name of a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.getId() - <_da_python_bindings.TransferElementID at 0x7f5298a8f400> - - """ - return self._accessor.getId() - - def interrupt(self) -> None: - """ - Place a thread interrupted exception on the read queue of this accessor, - so the thread currently waiting in a blocking read() will terminate. May - only be called for accessors with AccessMode.wait_for_new_data. - """ - self._accessor.interrupt() - -######################################################################################################################## -######################################################################################################################## - - -class NumpyGeneralRegisterAccessor(GeneralRegisterAccessor): - def __init__(self, channels, elementsPerChannel, userType, accessor, - accessModeFlags: Sequence[AccessMode] = None) -> None: - dtype = userType - if dtype == str: - dtype = 'U1' - if channels is None: - self.__array = np.zeros(shape=(elementsPerChannel), dtype=dtype) - else: - self.__array = np.zeros(shape=(channels, elementsPerChannel), dtype=dtype) - self._accessor = accessor - self.userType = userType - self._AccessModeFlags = accessModeFlags - - def get(self) -> np.ndarray: - return self.__array - - def set(self, value) -> None: - """ - Set the user buffer to the given value. - - The value shape has to match the accessor, any mismatch will throw an exception. - Different types will be converted to the userType of the accessor. - - Parameters - ---------- - value : numpy.array or compatible type - The new content of the user buffer. - - Examples - -------- - Setting a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.read() - ScalarRegisterAccessor([74678], dtype=int32) - >>> acc.set([-23]) - >>> acc.write() - ScalarRegisterAccessor([-23], dtype=int32) - - Setting a OneDRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor(np.int32, "BOARD/WORD_CLK_MUX") - >>> acc.read() - OneDRegisterAccessor([342011132 958674678 342011132 958674678], dtype=int32) - >>> acc.set([1, 9, 42, -23]) - >>> acc.write() - OneDRegisterAccessor([ 1, 9, 42, -23], dtype=int32) - - Setting a TwoDRegisterAccessor - >>> import deviceaccess as da - >>> dda.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getTwoDRegisterAccessor(np.int32, "BOARD/DMA") - >>> acc.read() - TwoDRegisterAccessor([[ 0 4 16 36 64 100] - [ 0 0 0 0 0 0] - [ 1 9 25 49 81 121] - [ 0 0 0 0 0 0]], dtype=int32) - >>> channels = acc.getNChannels() - >>> elementsPerChannel = acc.getNElementsPerChannel() - >>> reference = [ - >>> [i*j+i+j+12 for j in range(elementsPerChannel)] for i in range(channels)] - >>> acc.set(reference) - >>> acc.write() - TwoDRegisterAccessor([[12, 13, 14, 15, 16, 17], - [13, 15, 17, 19, 21, 23], - [14, 17, 20, 23, 26, 29], - [15, 19, 23, 27, 31, 35]], dtype=int32) - - """ - # TODO: maybe we should store scalars as such? - if self.__array.dtype.kind != 'U': - if not np.isscalar(value): - newarray = np.asarray(value, dtype=self.__array.dtype) - else: - newarray = np.asarray([value], dtype=self.__array.dtype) - else: - if not np.isscalar(value): - newarray = np.asarray(value) - else: - newarray = np.asarray([value]) - - if self.__array.shape != newarray.shape: - raise ValueError('The shape of the provided value is incompatible with this accessor.') - self.__array = newarray - - # pass through all (non-operator) functions to the np array (unless defined here) - def __getattr__(self, name): - if name == "__array_struct__": - # This fixes issues with string type accessors. Casting a string accessor into an np.array via either - # "np.array(myAccessor)" or "np.asarray(myAccessor)" delivers incorrect results if __array_struct__ - # is passed through, while it works when not redirecting it here. - return super().__getattr__(name) - return self.__array.__getattribute__(name) - - # comparison operators - def __lt__(self, other) -> bool: - return self.__array < other - - def __le__(self, other) -> bool: - return self.__array <= other - - def __gt__(self, other) -> bool: - return self.__array > other - - def __ge__(self, other) -> bool: - return self.__array >= other - - def __eq__(self, other) -> bool: - return self.__array == other - - def __ne__(self, other) -> bool: - return self.__array != other - - # conversion operators - def __str__(self) -> str: - return str(self.__array) - - def __bool__(self) -> bool: - return bool(self.__array) - - # subscript operator as getter - def __getitem__(self, key) -> UserType: - return self.__array[key] - - # subscript operator as setter - def __setitem__(self, key, newvalue) -> None: - self.__array[key] = newvalue - - # binary operators - def __add__(self, other) -> np.ndarray: - return self.__array.__add__(other) - - def __sub__(self, other) -> np.ndarray: - return self.__array.__sub__(other) - - def __mul__(self, other) -> np.ndarray: - return self.__array.__mul__(other) - - def __truediv__(self, other) -> np.ndarray: - return self.__array.__truediv__(other) - - def __floordiv__(self, other) -> np.ndarray: - return self.__array.__floordiv__(other) - - def __mod__(self, other) -> np.ndarray: - return self.__array.__mod__(other) - - def __pow__(self, other) -> np.ndarray: - return self.__array.__pow__(other) - - def __rshift__(self, other) -> np.ndarray: - return self.__array.__rshift__(other) - - def ___lshift___mul__(self, other) -> np.ndarray: - return self.__array.__lshift__(other) - - def __and__(self, other) -> np.ndarray: - return self.__array.__and__(other) - - def __or__(self, other) -> np.ndarray: - return self.__array.__or__(other) - - def __xor__(self, other) -> np.ndarray: - return self.__array.__xor__(other) - - # assignment operators - def __isub__(self, other) -> RegisterAccessor: - self.__array.__isub__(other) - return self - - def __iadd__(self, other) -> RegisterAccessor: - self.__array.__iadd__(other) - return self - - def __imul__(self, other) -> RegisterAccessor: - self.__array.__imul__(other) - return self - - def __idiv__(self, other) -> RegisterAccessor: - self.__array.__idiv__(other) - return self - - def __ifloordiv__(self, other) -> RegisterAccessor: - self.__array.__ifloordiv__(other) - return self - - def __imod__(self, other) -> RegisterAccessor: - self.__array.__imod__(other) - return self - - def __ipow__(self, other) -> RegisterAccessor: - self.__array.__ipow__(other) - return self - - def __irshift__(self, other) -> RegisterAccessor: - self.__array.__irshift__(other) - return self - - def __ilshift__(self, other) -> RegisterAccessor: - self.__array.__ilshift__(other) - return self - - def __iand__(self, other) -> RegisterAccessor: - self.__array.__iand__(other) - return self - - def __ior__(self, other) -> RegisterAccessor: - self.__array.__ior__(other) - return self - - def __ixor__(self, other) -> RegisterAccessor: - self.__array.__ixor__(other) - return self - - # unary operators - def __neg__(self) -> np.ndarray: - return self.__array.__neg__() - - def __pos__(self) -> np.ndarray: - return self.__array.__pos__() - - def __invert__(self) -> np.ndarray: - return self.__array.__invert__() - - # accessor functions - def read(self) -> None: - self.__array = self._accessor.read(self.__array) - - def readNonBlocking(self) -> None: - (status, self.__array) = self._accessor.readNonBlocking(self.__array) - return status - - def readLatest(self) -> None: - (status, self.__array) = self._accessor.readLatest(self.__array) - return status - - def write(self) -> None: - return self._accessor.write(self.__array) - - def writeDestructively(self) -> None: - return self._accessor.writeDestructively(self.__array) - - def getAsCooked(self, userType, channel: int, element: int): - userTypeFunctionExtension = Device._userTypeExtensions.get(userType, None) - if not userTypeFunctionExtension: - raise SyntaxError("userType not supported" + str(Device._userTypeExtensions)) - getAsCooked = getattr(self._accessor, "getAsCooked_" + userTypeFunctionExtension) - return getAsCooked(self.__array, channel, element) - - def setAsCooked(self, channel: int, element: int, value): - self.__array = self._accessor.setAsCooked(self.__array, channel, element, value) - - # transfer doc strings from base class for accessor functions - read.__doc__ = GeneralRegisterAccessor.read.__doc__ - readNonBlocking.__doc__ = GeneralRegisterAccessor.readNonBlocking.__doc__ - readLatest.__doc__ = GeneralRegisterAccessor.readLatest.__doc__ - write.__doc__ = GeneralRegisterAccessor.write.__doc__ - writeDestructively.__doc__ = GeneralRegisterAccessor.writeDestructively.__doc__ - -######################################################################################################################## -######################################################################################################################## - - -class TwoDRegisterAccessor(NumpyGeneralRegisterAccessor): - """ - Accessor class to read and write registers transparently by using the accessor object - like a 2D array of the type UserType. - - Conversion to and from the UserType will be handled by a data - converter matching the register description in the map (if applicable). - - .. note:: As all accessors inherit from :py:obj:`GeneralRegisterAccessor`, - please refer to the respective examples for the behaviour of - mathematical operations and slicing with accessors. - - .. note:: Transfers between the device and the internal buffer need - to be triggered using the read() and write() functions before reading - from resp. after writing to the buffer using the operators. - """ - - def __init__(self, userType, accessor, accessModeFlags: Sequence[AccessMode] = None) -> None: - channels = accessor.getNChannels() - elementsPerChannel = accessor.getNElementsPerChannel() - super().__init__(channels, elementsPerChannel, userType, accessor, accessModeFlags) - - def getNChannels(self) -> int: - """ - Return number of channels. - """ - return self._accessor.getNChannels() - - def getNElementsPerChannel(self) -> int: - """ - Return number of elements/samples per channel. - """ - return self._accessor.getNElementsPerChannel() - -######################################################################################################################## -######################################################################################################################## - - -class OneDRegisterAccessor(NumpyGeneralRegisterAccessor): - """ - Accessor class to read and write registers transparently by using the accessor object - like a vector of the type UserType. - - Conversion to and from the UserType will be handled by a data - converter matching the register description in the map (if applicable). - - .. note:: As all accessors inherit from :py:obj:`GeneralRegisterAccessor`, - please refer to the respective examples for the behaviour of - mathematical operations and slicing with accessors. - - .. note:: Transfers between the device and the internal buffer need - to be triggered using the read() and write() functions before reading - from resp. after writing to the buffer using the operators. - """ - - def __init__(self, userType, accessor, accessModeFlags: Sequence[AccessMode]) -> None: - elements = accessor.getNElements() - super().__init__(None, elements, userType, accessor, accessModeFlags) - - def getNElements(self) -> int: - """ - Return number of elements/samples in the register. - """ - return self._accessor.getNElements() - -######################################################################################################################## -######################################################################################################################## - - -class ScalarRegisterAccessor(NumpyGeneralRegisterAccessor): - """ - Accessor class to read and write scalar registers transparently by using the accessor object - like a vector of the type UserType. - - Conversion to and from the UserType will be handled by a data - converter matching the register description in the map (if applicable). - - .. note:: As all accessors inherit from :py:obj:`GeneralRegisterAccessor`, - please refer to the respective examples for the behaviour of - mathematical operations and slicing with accessors. - - .. note:: Transfers between the device and the internal buffer need - to be triggered using the read() and write() functions before reading - from resp. after writing to the buffer using the operators. - """ - - def __init__(self, userType, accessor, accessModeFlags: Sequence[AccessMode] = None) -> None: - super().__init__(None, 1, userType, accessor, accessModeFlags) - - def readAndGet(self) -> np.number: - """ - Convenience function to read and return a value of UserType. - - Examples - -------- - Reading and Getting from a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> dev.write("ADC/WORD_CLK_CNT_1", 37) - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.readAndGet() - 37 - - """ - return self._accessor.readAndGet() - - def setAndWrite(self, newValue: np.number, versionNumber: VersionNumber = None) -> None: - """ - Convenience function to set and write new value. - - Parameters - ---------- - newValue : numpy.number and compatible types - The content that should be written to the register. - - versionNumber: VersionNumber, optional - The versionNumber that should be used for the write action. - - Examples - -------- - Reading and Getting from a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.setAndWrite(38) - >>> acc.readAndGet() - 38 - - """ - if versionNumber is None: - versionNumber = VersionNumber() - self._accessor.setAndWrite(newValue, versionNumber) - - def writeIfDifferent(self, newValue: np.number, versionNumber: VersionNumber = None, - validity: DataValidity = DataValidity.ok) -> None: - """ - Convenience function to set and write new value if it differes from the current value. - - The given version number is only used in case the value differs. - - Parameters - ---------- - newValue : numpy.number and compatible types - The contentthat should be written to the register. - - versionmNumber: VersionNumber, optional - The versionNumber that should be used for the write action. - - Examples - -------- - Reading and Getting from a ScalarRegisterAccessor - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int32, "ADC/WORD_CLK_CNT_1") - >>> acc.setAndWrite(38) - >>> acc.writeIfDifferent(38) # will not write - - """ - if versionNumber is None: - versionNumber = VersionNumber.getNullVersion() - self._accessor.writeIfDifferent(newValue, versionNumber, validity) - -######################################################################################################################## -######################################################################################################################## - - -class VoidRegisterAccessor(GeneralRegisterAccessor, np.ndarray): - """ - Accessor class to read and write void registers transparently by using the accessor object.. - - .. note:: Transfers between the device and the internal buffer need - to be triggered using the read() and write() functions before reading - from resp. after writing to the buffer using the operators. - """ - def __new__(cls, accessor, accessModeFlags: Sequence[AccessMode] = None) -> None: - obj = np.asarray( - np.zeros(shape=(1, 1), dtype=np.void)).view(cls) - obj = obj.ravel() - obj._accessor = accessor - obj._AccessModeFlags = accessModeFlags - return obj - - def __array_finalize__(self, obj) -> None: - if obj is None: - return - self._accessor = getattr(obj, '_accessor', None) - self._AccessModeFlags = getattr(obj, '_AccessModeFlags', None) - - def read(self) -> None: - self._accessor.read() - - def readLatest(self) -> bool: - return self._accessor.readLatest() - - def readNonBlocking(self) -> bool: - return self._accessor.readNonBlocking() - - def write(self) -> bool: - return self._accessor.write() - - def writeDestructively(self) -> bool: - return self._accessor.writeDestructively() - -######################################################################################################################## -######################################################################################################################## - - -class Device: - """ Construct Device from user provided device information - - This constructor is used to open a device listed in the dmap file. - - Parameters - ---------- - aliasName : str - The device alias/name in the dmap file for the hardware - - Examples - -------- - Creating a device using a dmap file: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - # CARD_WITH_MODULES is an alias in the dmap file above - >>> dev = da.Device("CARD_WITH_MODULES") - Supports also with-statements: - >>> with da.Device('CARD_WITH_MODULES') as dev: - >>> reg_value = dev.read('/PATH/TO/REGISTER') - """ - # dict to get the corresponding function for each datatype - _userTypeExtensions = { - np.int8: "int8", - np.uint8: "uint8", - np.int16: "int16", - np.uint16: "uint16", - np.int32: "int32", - np.uint32: "uint32", - np.int64: "int64", - np.uint64: "uint64", - np.single: "float", - np.float32: "float", - np.double: "double", - np.float64: "double", - np.bool_: "boolean", - bool: "boolean", - str: "string", - } - - def __init__(self, aliasName: str = None) -> None: - self.aliasName = aliasName - if aliasName: - self._device = pb.getDevice(aliasName) - else: - self._device = pb.getDevice_no_alias() - - def __enter__(self) -> Device: - """Helper function for with-statements""" - if self.aliasName is None: - raise SyntaxError('In a with-statement, an alias has to be provided in the device constructor!') - else: - self._device.open(self.aliasName) - return self - - def __exit__(self, *args) -> None: - """Helper function for with-statements""" - self._device.close() - - def open(self, aliasName: str = None) -> None: - """Open a :py:class:`Device` - - This method has to be called after the initialization to get accessors. It - can also re-opens a :py:class:`Device` after :py:func:`close` was called. - If no aliasName was giving during initialization, it is needed by - this method. - - Parameters - ---------- - aliasName : str, optional - The :py:class:`Device` alias/name in the dmap file for the hardware - - Examples - -------- - Opening a :py:class:`Device` without aliasName, as it has already been supplied at creation: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - - Opening a :py:class:`Device` with aliasName, as it has non been supplied at creation: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device() - >>> dev.open("CARD_WITH_MODULES") - """ - if not aliasName: - if self.aliasName: - self._device.open() - else: - raise SyntaxError( - "No backend is assigned: the device is not opened" - ) - elif not self.aliasName: - self.aliasName = aliasName - self._device.open(aliasName) - else: - raise SyntaxError( - "Device has not been opened correctly: the device is not opened" - ) - - def close(self) -> None: - """Close the :py:class:`Device`. - - The connection with the alias name is kept so the device can be re-opened - using the :py:func:`open` function without argument. - - Examples - -------- - Closing an open device: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> dev.close() - """ - self._device.close() - - def getCatalogueMetadata(self, metaTag: str) -> str: - """Provides access to catalogue metadata. - This method uses the metadata catalogue to access the value, defined at the beginning of a mapp file, i.e.: - @TAG value - By convention the tag is in uppercase. - - Parameters - ---------- - metaTag - Tag of the metadata, i.e.: MAPFILE_REVISION - - Returns - ------- - String containing the metadata - """ - return self._device.getCatalogueMetadata(metaTag) - - def getTwoDRegisterAccessor( - self, - userType, - registerPathName: str, - numberOfElements: int = 0, - elementsOffset: int = 0, - accessModeFlags: Sequence[AccessMode] = None) -> TwoDRegisterAccessor: - """Get a :py:class:`TwoDRegisterAccessor` object for the given register. - - This allows to read and write transparently 2-dimensional registers. - The optional arguments allow to restrict the accessor to a region of - interest in the 2D register. - - Parameters - ---------- - userType : type or numpy.dtype - The userType for the accessor. Can be of any of the numpy.dtype - combinations of float, int, 32 or 64-bit. Integers are also - supported as signed, unsigned, 8 and 16-bit. E.g. - `numpy.uint8`, or `numpy.float32`. There are also `str`, `bool`. - - registerPathName : str - The name of the register to read from. - - numberOfElements : int, optional - Specifies the number of elements per channel to read from the register. - The width and fixed point representation of the register - element are internally obtained from the map file. - - The method returns all elements in the register if this parameter is - omitted or when its value is set as 0. - - If the value provided as this parameter exceeds the register size, an - array with all elements upto the last element is returned. - - elementsOffset : int, optional - This is a zero indexed offset from the first element of the register. When - an elementIndexInRegister parameter is specified, the method reads out - elements starting from this element index. The element at the index - position is included in the read as well. - - accessModeFlags : list, optional - A list to specify the access mode. It allows e.g. to enable raw access. - See :py:class:`AccessMode` documentation for more details. - Passing an access mode flag which is not supported by the backend or the given - register will raise a NOT_IMPLEMENTED DeviceException. - - Examples - -------- - Getting a Two-D Register Accessor of type uint8 from DMA; which is 6 elements long and has 4 channels: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getTwoDRegisterAccessor(np.float32, "BOARD/DMA", 0, 0, []) - >>> acc.read() - >>> acc - TwoDRegisterAccessor([[12., 13., 14., 15., 16., 17.], - [13., 15., 17., 19., 21., 23.], - [14., 17., 20., 23., 26., 29.], - [15., 19., 23., 27., 31., 35.]], dtype=float32) - - Getting a Two-D Register Accessor of type float64 from register "WORD_CLK_MUX" is 4 elements long. - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getTwoDRegisterAccessor(np.uint8, "BOARD/WORD_CLK_MUX") - >>> acc.read() - >>> acc - TwoDRegisterAccessor([[42, 43, 44, 45]], dtype=uint8) - """ - if not accessModeFlags: - accessModeFlags = [] - # get function name according to userType - userTypeFunctionExtension = self._userTypeExtensions.get(userType, None) - if not userTypeFunctionExtension: - raise SyntaxError("userType not supported") - getTwoDAccessor = getattr( - self._device, "getTwoDAccessor_" + userTypeFunctionExtension) - - accessor = getTwoDAccessor( - registerPathName, numberOfElements, elementsOffset, accessModeFlags) - twoDRegisterAccessor = TwoDRegisterAccessor( - userType, accessor, accessModeFlags) - return twoDRegisterAccessor - - def getOneDRegisterAccessor( - self, - userType, - registerPathName: str, - numberOfElements: int = 0, - elementsOffset: int = 0, - accessModeFlags: Sequence[AccessMode] = None) -> OneDRegisterAccessor: - """Get a :py:class:`OneDRegisterAccessor` object for the given register. - - The OneDRegisterAccessor allows to read and write registers transparently by using - the accessor object like a vector of the type UserType. If needed, the conversion - to and from the UserType will be handled by a data converter matching the - register description in e.g. a map file. - - Parameters - ---------- - userType : type or numpy.dtype - The userType for the accessor. Can be of any of the numpy.dtype - combinations of float, int, 32 or 64-bit. Integers are also - supported as signed, unsigned, 8 and 16-bit. E.g. - `numpy.uint8`, or `numpy.float32`. There are also `str`, `bool`. - - registerPathName : str - The name of the register to read from. - - numberOfElements : int, optional - Specifies the number of elements per channel to read from the register. - The width and fixed point representation of the register - element are internally obtained from the map file. - - The method returns all elements in the register if this parameter is - omitted or when its value is set as 0. - - If the value provided as this parameter exceeds the register size, an - array with all elements upto the last element is returned. - - elementsOffset : int, optional - This is a zero indexed offset from the first element of the register. When - an elementIndexInRegister parameter is specified, the method reads out - elements starting from this element index. The element at the index - position is included in the read as well. - - accessModeFlags : list, optional - A list to specify the access mode. It allows e.g. to enable raw access. - See :py:class:`AccessMode` documentation for more details. - Passing an access mode flag which is not supported by the backend or the given - register will raise a NOT_IMPLEMENTED DeviceException. - - Examples - -------- - Getting a One-D Register Accessor of type uint8 from WORD_STATUS; which is 1 element long: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor(np.uint8, "BOARD/WORD_STATUS") - >>> acc.read() - >>> acc - OneDRegisterAccessor([255], dtype=uint8) - - Getting a One-D Register Accessor of type float64 from register "WORD_CLK_MUX" is 4 elements long. - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getOneDRegisterAccessor(np.uint8, "BOARD/WORD_CLK_MUX") - >>> acc.read() - >>> acc - OneDRegisterAccessor([42., 43., 44., 45.], dtype=float32) - """ - if not accessModeFlags: - accessModeFlags = [] - # get function name according to userType - userTypeFunctionExtension = self._userTypeExtensions.get(userType, None) - if not userTypeFunctionExtension: - raise SyntaxError("userType not supported") - getOneDAccessor = getattr( - self._device, "getOneDAccessor_" + userTypeFunctionExtension) - - accessor = getOneDAccessor( - registerPathName, numberOfElements, elementsOffset, accessModeFlags) - oneDRegisterAccessor = OneDRegisterAccessor( - userType, accessor, accessModeFlags) - return oneDRegisterAccessor - - def getScalarRegisterAccessor( - self, - userType, - registerPathName: str, - elementsOffset: int = 0, - accessModeFlags: Sequence[AccessMode] = None) -> ScalarRegisterAccessor: - """Get a :py:class:`ScalarRegisterAccessor` object for the given register. - - The ScalarRegisterObject allows to read and write registers transparently by using - the accessor object like a variable of the type UserType. If needed, the conversion - to and from the UserType will be handled by a data converter matching the register - description in e.g. a map file. - - Parameters - ---------- - userType : type or numpy.dtype - The userType for the accessor. Can be of any of the numpy.dtype - combinations of float, int, 32 or 64-bit. Integers are also - supported as signed, unsigned, 8 and 16-bit. E.g. - `numpy.uint8`, or `numpy.float32`. There are also `str`, `bool`. - - registerPathName : str - The name of the register to read from. - - elementsOffset : int, optional - This is a zero indexed offset from the first element of the register. When - an elementIndexInRegister parameter is specified, the method reads out - elements starting from this element index. The element at the index - position is included in the read as well. - - accessModeFlags : list, optional - A list to specify the access mode. It allows e.g. to enable raw access. - See :py:class:`AccessMode` documentation for more details. - Passing an access mode flag which is not supported by the backend or the given - register will raise a NOT_IMPLEMENTED DeviceException. - - Examples - -------- - Getting a scalar Register Accessor of type int16 from WORD_STATUS: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/exampleCrate.dmap") - >>> dev = da.Device("CARD_WITH_MODULES") - >>> dev.open() - >>> acc = dev.getScalarRegisterAccessor(np.int16, "ADC/WORD_STATUS") - >>> acc.read() - >>> acc - ScalarRegisterAccessor([32767], dtype=int16) - - """ - if not accessModeFlags: - accessModeFlags = [] - # get function name according to userType - userTypeFunctionExtension = self._userTypeExtensions.get(userType, None) - if not userTypeFunctionExtension: - raise SyntaxError("userType not supported") - getScalarAccessor = getattr( - self._device, "getScalarAccessor_" + userTypeFunctionExtension) - - accessor = getScalarAccessor( - registerPathName, elementsOffset, accessModeFlags) - scalarRegisterAccessor = ScalarRegisterAccessor( - userType, accessor, accessModeFlags) - return scalarRegisterAccessor - - def getVoidRegisterAccessor(self, registerPathName: str, accessModeFlags: Sequence[AccessMode] = None) -> VoidRegisterAccessor: - """Get a :py:class:`VoidRegisterAccessor` object for the given register. - - The VoidRegisterAccessor allows to read and write registers. Getting a read - accessor is only possible with the wait_for_new_data flag. This access mode - will be rejected for write accessors. - - Parameters - ---------- - registerPathName : str - The name of the register to read from. - - accessModeFlags : list, optional - A list to specify the access mode. It allows e.g. to enable wait_for_new_data access. - See :py:class:`AccessMode` documentation for more details. - Passing an access mode flag which is not supported by the backend or the given - register will raise a NOT_IMPLEMENTED DeviceException. - - Examples - -------- - Sending interrupts per Void Accessor: - >>> import deviceaccess as da - >>> da.setDMapFilePath("deviceInformation/push.dmap") - >>> dev = da.Device("SHARED_RAW_DEVICE") - >>> dev.open() - >>> dev.activateAsyncRead() - >>> - >>> writeAcc = dev.getOneDRegisterAccessor(np.int32, "MODULE1/TEST_AREA") - >>> arr1to10 = np.array(range(1, 11), dtype=np.int32) - >>> writeAcc.set(arr1to10) - >>> writeAcc.write() - >>> - >>> readAcc = dev.getOneDRegisterAccessor( - >>> np.int32, "MODULE1/TEST_AREA_PUSH", 0, 0, [da.AccessMode.wait_for_new_data]) - >>> readAcc.read() # first read is always non-blocking - OneDRegisterAccessor([ 1 2 3 4 5 6 7 8 9 10]], dtype=int32) - >>> # double values of writeAccReg - >>> writeAcc += arr1to10 - >>> writeAcc.write() - >>> interruptAcc = dev.getVoidRegisterAccessor("DUMMY_INTERRUPT_2") - >>> interruptAcc.write() # interrupt needed, otherwise second read would be blocking - >>> - >>> readAcc.read() - OneDRegisterAccessor( - [ 2, 4, 6, 8, 10, 12, 14, 16, 18, 20], dtype=int32) - - """ - if not accessModeFlags: - accessModeFlags = [] - accessor = self._device.getVoidAccessor( - registerPathName, accessModeFlags) - voidRegisterAccessor = VoidRegisterAccessor(accessor, accessModeFlags) - return voidRegisterAccessor - - def activateAsyncRead(self) -> None: - """ - Activate asynchronous read for all transfer elements where - :py:obj:`AccessMode.wait_for_new_data` is set. - - If this method is called while the device is not opened or has an error, - this call has no effect. If it is called when no deactivated transfer - element exists, this call also has no effect. - On return, it is not guaranteed that - all initial values have been received already. - - See also - -------- - :py:func:`getVoidRegisterAccessor`: has a usage example. - """ - self._device.activateAsyncRead() - - def getRegisterCatalogue(self) -> pb.RegisterCatalogue: - """ - Return the register catalogue with detailed information on all registers. - """ - return self._device.getRegisterCatalogue() - - def read(self, registerPath: str, dtype: np.dtype = np.float64, numberOfWords: int = 0, - wordOffsetInRegister: int = 0, accessModeFlags: Sequence[AccessMode] = None) -> np.ndarray | np.number: - """ - Inefficient convenience function to read a register without obtaining an accessor. - If no dtype is selected, the returned ndarray will default to np.float64. - If numberOfWords is not specified, it takes the maximm minus the offset. - If numberOfChannels is not specified, it takes the maximm possible. - """ - accessModeFlags = [] if accessModeFlags is None else accessModeFlags - arr = self._device.read(registerPath, numberOfWords, wordOffsetInRegister, accessModeFlags) - if arr.shape == (1, 1): - return arr[0][0] - if arr.shape[0] == 1: - return arr[:][0] - return arr - - def write( - self, - registerPath: str, - dataToWrite: np.ndarray | np.number, - wordOffsetInRegister: int = 0, - accessModeFlags: Sequence[AccessMode] = None) -> None: - """ - Inefficient convenience function to write a register without obtaining an accessor. - If no dtype is selected, the returned ndarray will default to np.float64. - """ - - # make proper array, if number was submitted - if isinstance(dataToWrite, list): - array = np.array(dataToWrite) - # upgrade 1d-list input two the 2d that is expected for scalar and 1d lists: - if not array.ndim == 2: - array = np.array([dataToWrite]) - elif not isinstance(dataToWrite, np.ndarray): - array = np.array([[dataToWrite]]) - else: - array = dataToWrite - numberOfElements = array.shape[1] if array.ndim == 2 else (array.shape[0] if array.ndim == 1 else 1) - - accessModeFlags = [] if accessModeFlags is None else accessModeFlags - self._device.write(array, registerPath, numberOfElements, - wordOffsetInRegister, accessModeFlags) - - -######################################################################################################################## -# Middle Layer Class extensions, might be unnecessary in the future with a switch from boost to pybind11 - -class RegisterClassIterator: - def __init__(self, registerCatalogue): - self._rc = registerCatalogue - self._index = 0 - - def __iter__(self): - return self - - def __next__(self): - if len(self._rc._items()) > self._index: - self._index += 1 - return self._rc._items()[self._index-1] - else: - raise StopIteration - - -pb.RegisterCatalogue.__iter__ = lambda rc: RegisterClassIterator(rc) - - -######################################################################################################################## -# Type Definitions - - -UserType = functools.reduce(lambda x, y: Union[x, y], list(Device._userTypeExtensions.values())) - -RegisterAccessor = Union[OneDRegisterAccessor, - TwoDRegisterAccessor, - ScalarRegisterAccessor] diff --git a/src/mtca4u/__init__.py b/src/mtca4u/__init__.py deleted file mode 100644 index 5bea250..0000000 --- a/src/mtca4u/__init__.py +++ /dev/null @@ -1,651 +0,0 @@ -# SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project -# SPDX-License-Identifier: LGPL-3.0-or-later - -import sys - -import numpy - -import deviceaccess - -__version__ = "2.3.0" - -# http://stackoverflow.com/questions/4219717/how-to-assert-output-with-nosetest-unittest-in-python - - -def get_info(outputStream=sys.stdout): - """prints details about the module and the deviceaccess library - against which it was linked - - Parameters - ---------- - outputStream: optional - default: sys.stdout - - Returns - ------- - - Examples - -------- - >>> import mtca4u - >>> mtca4u.get_info() - mtca4uPy v02.03.00, linked with mtca4u-deviceaccess v$ChimeraTK-DeviceAccess_VERSION} - - """ - outputStream.write("mtca4uPy v02.03.00, linked with mtca4u-deviceaccess v02.06") - - -def set_dmap_location(dmapFileLocation): - """Sets the location of the dmap file to use - - The library will check the user specified device Alias names (when creating - devices) in this dmap file. Once set, the library will look at this dmap file - through out the program lifetime. This is true until a new dmap file is - set again using set_dmap_location - - Parameters - ---------- - dmapFileLocation: string - Path to the desired dmap file. - - Returns - ------- - - Examples - -------- - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> device = mtca4u.Device("my_card") # my_card is a alias in my_example_dmap_file.dmap - - See Also - -------- - get_dmap_location: View the current dmap file which the library uses for device name (alias) lookup. - Device : Open device using specified alias names or using device id and mapfile - - """ - # os.environ["DMAP_FILE"] = dmapFileLocation - deviceaccess.setDMapFilePath(dmapFileLocation) - - -def get_dmap_location(): - """Get the dmap file which is currently in use by the library. - - Method returns the file path of the dmap file the library currently uses. - This is the dmap file the library uses to look up the device name(alias) and - its details - - Parameters - ---------- - - Returns - ------- - string: File path of the dmap file the library currently uses. - - Examples - -------- - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> dmapPath = mtca4u.get_dmap_location() - >>> print dmapPath # prints '../my_example_dmap_file.dmap' - - See Also - -------- - set_dmap_location : set dmap file path for the library. - Device : Open device using specified alias names or using device id and mapfile - - """ - return deviceaccess.getDMapFilePath() - - -class Device: - """Construct Device from user provided device information - - This constructor is used to open a device listed in the dmap file. - - Parameters - ---------- - alias : str - The device alias/name in the dmap file for the hardware - - Examples - -------- - Creating a device using dmap file: - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> device = mtca4u.Device("my_card") # my_card is a alias in my_example_dmap_file.dmap - """ - - def __init__(self, cddOrAlias): - self.__openedDevice = deviceaccess.Device(cddOrAlias) - self.__openedDevice.open() - - def read( - self, - moduleName="", - registerName=None, - numberOfElementsToRead=0, - elementIndexInRegister=0, - registerPath=None, - ): - """Reads out Fixed point converted values from the opened device - - This method uses the map file to return Fixed Point converted values from a - register. It can read the whole register or an arbitary number of register - elements. Data can also be read from an offset within the register (through - the 'elementIndexInRegister' parameter). - - .. note:: Both moduleName and registerName parameters are ignored when - registerPath is provided. - - Parameters - ---------- - moduleName : str, optional - The name of the device module to which the register belongs to. If the - register is not contained in a module, then provide an empty string as - the parameter value. - - registerName : str, optional - The name of the register to read from. - - numberOfElementsToRead : int, optional - Specifies the number of register elements that should - be read out. The width and fixed point representation of the register - element are internally obtained from the map file. - - The method returns all elements in the register if this parameter is - ommitted or when its value is set as 0. - - If the value provided as this parameter exceeds the register size, an - array with all elements upto the last element is returned - - elementIndexInRegister : int, optional - This is a zero indexed offset from the first element of the register. When - an elementIndexInRegister parameter is specified, the method reads out - elements starting from this element index. The elemnt at the index - position is included in the read as well. - - registerPath : str, optional - When provided, it takes precedences over moduleName and registerName for - location lookup. - - Returns - ------- - readoutValues: numpy.array, dtype == numpy.float64 - The return type for the method is a 1-Dimensional numpy array with - datatype numpy.float64. The returned numpy.array would either contain all - elements in the register or only the number specified by the - numberOfElementsToRead parameter - - Examples - -------- - register "WORD_STATUS" is 1 element long.. - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> boardWithModules = mtca4u.Device("device_name") - >>> boardWithModules.read("BOARD", "WORD_STATUS") - array([15.0], dtype=float64) - >>> boardWithModules.read(registerPath="/BOARD/WORD_STATUS") - array([15.0], dtype=float64) - - - register "WORD_CLK_MUX" is 4 elements long. - >>> import mtca4u - >>> device = mtca4u.Device("device_name") - >>> device.read("", "WORD_CLK_MUX") - array([15.0, 14.0, 13.0, 12.0], dtype=float64) - >>> device.read("", "WORD_CLK_MUX", 0) - array([15.0, 14.0, 13.0, 12.0], dtype=float64) - read out select number of elements from specified locations: - >>> device.read("", "WORD_CLK_MUX", 1) - array([15.0], dtype=float64) - >>> device.read("", "WORD_CLK_MUX", 1, 2 ) - array([13.0], dtype=float64) - >>> device.read("", "WORD_CLK_MUX", 0, 2 ) - array([13.0, 12.0], dtype=float64) - >>> device.read("", "WORD_CLK_MUX", 5, 2 ) - array([13.0, 12.0], dtype=float64) - >>> device.read("", "WORD_CLK_MUX", numberOfElementsToRead=1, elementIndexInRegister=2 ) - array([13.0], dtype=float64) - >>> device.read("", "WORD_CLK_MUX", elementIndexInRegister=2 ) - array([13.0, 12.0], dtype=float64) - - - See Also - -------- - Device.read_raw : Read in 'raw' bit values from a device register - - """ - if registerPath is None: - registerPath = "/" + moduleName + "/" + registerName - val = self.__openedDevice.read( - registerPath, - numberOfWords=numberOfElementsToRead, - wordOffsetInRegister=elementIndexInRegister, - ) - if isinstance(val, numpy.ndarray): - return val.astype(numpy.float64) - return numpy.asarray([val]).astype(numpy.float64) - - def write( - self, - moduleName="", - registerName=None, - dataToWrite=None, - elementIndexInRegister=0, - registerPath=None, - ): - """ Sets data into a desired register - - This method writes values into a register on the board. The method - internally uses a fixed point converter that is aware of the register width - on the device and its fractional representation. This Fixed point converter - converts the input into corresponding Fixed Point representaions that fit - into the decive register. - - .. note:: Both moduleName and registerName parameters are ignored when - registerPath is provided. - - Parameters - ---------- - moduleName : str, optional - The name of the device module which has the register to write into. - If module name is not applicable to the register, then provide an empty - string as the parameter value. - - registerName : str, optional - Mapped name of the register to write to - - dataToWrite : int, float, \ - list of int/float, numpy.array(dtype numpy.float32/64), \ - numpy.array(dtype = numpy.int32/64) - The data to be written in to the register. it may be a numpy.float32/64 or a - numpy.int32/64 array or a list with int or float values . Each value in this - array represents an induvidual element of the register. dataToWrite may also - take on int/float type when single vaues are passesed - - elementIndexInRegister : int, optional - This is a zero indexed offset from the first element of the register. When - an elementIndexInRegister parameter is specified, the method starts the - write from this index - - registerPath : str, optional - When provided, it takes precedences over moduleName and registerName for - location lookup. - - Returns - ------- - - Examples - -------- - register "WORD_STATUS" is 1 element long and belongs to module "BOARD". - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> boardWithModules = mtca4u.Device("device_name") - >>> boardWithModules.write("BOARD", "WORD_STATUS", 15) - >>> boardWithModules.write("BOARD", "WORD_STATUS", 15.0) - >>> boardWithModules.write("BOARD", "WORD_STATUS", [15]) - >>> boardWithModules.write("BOARD", "WORD_STATUS", [15.0]) - - >>> boardWithModules.write(registerPath = "/BOARD/WORD_STATUS", - dataToWrite = [15.0]) - - >>> dataToWrite = numpy.array([15.0]) - >>> boardWithModules.write("BOARD", "WORD_STATUS", dataToWrite) - - register "WORD_CLK_MUX" is 4 elements long. - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> device = mtca4u.Device("device_name") - >>> dataToWrite = numpy.array([15.0, 14.0, 13.0, 12.0]) - >>> device.write("", "WORD_CLK_MUX", dataToWrite) - >>> dataToWrite = numpy.array([13, 12]) - >>> device.write("", "WORD_CLK_MUX", dataToWrite, 2) - >>> device.write("", "WORD_CLK_MUX", 2.78) # writes value to first element of register - >>> device.write("", "WORD_CLK_MUX", 10, elementIndexInRegister=3) - - See Also - -------- - Device.write_raw : Write 'raw' bit values to a device register - - """ - if registerPath is None: - registerPath = "/" + moduleName + "/" + registerName - self.__openedDevice.write( - registerPath, - dataToWrite=dataToWrite, - wordOffsetInRegister=elementIndexInRegister, - ) - - def read_raw( - self, - moduleName="", - registerName=None, - numberOfElementsToRead=0, - elementIndexInRegister=0, - registerPath=None, - ): - """Returns 'raw values' (Without fixed point conversion applied) from a device's register - - This method returns the raw bit values contained in the queried register. - The returned values are not Fixed Point converted, but direct binary values - contained in the register elements. - - .. note:: Both moduleName and registerName parameters are ignored when - registerPath is provided. - - Parameters - ---------- - moduleName : str, optional - The name of the device module to which the register to read from belongs. - If module name is not applicable to the register, then provide an empty - string as the parameter value. - - registerName : str, optional - The name of the device register to read from. - - numberOfElementsToRead : int, optional - Specifies the number of register elements that should be read out. - The method returns all elements in the register if this parameter is - ommitted or when its value is set as 0. - If the value provided as this parameter exceeds the register size, an - array will all elements upto the last element is returned - - elementIndexInRegister : int, optional - This is a zero indexed offset from the first element of the register. When - an elementIndexInRegister parameter is specified, the method reads out - elements starting from this element index. The element at the index - position is included in the read as well. - - registerPath : str, optional - When provided, it takes precedences over moduleName and registerName for - location lookup. - - Returns - ------- - readInRawValues: numpy.array, dtype == numpy.int32 - The method returns a numpy.int32 array containing the raw bit values of - the register elements. The length of the array either equals the number of - elements that make up the register or the number specified through the - numberOfElementsToRead parameter - - Examples - -------- - register "WORD_STATUS" is 1 element long. - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> boardWithModules = mtca4u.Device("device_name") - >>> boardWithModules.read_raw("BOARD", "WORD_STATUS") - array([15], dtype=int32) - >>> boardWithModules.read_raw(registerPath="/BOARD/WORD_STATUS") - array([15], dtype=int32) - - register "WORD_CLK_MUX" is 4 elements long. - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> device = mtca4u.Device("device_name") - >>> device.read_raw("", "WORD_CLK_MUX") - array([15, 14, 13, 12], dtype=int32) - >>> device.read_raw("", "WORD_CLK_MUX", 0) - array([15, 14, 13, 12], dtype=int32) - >>> device.read_raw("", "WORD_CLK_MUX", 1) - array([15], dtype=int32) - >>> device.read_raw("", "WORD_CLK_MUX", 1, 2 ) - array([13], dtype = int32) - >>> device.read_raw("", "WORD_CLK_MUX", 0, 2 ) - array([13, 12], dtype=int32) - >>> device.read_raw("", "WORD_CLK_MUX", numberOfElementsToRead=1, elementIndexInRegister=2 ) - array([13], dtype=int32) - >>> device.read_raw("", "WORD_CLK_MUX", elementIndexInRegister=2 ) - array([13, 12], dtype=int32) - - See Also - -------- - Device.read : Read in Fixed Point converted bit values from a device register - - """ - if registerPath is None: - registerPath = "/" + moduleName + "/" + registerName - val = self.__openedDevice.read( - registerPath, - numberOfWords=numberOfElementsToRead, - dtype=numpy.int32, - wordOffsetInRegister=elementIndexInRegister, - accessModeFlags=[deviceaccess.AccessMode.raw], - ) - if isinstance(val, numpy.ndarray): - return val - return numpy.asarray([val]) - - def write_raw( - self, - moduleName="", - registerName=None, - dataToWrite=None, - elementIndexInRegister=0, - registerPath=None, - ): - """Write raw bit values (no fixed point conversion applied) into the register - - Provides a way to put in a desired bit value into individual register - elements. - - .. note:: Both moduleName and registerName parameters are ignored when - registerPath is provided. - - Parameters - ---------- - moduleName : str, optional - The name of the device module that has the register we intend to write to. - If module name is not applicable to the register, then provide an empty - string as the parameter value. - - registerName : str, optional - The name of the desired register to write into. - - dataToWrite : numpy.array, dtype == numpy.int32 - The array holding the bit values to be written into the register. The numpy - array is expected to contain numpy.int32 values - - elementIndexInRegister : int, optional - This is a zero indexed offset from the first element of the register. When - an elementIndexInRegister parameter is specified, the method starts the - write from this index - - registerPath : str, optional - When provided, it takes precedences over moduleName and registerName for - location lookup. - - Returns - ------- - - Examples - -------- - register "WORD_STATUS" is 1 element long and is part of the module "BOARD". - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> boardWithModules = mtca4u.Device("device_name") - >>> _dataToWrite = numpy.array([15], dtype=int32) - >>> boardWithModules.write_raw("BOARD", "WORD_STATUS", dataToWrite=_dataToWrite) - >>> boardWithModules.write_raw(registerPath = "/BOARD/WORD_STATUS", - dataToWrite=_dataToWrite) - - register "WORD_CLK_MUX" is 4 elements long. - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> device = mtca4u.Device("device_name") - >>> dataToWrite = numpy.array([15, 14, 13, 12], dtype=int32) - >>> device.write_raw("", "WORD_CLK_MUX", dataToWrite) - >>> dataToWrite = numpy.array([13, 12], dtype=int32) - >>> device.write_raw("MODULE1", "WORD_CLK_MUX", dataToWrite, 2) - - See Also - -------- - Device.write : Write values that get fixed point converted to the device - - """ - if registerPath is None: - registerPath = "/" + moduleName + "/" + registerName - self.__openedDevice.write( - registerPath, - dataToWrite=dataToWrite, - wordOffsetInRegister=elementIndexInRegister, - accessModeFlags=[deviceaccess.AccessMode.raw], - dtype=numpy.int32, - ) - - def read_dma_raw( - self, - moduleName="", - DMARegisterName=None, - numberOfElementsToRead=0, - elementIndexInRegister=0, - registerPath=None, - ): - """Read in Data from the DMA region of the card - - This method can be used to fetch data copied to a dma memory block. The - method assumes that the device maps the DMA memory block to a register made - up of 32 bit elements. - - - .. note:: Deprecated since 1.0.0; use Device.read_raw instead. - - - Parameters - ---------- - moduleName : str, optional - The name of the device module that has the register we intend to write to. - If module name is not applicable to the device, then provide an empty - string as the parameter value. - - DMARegisterName : str, optional - The register name to which the DMA memory region is mapped - - numberOfElementsToRead : int, optional - This optional parameter specifies the number of 32 bit elements that have - to be returned from the mapped dma register. When this parameter is not - specified or is provided with a value of 0, every element in the DMA - memory block is returned. - - If the value provided as this parameter exceeds the register size, an - array with all elements upto the last element is returned - - elementIndexInRegister : int, optional - This parameter specifies the index from which the read should commence. - - registerPath : str, optional - When provided, it takes precedences over moduleName and registerName for - location lookup. - - Returns - ------- - arrayOfRawValues: numpy.array, dtype == numpy.int32 - The method returns a numpy.int32 array containing the raw bit values - contained in the DMA register elements. The length of the array either - equals the number of 32 bit elements that make up the whole DMA region or - the number specified through the numberOfElementsToRead parameter - - Examples - -------- - Use Device.read_raw: In the example, register "AREA_DMA_VIA_DMA" is the DMA mapped memory made up of 32 bit elements. - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> device = mtca4u.Device("device_name") - >>> device.read__raw("", "AREA_DMA_VIA_DMA", 10) - array([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], dtype=int32) - - See Also - -------- - Device.read_raw : Use this method for the same purpose instead. - """ - - return self.read_raw( - moduleName, - DMARegisterName, - numberOfElementsToRead, - elementIndexInRegister, - registerPath, - ) - - def read_sequences(self, moduleName="", regionName=None, registerPath=None): - """Read in all sequences from a Multiplexed data Region - - This method returns the demultiplexed sequences in the memory area specified - by regionName. The data is returned as a 2D numpy array with the coulums - representing induvidual sequences - - .. note:: Both moduleName and regionName parameters are ignored when - registerPath is provided. - - Parameters - ---------- - moduleName : str, optional - The name of the device module that has the register we intend to write to. - If module name is not applicable to the device, then provide an empty - string as the parameter value. - - regionName : str, optional - The name of the memory area containing the multiplexed data. - - registerPath : str, optional - When provided, it takes precedences over moduleName and regionName for - location lookup. - - Returns - ------- - 2DarrayOfValues: numpy.array, dtype == numpy.double - The method returns a 2D numpy.double array containing extracted - induvidual sequences as the columns - - Examples - -------- - "DMA" is the Multiplexed data region name. This region is defined by 'AREA_MULTIPLEXED_SEQUENCE_DMA' in the mapfile. - >>> import mtca4u - >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") - >>> device = mtca4u.Device("device_name") - >>> device.read_sequences("BOARD.0", "DMA") - array([[ 0., 1., 4., 9., 16.], - [ 25., 36., 49., 64., 81.], - [ 100., 121., 144., 169., 196.], - [ 225., 256., 289., 324., 361.] - [ 400., 441., 484., 529., 576.]], dtype=double) - >>> device.read_sequences(registerPath= '/BOARD.0/DMA') - array([[ 0., 1., 4., 9., 16.], - [ 25., 36., 49., 64., 81.], - [ 100., 121., 144., 169., 196.], - [ 225., 256., 289., 324., 361.] - [ 400., 441., 484., 529., 576.]], dtype=double) - - Each column of the 2D matrix represents an extracted sequence: - >>> data = device.read_sequences("BOARD.0", "DMA") - >>> adc0_values = data[:,0] # array([0., 25., 100., 225., 400.]) - >>> adc1_values = data[:,1] # array([1., 36., 49., 64., 81.]) - >>> adc3_values = data[:,3] # array([9., 64., 169., 324., 529.]) - - """ - - if registerPath is None: - registerPath = "/" + moduleName + "/" + regionName - accessor = self.__openedDevice.getTwoDRegisterAccessor( - numpy.float64, registerPath - ) - accessor.read() - return accessor.get().transpose() - - def getCatalogueMetadata(self, parameterName): - """Reads out metadata form the device catalogue - - The available metadata depends on the use backend. E.g. for - NumericAddressedBackends, metadata will come from the map file. - - Parameters - ---------- - parameterName : str - Name of the metadata parameter to read. - - Returns - ------- - metadataValue: str - The value corresponding to the given parameterName. - - """ - return self.__openedDevice.getCatalogueMetadata(parameterName) From 24e23578b692d9af4ad5c821f088aeccbc646268 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sun, 19 Oct 2025 18:43:24 +0200 Subject: [PATCH 21/34] fix: missing mtca4u.py --- mtca4u.py | 600 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100644 mtca4u.py diff --git a/mtca4u.py b/mtca4u.py new file mode 100644 index 0000000..aa0aef6 --- /dev/null +++ b/mtca4u.py @@ -0,0 +1,600 @@ +# SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project +# SPDX-License-Identifier: LGPL-3.0-or-later + +import deviceaccess +import numpy +import sys + +__version__ = "02.03.00" + +# http://stackoverflow.com/questions/4219717/how-to-assert-output-with-nosetest-unittest-in-python + + +def get_info(outputStream=sys.stdout): + """ prints details about the module and the deviceaccess library + against which it was linked + + Parameters + ---------- + outputStream: optional + default: sys.stdout + + Returns + ------- + + Examples + -------- + >>> import mtca4u + >>> mtca4u.get_info() + mtca4uPy v02.03.00, linked with mtca4u-deviceaccess v$ChimeraTK-DeviceAccess_VERSION} + + """ + outputStream.write( + "mtca4uPy v02.03.00, linked with mtca4u-deviceaccess v02.06") + + +def set_dmap_location(dmapFileLocation): + """ Sets the location of the dmap file to use + + The library will check the user specified device Alias names (when creating + devices) in this dmap file. Once set, the library will look at this dmap file + through out the program lifetime. This is true until a new dmap file is + set again using set_dmap_location + + Parameters + ---------- + dmapFileLocation: string + Path to the desired dmap file. + + Returns + ------- + + Examples + -------- + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> device = mtca4u.Device("my_card") # my_card is a alias in my_example_dmap_file.dmap + + See Also + -------- + get_dmap_location: View the current dmap file which the library uses for device name (alias) lookup. + Device : Open device using specified alias names or using device id and mapfile + + """ + # os.environ["DMAP_FILE"] = dmapFileLocation + deviceaccess.setDMapFilePath(dmapFileLocation) + + +def get_dmap_location(): + """ Get the dmap file which is currently in use by the library. + + Method returns the file path of the dmap file the library currently uses. + This is the dmap file the library uses to look up the device name(alias) and + its details + + Parameters + ---------- + + Returns + ------- + string: File path of the dmap file the library currently uses. + + Examples + -------- + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> dmapPath = mtca4u.get_dmap_location() + >>> print dmapPath # prints '../my_example_dmap_file.dmap' + + See Also + -------- + set_dmap_location : set dmap file path for the library. + Device : Open device using specified alias names or using device id and mapfile + + """ + return deviceaccess.getDMapFilePath() + + +class Device: + """ Construct Device from user provided device information + + This constructor is used to open a device listed in the dmap file. + + Parameters + ---------- + alias : str + The device alias/name in the dmap file for the hardware + + Examples + -------- + Creating a device using dmap file: + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> device = mtca4u.Device("my_card") # my_card is a alias in my_example_dmap_file.dmap + """ + + def __init__(self, cddOrAlias): + self.__openedDevice = deviceaccess.Device(cddOrAlias) + self.__openedDevice.open() + + def read(self, moduleName="", registerName=None, numberOfElementsToRead=0, + elementIndexInRegister=0, registerPath=None): + """ Reads out Fixed point converted values from the opened device + + This method uses the map file to return Fixed Point converted values from a + register. It can read the whole register or an arbitary number of register + elements. Data can also be read from an offset within the register (through + the 'elementIndexInRegister' parameter). + + .. note:: Both moduleName and registerName parameters are ignored when + registerPath is provided. + + Parameters + ---------- + moduleName : str, optional + The name of the device module to which the register belongs to. If the + register is not contained in a module, then provide an empty string as + the parameter value. + + registerName : str, optional + The name of the register to read from. + + numberOfElementsToRead : int, optional + Specifies the number of register elements that should + be read out. The width and fixed point representation of the register + element are internally obtained from the map file. + + The method returns all elements in the register if this parameter is + ommitted or when its value is set as 0. + + If the value provided as this parameter exceeds the register size, an + array with all elements upto the last element is returned + + elementIndexInRegister : int, optional + This is a zero indexed offset from the first element of the register. When + an elementIndexInRegister parameter is specified, the method reads out + elements starting from this element index. The elemnt at the index + position is included in the read as well. + + registerPath : str, optional + When provided, it takes precedences over moduleName and registerName for + location lookup. + + Returns + ------- + readoutValues: numpy.array, dtype == numpy.float64 + The return type for the method is a 1-Dimensional numpy array with + datatype numpy.float64. The returned numpy.array would either contain all + elements in the register or only the number specified by the + numberOfElementsToRead parameter + + Examples + -------- + register "WORD_STATUS" is 1 element long.. + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> boardWithModules = mtca4u.Device("device_name") + >>> boardWithModules.read("BOARD", "WORD_STATUS") + array([15.0], dtype=float64) + >>> boardWithModules.read(registerPath="/BOARD/WORD_STATUS") + array([15.0], dtype=float64) + + + register "WORD_CLK_MUX" is 4 elements long. + >>> import mtca4u + >>> device = mtca4u.Device("device_name") + >>> device.read("", "WORD_CLK_MUX") + array([15.0, 14.0, 13.0, 12.0], dtype=float64) + >>> device.read("", "WORD_CLK_MUX", 0) + array([15.0, 14.0, 13.0, 12.0], dtype=float64) + read out select number of elements from specified locations: + >>> device.read("", "WORD_CLK_MUX", 1) + array([15.0], dtype=float64) + >>> device.read("", "WORD_CLK_MUX", 1, 2 ) + array([13.0], dtype=float64) + >>> device.read("", "WORD_CLK_MUX", 0, 2 ) + array([13.0, 12.0], dtype=float64) + >>> device.read("", "WORD_CLK_MUX", 5, 2 ) + array([13.0, 12.0], dtype=float64) + >>> device.read("", "WORD_CLK_MUX", numberOfElementsToRead=1, elementIndexInRegister=2 ) + array([13.0], dtype=float64) + >>> device.read("", "WORD_CLK_MUX", elementIndexInRegister=2 ) + array([13.0, 12.0], dtype=float64) + + + See Also + -------- + Device.read_raw : Read in 'raw' bit values from a device register + + """ + if registerPath is None: + registerPath = '/' + moduleName + '/' + registerName + val = self.__openedDevice.read(registerPath, numberOfWords=numberOfElementsToRead, + wordOffsetInRegister=elementIndexInRegister) + if isinstance(val, numpy.ndarray): + return val.astype(numpy.float64) + return numpy.asarray([val]).astype(numpy.float64) + + def write(self, moduleName="", registerName=None, dataToWrite=None, + elementIndexInRegister=0, registerPath=None): + """ Sets data into a desired register + + This method writes values into a register on the board. The method + internally uses a fixed point converter that is aware of the register width + on the device and its fractional representation. This Fixed point converter + converts the input into corresponding Fixed Point representaions that fit + into the decive register. + + .. note:: Both moduleName and registerName parameters are ignored when + registerPath is provided. + + Parameters + ---------- + moduleName : str, optional + The name of the device module which has the register to write into. + If module name is not applicable to the register, then provide an empty + string as the parameter value. + + registerName : str, optional + Mapped name of the register to write to + + dataToWrite : int, float, \ + list of int/float, numpy.array(dtype numpy.float32/64), \ + numpy.array(dtype = numpy.int32/64) + The data to be written in to the register. it may be a numpy.float32/64 or a + numpy.int32/64 array or a list with int or float values . Each value in this + array represents an induvidual element of the register. dataToWrite may also + take on int/float type when single vaues are passesed + + elementIndexInRegister : int, optional + This is a zero indexed offset from the first element of the register. When + an elementIndexInRegister parameter is specified, the method starts the + write from this index + + registerPath : str, optional + When provided, it takes precedences over moduleName and registerName for + location lookup. + + Returns + ------- + + Examples + -------- + register "WORD_STATUS" is 1 element long and belongs to module "BOARD". + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> boardWithModules = mtca4u.Device("device_name") + >>> boardWithModules.write("BOARD", "WORD_STATUS", 15) + >>> boardWithModules.write("BOARD", "WORD_STATUS", 15.0) + >>> boardWithModules.write("BOARD", "WORD_STATUS", [15]) + >>> boardWithModules.write("BOARD", "WORD_STATUS", [15.0]) + + >>> boardWithModules.write(registerPath = "/BOARD/WORD_STATUS", + dataToWrite = [15.0]) + + >>> dataToWrite = numpy.array([15.0]) + >>> boardWithModules.write("BOARD", "WORD_STATUS", dataToWrite) + + register "WORD_CLK_MUX" is 4 elements long. + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> device = mtca4u.Device("device_name") + >>> dataToWrite = numpy.array([15.0, 14.0, 13.0, 12.0]) + >>> device.write("", "WORD_CLK_MUX", dataToWrite) + >>> dataToWrite = numpy.array([13, 12]) + >>> device.write("", "WORD_CLK_MUX", dataToWrite, 2) + >>> device.write("", "WORD_CLK_MUX", 2.78) # writes value to first element of register + >>> device.write("", "WORD_CLK_MUX", 10, elementIndexInRegister=3) + + See Also + -------- + Device.write_raw : Write 'raw' bit values to a device register + + """ + if registerPath is None: + registerPath = '/' + moduleName + '/' + registerName + self.__openedDevice.write( + registerPath, dataToWrite=dataToWrite, wordOffsetInRegister=elementIndexInRegister) + + def read_raw(self, moduleName='', registerName=None, numberOfElementsToRead=0, + elementIndexInRegister=0, registerPath=None): + """ Returns 'raw values' (Without fixed point conversion applied) from a device's register + + This method returns the raw bit values contained in the queried register. + The returned values are not Fixed Point converted, but direct binary values + contained in the register elements. + + .. note:: Both moduleName and registerName parameters are ignored when + registerPath is provided. + + Parameters + ---------- + moduleName : str, optional + The name of the device module to which the register to read from belongs. + If module name is not applicable to the register, then provide an empty + string as the parameter value. + + registerName : str, optional + The name of the device register to read from. + + numberOfElementsToRead : int, optional + Specifies the number of register elements that should be read out. + The method returns all elements in the register if this parameter is + ommitted or when its value is set as 0. + If the value provided as this parameter exceeds the register size, an + array will all elements upto the last element is returned + + elementIndexInRegister : int, optional + This is a zero indexed offset from the first element of the register. When + an elementIndexInRegister parameter is specified, the method reads out + elements starting from this element index. The element at the index + position is included in the read as well. + + registerPath : str, optional + When provided, it takes precedences over moduleName and registerName for + location lookup. + + Returns + ------- + readInRawValues: numpy.array, dtype == numpy.int32 + The method returns a numpy.int32 array containing the raw bit values of + the register elements. The length of the array either equals the number of + elements that make up the register or the number specified through the + numberOfElementsToRead parameter + + Examples + -------- + register "WORD_STATUS" is 1 element long. + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> boardWithModules = mtca4u.Device("device_name") + >>> boardWithModules.read_raw("BOARD", "WORD_STATUS") + array([15], dtype=int32) + >>> boardWithModules.read_raw(registerPath="/BOARD/WORD_STATUS") + array([15], dtype=int32) + + register "WORD_CLK_MUX" is 4 elements long. + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> device = mtca4u.Device("device_name") + >>> device.read_raw("", "WORD_CLK_MUX") + array([15, 14, 13, 12], dtype=int32) + >>> device.read_raw("", "WORD_CLK_MUX", 0) + array([15, 14, 13, 12], dtype=int32) + >>> device.read_raw("", "WORD_CLK_MUX", 1) + array([15], dtype=int32) + >>> device.read_raw("", "WORD_CLK_MUX", 1, 2 ) + array([13], dtype = int32) + >>> device.read_raw("", "WORD_CLK_MUX", 0, 2 ) + array([13, 12], dtype=int32) + >>> device.read_raw("", "WORD_CLK_MUX", numberOfElementsToRead=1, elementIndexInRegister=2 ) + array([13], dtype=int32) + >>> device.read_raw("", "WORD_CLK_MUX", elementIndexInRegister=2 ) + array([13, 12], dtype=int32) + + See Also + -------- + Device.read : Read in Fixed Point converted bit values from a device register + + """ + if registerPath is None: + registerPath = '/' + moduleName + '/' + registerName + val = self.__openedDevice.read(registerPath, numberOfWords=numberOfElementsToRead, dtype=numpy.int32, + wordOffsetInRegister=elementIndexInRegister, accessModeFlags=[deviceaccess.AccessMode.raw]) + if isinstance(val, numpy.ndarray): + return val + return numpy.asarray([val]) + + def write_raw(self, moduleName='', registerName=None, dataToWrite=None, + elementIndexInRegister=0, registerPath=None): + """ Write raw bit values (no fixed point conversion applied) into the register + + Provides a way to put in a desired bit value into individual register + elements. + + .. note:: Both moduleName and registerName parameters are ignored when + registerPath is provided. + + Parameters + ---------- + moduleName : str, optional + The name of the device module that has the register we intend to write to. + If module name is not applicable to the register, then provide an empty + string as the parameter value. + + registerName : str, optional + The name of the desired register to write into. + + dataToWrite : numpy.array, dtype == numpy.int32 + The array holding the bit values to be written into the register. The numpy + array is expected to contain numpy.int32 values + + elementIndexInRegister : int, optional + This is a zero indexed offset from the first element of the register. When + an elementIndexInRegister parameter is specified, the method starts the + write from this index + + registerPath : str, optional + When provided, it takes precedences over moduleName and registerName for + location lookup. + + Returns + ------- + + Examples + -------- + register "WORD_STATUS" is 1 element long and is part of the module "BOARD". + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> boardWithModules = mtca4u.Device("device_name") + >>> _dataToWrite = numpy.array([15], dtype=int32) + >>> boardWithModules.write_raw("BOARD", "WORD_STATUS", dataToWrite=_dataToWrite) + >>> boardWithModules.write_raw(registerPath = "/BOARD/WORD_STATUS", + dataToWrite=_dataToWrite) + + register "WORD_CLK_MUX" is 4 elements long. + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> device = mtca4u.Device("device_name") + >>> dataToWrite = numpy.array([15, 14, 13, 12], dtype=int32) + >>> device.write_raw("", "WORD_CLK_MUX", dataToWrite) + >>> dataToWrite = numpy.array([13, 12], dtype=int32) + >>> device.write_raw("MODULE1", "WORD_CLK_MUX", dataToWrite, 2) + + See Also + -------- + Device.write : Write values that get fixed point converted to the device + + """ + if registerPath is None: + registerPath = '/' + moduleName + '/' + registerName + self.__openedDevice.write(registerPath, dataToWrite=dataToWrite, + wordOffsetInRegister=elementIndexInRegister, accessModeFlags=[deviceaccess.AccessMode.raw], dtype=numpy.int32) + + def read_dma_raw(self, moduleName='', DMARegisterName=None, + numberOfElementsToRead=0, elementIndexInRegister=0, + registerPath=None): + """ Read in Data from the DMA region of the card + + This method can be used to fetch data copied to a dma memory block. The + method assumes that the device maps the DMA memory block to a register made + up of 32 bit elements. + + + .. note:: Deprecated since 1.0.0; use Device.read_raw instead. + + + Parameters + ---------- + moduleName : str, optional + The name of the device module that has the register we intend to write to. + If module name is not applicable to the device, then provide an empty + string as the parameter value. + + DMARegisterName : str, optional + The register name to which the DMA memory region is mapped + + numberOfElementsToRead : int, optional + This optional parameter specifies the number of 32 bit elements that have + to be returned from the mapped dma register. When this parameter is not + specified or is provided with a value of 0, every element in the DMA + memory block is returned. + + If the value provided as this parameter exceeds the register size, an + array with all elements upto the last element is returned + + elementIndexInRegister : int, optional + This parameter specifies the index from which the read should commence. + + registerPath : str, optional + When provided, it takes precedences over moduleName and registerName for + location lookup. + + Returns + ------- + arrayOfRawValues: numpy.array, dtype == numpy.int32 + The method returns a numpy.int32 array containing the raw bit values + contained in the DMA register elements. The length of the array either + equals the number of 32 bit elements that make up the whole DMA region or + the number specified through the numberOfElementsToRead parameter + + Examples + -------- + Use Device.read_raw: In the example, register "AREA_DMA_VIA_DMA" is the DMA mapped memory made up of 32 bit elements. + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> device = mtca4u.Device("device_name") + >>> device.read__raw("", "AREA_DMA_VIA_DMA", 10) + array([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], dtype=int32) + + See Also + -------- + Device.read_raw : Use this method for the same purpose instead. + """ + + return self.read_raw(moduleName, DMARegisterName, + numberOfElementsToRead, + elementIndexInRegister, registerPath) + + def read_sequences(self, moduleName='', regionName=None, registerPath=None): + """ Read in all sequences from a Multiplexed data Region + + This method returns the demultiplexed sequences in the memory area specified + by regionName. The data is returned as a 2D numpy array with the coulums + representing induvidual sequences + + .. note:: Both moduleName and regionName parameters are ignored when + registerPath is provided. + + Parameters + ---------- + moduleName : str, optional + The name of the device module that has the register we intend to write to. + If module name is not applicable to the device, then provide an empty + string as the parameter value. + + regionName : str, optional + The name of the memory area containing the multiplexed data. + + registerPath : str, optional + When provided, it takes precedences over moduleName and regionName for + location lookup. + + Returns + ------- + 2DarrayOfValues: numpy.array, dtype == numpy.double + The method returns a 2D numpy.double array containing extracted + induvidual sequences as the columns + + Examples + -------- + "DMA" is the Multiplexed data region name. This region is defined by 'AREA_MULTIPLEXED_SEQUENCE_DMA' in the mapfile. + >>> import mtca4u + >>> mtca4u.set_dmap_location("../my_example_dmap_file.dmap") + >>> device = mtca4u.Device("device_name") + >>> device.read_sequences("BOARD.0", "DMA") + array([[ 0., 1., 4., 9., 16.], + [ 25., 36., 49., 64., 81.], + [ 100., 121., 144., 169., 196.], + [ 225., 256., 289., 324., 361.] + [ 400., 441., 484., 529., 576.]], dtype=double) + >>> device.read_sequences(registerPath= '/BOARD.0/DMA') + array([[ 0., 1., 4., 9., 16.], + [ 25., 36., 49., 64., 81.], + [ 100., 121., 144., 169., 196.], + [ 225., 256., 289., 324., 361.] + [ 400., 441., 484., 529., 576.]], dtype=double) + + Each column of the 2D matrix represents an extracted sequence: + >>> data = device.read_sequences("BOARD.0", "DMA") + >>> adc0_values = data[:,0] # array([0., 25., 100., 225., 400.]) + >>> adc1_values = data[:,1] # array([1., 36., 49., 64., 81.]) + >>> adc3_values = data[:,3] # array([9., 64., 169., 324., 529.]) + + """ + + if registerPath is None: + registerPath = '/' + moduleName + '/' + regionName + accessor = self.__openedDevice.getTwoDRegisterAccessor( + numpy.float64, registerPath) + accessor.read() + return accessor.get().transpose() + + def getCatalogueMetadata(self, parameterName): + """ Reads out metadata form the device catalogue + + The available metadata depends on the use backend. E.g. for + NumericAddressedBackends, metadata will come from the map file. + + Parameters + ---------- + parameterName : str + Name of the metadata parameter to read. + + Returns + ------- + metadataValue: str + The value corresponding to the given parameterName. + + """ + return self.__openedDevice.getCatalogueMetadata(parameterName) From 5f3225bd1414eff3ea9802e9ffba1f7d7962a9aa Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sun, 19 Oct 2025 18:46:44 +0200 Subject: [PATCH 22/34] fix: checkout tests. Add autopep configuration --- pyproject.toml | 2 ++ tests/testConvenienceFunctions.py | 4 +--- tests/testDeviceAccessLib.py | 4 +--- tests/testDeviceReadWrite.py | 4 +--- tests/testMtca4upy.py | 7 +++---- tests/testOpeningException.py | 2 -- tests/testPushType.py | 10 ++++------ tests/testRegisterCatalogue.py | 4 +--- 8 files changed, 13 insertions(+), 24 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 20a2681..c53642b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,3 +77,5 @@ PKG_CONFIG_PATH="${BUILD_DIR}/local/lib/pkgconfig/:/usr/lib/pkgconfig:${PKG_CONF LD_LIBRARY_PATH="${BUILD_DIR}/local/lib/:${LD_LIBRARY_PATH}" PATH="${BUILD_DIR}/local/bin/:${PATH}" +[tool.autopep8] +max_line_length = 120 diff --git a/tests/testConvenienceFunctions.py b/tests/testConvenienceFunctions.py index d70c7e8..2333cd4 100755 --- a/tests/testConvenienceFunctions.py +++ b/tests/testConvenienceFunctions.py @@ -5,7 +5,6 @@ from concurrent.futures import thread import sys import unittest -from pathlib import Path import numpy as np import os import threading @@ -20,12 +19,11 @@ import deviceaccess as da # fmt: on -DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestConvenienceFunctions(unittest.TestCase): def setUp(self): - da.setDMapFilePath(str(DEVINFO_DIR / "testCrate.dmap")) + da.setDMapFilePath("deviceInformation/testCrate.dmap") self.dev = da.Device("TEST_CARD") self.dev.open() diff --git a/tests/testDeviceAccessLib.py b/tests/testDeviceAccessLib.py index 70bb16c..4d2dabe 100644 --- a/tests/testDeviceAccessLib.py +++ b/tests/testDeviceAccessLib.py @@ -5,7 +5,6 @@ from concurrent.futures import thread import sys import unittest -from pathlib import Path import numpy as np import os import threading @@ -20,12 +19,11 @@ import deviceaccess as da # fmt: on -DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestDeviceAccessLib(unittest.TestCase): def setUp(self): - self.dmap_filepath = str(DEVINFO_DIR / "testCrate.dmap") + self.dmap_filepath = "deviceInformation/testCrate.dmap" da.setDMapFilePath(self.dmap_filepath) self.dev = da.Device("TEST_CARD") self.dev.open() diff --git a/tests/testDeviceReadWrite.py b/tests/testDeviceReadWrite.py index 9ace762..83050e5 100755 --- a/tests/testDeviceReadWrite.py +++ b/tests/testDeviceReadWrite.py @@ -5,7 +5,6 @@ from concurrent.futures import thread import sys import unittest -from pathlib import Path import numpy as np import os import threading @@ -20,12 +19,11 @@ import deviceaccess as da # fmt: on -DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestDeviceReadWrite(unittest.TestCase): def setUp(self): - da.setDMapFilePath(str(DEVINFO_DIR / "testCrate.dmap")) + da.setDMapFilePath("deviceInformation/testCrate.dmap") self.dev = da.Device("TEST_CARD") self.dev.open() diff --git a/tests/testMtca4upy.py b/tests/testMtca4upy.py index 4b76ca9..2248be7 100755 --- a/tests/testMtca4upy.py +++ b/tests/testMtca4upy.py @@ -6,7 +6,6 @@ import sys import fcntl import unittest -from pathlib import Path import numpy # fmt: off @@ -17,13 +16,12 @@ import mtca4u # fmt: on -DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestPCIEDevice(unittest.TestCase): # TODO: Refactor to take care of the harcoded values used for comparisions def setUp(self): - mtca4u.set_dmap_location(str(DEVINFO_DIR / "exampleCrate.dmap")) + mtca4u.set_dmap_location("deviceInformation/exampleCrate.dmap") def testRead(self): # first open devices, so shared memory dummies work correctly with __prepareDataOnCards and __testRead @@ -143,7 +141,8 @@ def testDeviceCreation(self): def testSetGetDmapfile(self): # set by the test setUp method - self.assertTrue(mtca4u.get_dmap_location() == str(DEVINFO_DIR / "exampleCrate.dmap")) + self.assertTrue(mtca4u.get_dmap_location() == + "deviceInformation/exampleCrate.dmap") """ The idea here is to preset data on registers that is then read in and diff --git a/tests/testOpeningException.py b/tests/testOpeningException.py index 5d3f6f3..a082bf2 100755 --- a/tests/testOpeningException.py +++ b/tests/testOpeningException.py @@ -4,7 +4,6 @@ import sys import unittest -from pathlib import Path import numpy import os @@ -16,7 +15,6 @@ import mtca4u # fmt: on -DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestOpeningException(unittest.TestCase): diff --git a/tests/testPushType.py b/tests/testPushType.py index 759059c..090f714 100755 --- a/tests/testPushType.py +++ b/tests/testPushType.py @@ -5,7 +5,6 @@ from concurrent.futures import thread import sys import unittest -from pathlib import Path import numpy as np import os import threading @@ -19,13 +18,12 @@ import deviceaccess as da # fmt: on -DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestPushType(unittest.TestCase): def testCorrectInterupt(self): - da.setDMapFilePath(str(DEVINFO_DIR / "push.dmap")) + da.setDMapFilePath("deviceInformation/push.dmap") dev = da.Device("SHARED_RAW_DEVICE") dev.open() dev.activateAsyncRead() @@ -47,7 +45,7 @@ def blockingRead(readAcc, barrier): readAcc.read() barrier.wait() - da.setDMapFilePath(str(DEVINFO_DIR / "push.dmap")) + da.setDMapFilePath("deviceInformation/push.dmap") dev = da.Device("SHARED_RAW_DEVICE") dev.open() dev.activateAsyncRead() @@ -70,7 +68,7 @@ def blockingRead(readAcc, barrier): barrier.wait(timeout=2) def testCorrectWrite(self): - da.setDMapFilePath(str(DEVINFO_DIR /"push.dmap")) + da.setDMapFilePath("deviceInformation/push.dmap") dev = da.Device("SHARED_RAW_DEVICE") dev.open() dev.activateAsyncRead() @@ -104,7 +102,7 @@ def blockingRead(readAcc, barrier): readAcc.read() barrier.wait() - da.setDMapFilePath(str(DEVINFO_DIR / "push.dmap")) + da.setDMapFilePath("deviceInformation/push.dmap") dev = da.Device("SHARED_RAW_DEVICE") dev.open() dev.activateAsyncRead() diff --git a/tests/testRegisterCatalogue.py b/tests/testRegisterCatalogue.py index 11b4018..174d523 100755 --- a/tests/testRegisterCatalogue.py +++ b/tests/testRegisterCatalogue.py @@ -5,7 +5,6 @@ from concurrent.futures import thread import sys import unittest -from pathlib import Path import numpy as np import os import threading @@ -20,12 +19,11 @@ import deviceaccess as da # fmt: on -DEVINFO_DIR = Path(__file__).parent / "deviceInformation" class TestRegisterCatalogue(unittest.TestCase): def setUp(self): - da.setDMapFilePath(str(DEVINFO_DIR / "push.dmap")) + da.setDMapFilePath("deviceInformation/push.dmap") self.dev = da.Device("SHARED_RAW_DEVICE") def tearDown(self) -> None: From ec9c8432688c8e07c7eb46c777d691b9355b3f0e Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sun, 19 Oct 2025 22:14:44 +0200 Subject: [PATCH 23/34] fix: move deviceaccess and mtca4u modules at root --- .gitignore | 1 + CMakeLists.txt | 4 +++- pyproject.toml | 7 +------ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 3d01028..c83cdd2 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ CMakeLists.txt.user *.code-workspace #used by PyCharm IDE .idea/ +wheelhouse/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 659bc51..2076133 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,9 @@ endif() # ==============================================================================# if(SKBUILD) - install(TARGETS ${PROJECT_NAME} DESTINATION ${SKBUILD_PROJECT_NAME}) + install( FILES ${PROJECT_BINARY_DIR}/deviceaccess.pyi DESTINATION ${SKBUILD_PLATLIB_DIR}) + install( FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION ${SKBUILD_PLATLIB_DIR}) + install( TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}) else() # install Python modules to correct platform-dependent directory (if installing to system prefix) if( "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr" OR "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local" ) diff --git a/pyproject.toml b/pyproject.toml index c53642b..0cfc3ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,8 +35,6 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", -"Programming Language :: Python :: Implementation :: GraalPy", -"Programming Language :: Python :: Implementation :: PyPy", "Topic :: Scientific/Engineering" ] @@ -52,12 +50,9 @@ minimum-version = "0.4" # Setuptools-style build caching in a local directory build-dir = "build/{wheel_tag}" -# Build stable ABI wheels for CPython 3.12+ -wheel.py-api = "cp312" - [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" -skip = "cp36-* cp37-* *-win32 *_i686" +skip = "cp36-* cp37-* cp14t-* *-win32 *_i686 *-musllinux_*" manylinux-x86_64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" From 9a51947c1839d64d4b3a067a099952b6030aa050 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Sun, 19 Oct 2025 22:18:34 +0200 Subject: [PATCH 24/34] chore: add __pycache__ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c83cdd2..5ea46ba 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ CMakeLists.txt.user #used by PyCharm IDE .idea/ wheelhouse/ +__pycache__/ From 3273bfa84b785a06f16cc607291822a24c6325bd Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 20 Oct 2025 09:59:01 +0200 Subject: [PATCH 25/34] chore: add note why tests are disabled in cibuildwheel --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 0cfc3ed..ea7a3a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,8 @@ build-verbosity = 1 ## Run unittest to ensure that the package was correctly built ## tests disabled due to corruption on interpreter exit +## Note: tests are disabled at the moment since to make it work, the +## resource path of them should be made cwd -independent # test-command = "python -m unittest discover -v {package}/tests" test-skip = "*" From 486d367fed6fb93f151540ad15e6a865f1eb45c0 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 20 Oct 2025 10:36:04 +0200 Subject: [PATCH 26/34] fix: minor cibuildwheel configuration error --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ea7a3a5..c6b1e03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ build-dir = "build/{wheel_tag}" [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" -skip = "cp36-* cp37-* cp14t-* *-win32 *_i686 *-musllinux_*" +skip = "cp36-* cp37-* cp314t-* *-win32 *_i686 *-musllinux_*" manylinux-x86_64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" @@ -76,3 +76,4 @@ PATH="${BUILD_DIR}/local/bin/:${PATH}" [tool.autopep8] max_line_length = 120 + From 0a6eef05e3671d99e66268bb95456927105e774a Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 20 Oct 2025 14:58:43 +0200 Subject: [PATCH 27/34] fix: sanitized wheel structure. To test --- CMakeLists.txt | 13 ++++++++++--- deviceaccess.py | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 deviceaccess.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 2076133..0f6f627 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,9 +112,16 @@ endif() # ==============================================================================# if(SKBUILD) - install( FILES ${PROJECT_BINARY_DIR}/deviceaccess.pyi DESTINATION ${SKBUILD_PLATLIB_DIR}) - install( FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION ${SKBUILD_PLATLIB_DIR}) - install( TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}) + install( FILES ${PROJECT_BINARY_DIR}/deviceaccess.pyi + DESTINATION ${PROJECT_BINARY_DIR}) + install( FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py + DESTINATION ${PROJECT_BINARY_DIR} + RENAME __init__.py) + install( FILES ${PROJECT_SOURCE_DIR}/mtca4u.py + DESTINATION "${SKBUILD_PLATLIB_DIR}/mtca4u" + RENAME __init__.py) + install( TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION ${PROJECT_BINARY_DIR}) else() # install Python modules to correct platform-dependent directory (if installing to system prefix) if( "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr" OR "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local" ) diff --git a/deviceaccess.py b/deviceaccess.py new file mode 100644 index 0000000..4827c1e --- /dev/null +++ b/deviceaccess.py @@ -0,0 +1,8 @@ +from . import deviceaccess + +try: + __all__ = tuple(deviceaccess.__all__) +except Exception: + __all__ = tuple(n for n in dir(deviceaccess) if not n.startswith("_")) + +globals().update({name: getattr(deviceaccess, name) for name in __all__}) From a1c8a1222ed7dbceca4996bdaf605f377d44ba75 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 20 Oct 2025 16:06:16 +0200 Subject: [PATCH 28/34] fix: improved modules --- CMakeLists.txt | 9 +++------ pyproject.toml | 1 + deviceaccess.py => src/deviceaccess/__init__.py | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) rename deviceaccess.py => src/deviceaccess/__init__.py (88%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f6f627..a4a53f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,15 +113,12 @@ endif() if(SKBUILD) install( FILES ${PROJECT_BINARY_DIR}/deviceaccess.pyi - DESTINATION ${PROJECT_BINARY_DIR}) - install( FILES ${PROJECT_SOURCE_DIR}/deviceaccess.py - DESTINATION ${PROJECT_BINARY_DIR} - RENAME __init__.py) + DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess") install( FILES ${PROJECT_SOURCE_DIR}/mtca4u.py - DESTINATION "${SKBUILD_PLATLIB_DIR}/mtca4u" + DESTINATION "${PROJECT_SOURCE_DIR}/src/mtca4u" RENAME __init__.py) install( TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${PROJECT_BINARY_DIR}) + LIBRARY DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess") else() # install Python modules to correct platform-dependent directory (if installing to system prefix) if( "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr" OR "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local" ) diff --git a/pyproject.toml b/pyproject.toml index c6b1e03..69b79cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ minimum-version = "0.4" # Setuptools-style build caching in a local directory build-dir = "build/{wheel_tag}" +wheel.packages = ["src/mtca4u", "src/deviceaccess"] [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" diff --git a/deviceaccess.py b/src/deviceaccess/__init__.py similarity index 88% rename from deviceaccess.py rename to src/deviceaccess/__init__.py index 4827c1e..0aef6ef 100644 --- a/deviceaccess.py +++ b/src/deviceaccess/__init__.py @@ -5,4 +5,5 @@ except Exception: __all__ = tuple(n for n in dir(deviceaccess) if not n.startswith("_")) +# Populate the namespace eagerly globals().update({name: getattr(deviceaccess, name) for name in __all__}) From eeaf5971a9578434753ec7ccbb1e033dba5ae044 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 20 Oct 2025 20:41:15 +0200 Subject: [PATCH 29/34] chore: rename extension name --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a4a53f9..8ebb860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,7 +118,8 @@ if(SKBUILD) DESTINATION "${PROJECT_SOURCE_DIR}/src/mtca4u" RENAME __init__.py) install( TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess") + LIBRARY DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess" + RENAME deviceaccess.so) else() # install Python modules to correct platform-dependent directory (if installing to system prefix) if( "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr" OR "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local" ) From cadc8bcd22725320e31a8733e970de7581f7a870 Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 20 Oct 2025 21:22:03 +0200 Subject: [PATCH 30/34] fix: move deviceaccess.pyi to __init__.py when generatin the wheel --- CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ebb860..2a8a033 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,13 +113,15 @@ endif() if(SKBUILD) install( FILES ${PROJECT_BINARY_DIR}/deviceaccess.pyi - DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess") + DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess" + RENAME __init__.pyi ) + install( FILES ${PROJECT_BINARY_DIR}/deviceaccess.pyi + DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess" ) install( FILES ${PROJECT_SOURCE_DIR}/mtca4u.py DESTINATION "${PROJECT_SOURCE_DIR}/src/mtca4u" - RENAME __init__.py) + RENAME __init__.py ) install( TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess" - RENAME deviceaccess.so) + LIBRARY DESTINATION "${PROJECT_SOURCE_DIR}/src/deviceaccess" ) else() # install Python modules to correct platform-dependent directory (if installing to system prefix) if( "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr" OR "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local" ) From f9678744a96a63cd3018f977d9c0d0761fea5abb Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Mon, 20 Oct 2025 21:39:15 +0200 Subject: [PATCH 31/34] feat: added py.typed for pyright --- src/deviceaccess/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/deviceaccess/py.typed diff --git a/src/deviceaccess/py.typed b/src/deviceaccess/py.typed new file mode 100644 index 0000000..e69de29 From 5be6af2acd3ee5d1a9867c1f912a3cb45175336a Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Tue, 21 Oct 2025 11:26:39 +0200 Subject: [PATCH 32/34] chore: set more precise manylinux images in pyproject.toml --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 69b79cf..de86016 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,9 +53,9 @@ wheel.packages = ["src/mtca4u", "src/deviceaccess"] [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" -skip = "cp36-* cp37-* cp314t-* *-win32 *_i686 *-musllinux_*" -manylinux-x86_64-image = "manylinux_2_28" -manylinux-aarch64-image = "manylinux_2_28" +skip = "cp314t-* *-win32 *_i686 *-musllinux_*" +manylinux-x86_64-image = "manylinux_2_28_x86_64" +manylinux-aarch64-image = "manylinux_2_28_aarch64" # Necessary to see build output from the actual compilation build-verbosity = 1 From e7a2acaa548863092f656e1049874ba0a58bc85a Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Tue, 21 Oct 2025 11:47:44 +0200 Subject: [PATCH 33/34] chore: cleaned up pre_build_manylinux.sh installation of system dependency packages --- pre_build_manylinux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_build_manylinux.sh b/pre_build_manylinux.sh index 8a5d2b7..3bf3f29 100755 --- a/pre_build_manylinux.sh +++ b/pre_build_manylinux.sh @@ -28,7 +28,7 @@ build_cmake () { # Install required system packages dnf install -y epel-release -dnf -y install gcc-c++ gcc-toolset-14-libatomic-devel libxml++-devel boost1.78 boost1.78-system boost1.78-thread boost1.78-chrono boost1.78-filesystem boost1.78-python3-devel +dnf -y install gcc-c++ gcc-toolset-14-libatomic-devel libxml++-devel boost1.78-devel g++ --version From e3ac58d7da7d6d2d5e8774f9de3609e932349c8b Mon Sep 17 00:00:00 2001 From: Andrea Bellandi Date: Wed, 12 Nov 2025 09:26:36 +0100 Subject: [PATCH 34/34] fix: add correct manylinux address --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index de86016..6c37ed0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,8 +54,8 @@ wheel.packages = ["src/mtca4u", "src/deviceaccess"] [tool.cibuildwheel] before-all = "bash pre_build_manylinux.sh" skip = "cp314t-* *-win32 *_i686 *-musllinux_*" -manylinux-x86_64-image = "manylinux_2_28_x86_64" -manylinux-aarch64-image = "manylinux_2_28_aarch64" +manylinux-x86_64-image = "quay.io/pypa/manylinux_2_28_x86_64:2025.10.10-1" +manylinux-aarch64-image = "quay.io/pypa/manylinux_2_28_aarch64:2025.10.10-1" # Necessary to see build output from the actual compilation build-verbosity = 1