diff --git a/.github/workflows/standard.yml b/.github/workflows/standard.yml new file mode 100644 index 0000000..a22e08a --- /dev/null +++ b/.github/workflows/standard.yml @@ -0,0 +1,64 @@ +name: PCDS Standard Testing + +on: + push: + pull_request: + release: + types: + - created + +jobs: + pre-commit: + name: "pre-commit checks" + uses: pcdshub/pcds-ci-helpers/.github/workflows/pre-commit.yml@master + with: + args: "--all-files" + + conda-test: + strategy: + fail-fast: false + matrix: + include: + - python-version: "3.9" + deploy-on-success: true + - python-version: "3.10" + - python-version: "3.11" + experimental: true + - python-version: "3.12" + experimental: true + + name: "Conda" + uses: pcdshub/pcds-ci-helpers/.github/workflows/python-conda-test.yml@master + secrets: inherit + with: + package-name: "pyca" + python-version: ${{ matrix.python-version }} + experimental: ${{ matrix.experimental || false }} + deploy-on-success: ${{ matrix.deploy-on-success || false }} + testing-extras: "" + system-packages: "" + use-setuptools-scm: true + + pip-test: + strategy: + fail-fast: false + matrix: + include: + - python-version: "3.9" + deploy-on-success: true + - python-version: "3.10" + - python-version: "3.11" + experimental: true + - python-version: "3.12" + experimental: true + + name: "Pip" + uses: pcdshub/pcds-ci-helpers/.github/workflows/python-pip-test.yml@master + secrets: inherit + with: + package-name: "pyca" + python-version: ${{ matrix.python-version }} + experimental: ${{ matrix.experimental || false }} + deploy-on-success: ${{ matrix.deploy-on-success || false }} + system-packages: "" + testing-extras: "" diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index a40f36e..9ecbe12 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -1,11 +1,10 @@ {% set package_name = "pyca" %} {% set import_name = "pyca" %} -{% set version = load_file_regex(load_file=os.path.join(import_name, "_version.py"), regex_pattern=".*version = '(\S+)'").group(1) %} -{% set EPICS = '3.14.12.6' %} +{% set data = load_setup_py_data() %} package: name: {{ package_name }} - version: {{ version }} + version: {{ data.get('version') }} source: path: .. @@ -14,33 +13,52 @@ build: number: 0 noarch: python script: {{ PYTHON }} -m pip install . -vv - - + skip: true # [win] + missing_dso_whitelist: + - '*/libca.*' + - '*/libCom.*' requirements: build: - - python {{ PY_VER }}* - - epics-base {{ EPICS }}* - - numpy {{ NPY_VER }}* - - setuptools_scm - - pip + - epicscorelibs + - python >=3.9 + - pip + - numpy + - setuptools + - setuptools_scm + host: + - python >=3.9 + - epicscorelibs + - numpy + - pip + - setuptools + - setuptools_dso + - setuptools_scm run: - - python {{ PY_VER }}* - - epics-base {{ EPICS }}* - - numpy {{ NPY_VER }}* - - + - python + - epicscorelibs + - numpy test: requires: - - pcaspy + - pcaspy imports: - - pyca - - psp - - + - pyca + - psp about: home: https://github.com/slaclab/pyca - licence: SLAC Open Licence - summary: Python Channel Access + license: LicenseRef-BSD-3-Clause-SLAC + license_family: BSD + license_file: LICENSE.md + summary: PyCA - lightweight bindings for Python applications to access EPICS PVs. + + description: | + PyCA (Python Channel Access) is a module that offers lightweight + bindings for Python applications to access EPICS PVs. It acts as + a channel access client, much like pyepics. The intention of the + module is to provide better performance for embedded applications, + rather than to provide an interactive interface. The most significant + gains will be found when monitoring large waveforms that need to be + processed before exposing them the Python layer. + dev_url: https://github.com/slaclab/pyca diff --git a/dev-requirements.txt b/dev-requirements.txt index 5ee0193..1f73203 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1 +1,2 @@ pcaspy +pytest \ No newline at end of file diff --git a/pyca/getfunctions.hh b/pyca/getfunctions.hh index c8add59..211fe31 100644 --- a/pyca/getfunctions.hh +++ b/pyca/getfunctions.hh @@ -1,4 +1,5 @@ #include "p3compat.h" +// #include "npy_2_compat.h" // Channel access GET template functions static inline PyObject* _pyca_get(const dbr_string_t value) { @@ -107,7 +108,8 @@ PyObject* _pyca_get_value(capv* pv, const T* dbrv, long count) npy_intp dims[1] = {count}; int typenum = _numpy_array_type(&(dbrv->value)); PyObject* nparray = PyArray_EMPTY(1, dims, typenum, 0); - memcpy(PyArray_DATA(nparray), &(dbrv->value), count*sizeof(dbrv->value)); + PyArrayObject *arr = (PyArrayObject *)PyArray_FROM_O(nparray); + memcpy(PyArray_DATA(arr), &(dbrv->value), count*sizeof(dbrv->value)); return nparray; } else { PyObject* pytup = PyTuple_New(count); diff --git a/pyca/putfunctions.hh b/pyca/putfunctions.hh index 97c3fff..72c30d6 100644 --- a/pyca/putfunctions.hh +++ b/pyca/putfunctions.hh @@ -60,13 +60,16 @@ void _pyca_put_value(capv* pv, PyObject* pyvalue, T** buf, long count) } T* buffer = reinterpret_cast(pv->putbuffer); if (count == 1) { + // if we only want to put the first element if (PyTuple_Check(pyvalue)) { PyObject* pyval = PyTuple_GetItem(pyvalue, 0); _pyca_put(pyval, buffer); } else if (PyArray_Check(pyvalue)) { - void* npdata = PyArray_GETPTR1(pyvalue, 0); + // Convert to array + PyArrayObject *arr = (PyArrayObject *)PyArray_FROM_O(pyvalue); + char* npdata = static_cast(PyArray_GETPTR1(arr, 0)); if (PyArray_IsPythonScalar(pyvalue)) { - PyObject* pyval = PyArray_GETITEM(pyvalue, npdata); + PyObject* pyval = PyArray_GETITEM(arr, npdata); _pyca_put(pyval, buffer); } else { _pyca_put_np(npdata, buffer); @@ -85,11 +88,12 @@ void _pyca_put_value(capv* pv, PyObject* pyvalue, T** buf, long count) _pyca_put(pyval, buffer+i); } } else if (PyArray_Check(pyvalue)) { - bool py_type = PyArray_IsPythonScalar(pyvalue); + PyArrayObject *arr2 = (PyArrayObject *)PyArray_FROM_O(pyvalue); + bool py_type = PyArray_IsPythonScalar(arr2); for (long i=0; i(PyArray_GETPTR1(arr2, i)); if (py_type) { - PyObject* pyval = PyArray_GETITEM(pyvalue, npdata); + PyObject* pyval = PyArray_GETITEM(arr2, npdata); _pyca_put(pyval, buffer+i); } else { _pyca_put_np(npdata, buffer+i); diff --git a/pyca/pyca.cc b/pyca/pyca.cc index b88b038..cab7cc5 100644 --- a/pyca/pyca.cc +++ b/pyca/pyca.cc @@ -1,4 +1,6 @@ #include +// We apparently use deprecated API, but I can't find which bits to update +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #include #include #include @@ -785,7 +787,7 @@ extern "C" { Py_INCREF(pyca_caexc); PyModule_AddObject(module, "caexc", pyca_caexc); - PyEval_InitThreads(); + // PyEval_InitThreads(); if (!has_proc_context()) { int result = ca_context_create(ca_enable_preemptive_callback); if (result != ECA_NORMAL) { diff --git a/pyproject.toml b/pyproject.toml index b7bf058..64e90a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] build-backend = "setuptools.build_meta" -requires = [ "setuptools>=45", "setuptools_scm[toml]>=6.2", "numpy"] +requires = [ "setuptools>=45", "setuptools_scm[toml]>=6.2", "setuptools_dso", "numpy>1.23", "epicscorelibs"] [project] classifiers = [ "Development Status :: 2 - Pre-Alpha", "Natural Language :: English", "Programming Language :: Python :: 3",] diff --git a/requirements.txt b/requirements.txt index 24ce15a..2644905 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -numpy +epicscorelibs +numpy>1.23 diff --git a/setup.py b/setup.py index eaad7cf..354eb9a 100644 --- a/setup.py +++ b/setup.py @@ -1,32 +1,50 @@ -import os import sys -import numpy as np -from setuptools import Extension, setup - -if sys.platform == 'darwin': - libsrc = 'Darwin' - compiler = 'clang' -elif sys.platform.startswith('linux'): - libsrc = 'Linux' - compiler = 'gcc' -else: - libsrc = None - -epics_inc = os.getenv("EPICS_BASE") + "/include" -epics_lib = os.getenv("EPICS_BASE") + "/lib/" + os.getenv("EPICS_HOST_ARCH") -numpy_inc = np.get_include() -numpy_lib = np.__path__[0] - -pyca = Extension('pyca', - language='c++', - sources=['pyca/pyca.cc'], - include_dirs=['pyca', epics_inc, - epics_inc + '/os/' + libsrc, - epics_inc + '/compiler/' + compiler, - numpy_inc], - library_dirs=[epics_lib, numpy_lib], - runtime_library_dirs=[epics_lib, numpy_lib], - libraries=['Com', 'ca']) - -setup(ext_modules=[pyca,]) +import numpy +import platform +from setuptools_dso import Extension, setup + +from numpy import get_include +def get_numpy_include_dirs(): + return [get_include()] + +import epicscorelibs.path +import epicscorelibs.version +from epicscorelibs.config import get_config_var + + +extra = [] +if sys.platform=='linux2': + extra += ['-v'] +elif platform.system()=='Darwin': + # avoid later failure where install_name_tool may run out of space. + # install_name_tool: changing install names or rpaths can't be redone for: + # ... because larger updated load commands do not fit (the program must be relinked, + # and you may need to use -headerpad or -headerpad_max_install_names) + extra += ['-Wl,-headerpad_max_install_names'] + + +pyca = Extension( + name='pyca', + sources=['pyca/pyca.cc'], + include_dirs= get_numpy_include_dirs()+[epicscorelibs.path.include_path], + define_macros = get_config_var('CPPFLAGS'), + extra_compile_args = get_config_var('CXXFLAGS'), + extra_link_args = get_config_var('LDFLAGS')+extra, + dsos = ['epicscorelibs.lib.ca', + 'epicscorelibs.lib.Com' + ], + libraries=get_config_var('LDADD'), +) + +setup( + name='pyca', + description='python channel access library', + packages=['psp', 'pyca'], + ext_modules=[pyca], + install_requires = [ + epicscorelibs.version.abi_requires(), + 'numpy >=%s'%numpy.version.short_version, + ], + zip_safe=False, +)