diff --git a/.ci b/.ci index 4e4f33e..a32a1da 160000 --- a/.ci +++ b/.ci @@ -1 +1 @@ -Subproject commit 4e4f33e54f59343c77342200a95800073661e7ac +Subproject commit a32a1dab0586dde3f5b2990506d54950e61ce25b diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index c46ce43..a2056aa 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -159,6 +159,39 @@ jobs: python: "3.7" container: "python:3.7" profile: deb10 + + - os: windows-latest + cmp: vs2026 + configuration: default + base: "3.15" + python: "3.11" + profile: latest + test: yes + + - os: windows-latest + cmp: vs2026 + configuration: default + base: "7.0" + python: "3.11" + profile: latest + test: yes + + - os: windows-latest + cmp: vs2026 + configuration: default + base: "7.0" + python: "3.12" + profile: latest + test: yes + + - os: windows-latest + cmp: vs2026 + configuration: default + base: "7.0" + python: "3.13" + profile: latest + test: yes + steps: - uses: actions/checkout@v3 with: @@ -169,6 +202,7 @@ jobs: with: python-version: ${{ matrix.python }} - name: More Setup Python + shell: bash run: | python --version python -m pip --version diff --git a/configure/CONFIG_PY b/configure/CONFIG_PY index f2edef8..0eeb5f3 100644 --- a/configure/CONFIG_PY +++ b/configure/CONFIG_PY @@ -48,6 +48,10 @@ LOADABLE_SHRLIB_LDFLAGS = -bundle -flat_namespace -undefined dynamic_lookup LOADABLE_SHRLIB_SUFFIX = .so endif +ifeq ($(OS_CLASS),WIN32) +SHRLIB_SUFFIX_BASE = .pyd +endif + endif endif diff --git a/configure/RULES_PY b/configure/RULES_PY index c635d51..9f15b01 100644 --- a/configure/RULES_PY +++ b/configure/RULES_PY @@ -1,15 +1,15 @@ ifneq ($(T_A),) - ifndef PY_VER $(error Must include CONFIG_PY) endif +include $(CONFIG)/CONFIG_BASE + PY_FILES += $(PY:%=$(PY_INSTALL_DIR)/%) $(PY_FILES) : $(PY_INSTALL_DIR)/%: ../% - @[ -d $(dir $@) ] || install -d $(dir $@) - @echo "Install PY $@" - install -m 644 $< $@ + $(ECHO) "Install PY $@" + @$(INSTALL) -d -m $(INSTALL_PERMISSIONS) $< $(@D) build: $(PY_FILES) diff --git a/devsupApp/src/Makefile b/devsupApp/src/Makefile index 9f1b468..d995155 100644 --- a/devsupApp/src/Makefile +++ b/devsupApp/src/Makefile @@ -29,8 +29,11 @@ _dbapi_SRCS += utest.c _dbapi_SRCS += pyDevSupCommon_registerRecordDeviceDriver.cpp _dbapi_LIBS += $(EPICS_BASE_IOC_LIBS) +_dbapi_LDFLAGS_WIN32 += -LIBPATH:$(PY_LIBDIRS) +_dbapi_SYS_LIBS_WIN32 += ws2_32 Advapi32 Dbghelp PY += devsup/__init__.py +PY += devsup/_dbapi.dbd PY += devsup/db.py PY += devsup/dset.py PY += devsup/hooks.py @@ -61,7 +64,11 @@ pyconfig: ifneq (,$(T_A)) nose: +ifeq ($(OS),Windows_NT) + pushd "$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" && $(PYTHON) -m nose2 -v devsup $(NOSEFLAGS) && popd +else PYTHONPATH="${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" $(PYTHON) -m nose2 -v devsup $(NOSEFLAGS) +endif # bounce back down to the sphinx generated Makefile # aren't Makefiles fun... diff --git a/devsupApp/src/dbdset.c b/devsupApp/src/dbdset.c index 68d76e4..954d0c5 100644 --- a/devsupApp/src/dbdset.c +++ b/devsupApp/src/dbdset.c @@ -24,6 +24,7 @@ #include #include "pydevsup.h" +#include static int inshutdown; @@ -449,7 +450,8 @@ static long python_asub(aSubRecord* prec) } /* uglyness to detect aSubRecord */ -extern rset* pvar_rset_aSubRSET; +epicsShareExtern +rset* pvar_rset_aSubRSET; int isPyRecord(dbCommon *prec) { diff --git a/devsupApp/src/devsup/__init__.py b/devsupApp/src/devsup/__init__.py index dd8da5a..8bb090b 100644 --- a/devsupApp/src/devsup/__init__.py +++ b/devsupApp/src/devsup/__init__.py @@ -1,7 +1,29 @@ import os +import ctypes +import sys import atexit import tempfile +if sys.platform == 'win32': + # See https://stackoverflow.com/questions/72858093/how-to-specify-pyd-dll-dependencies-search-paths-at-runtime-with-python + # This is required for use of e.g. nose testing, but + # not when running as an IOC, since the IOC will already have loaded EPICS base DLLs. + epics_base = os.getenv('EPICS_BASE') + epics_host_arch = os.getenv('EPICS_HOST_ARCH') + if epics_base is not None and epics_host_arch is not None: + epics_base = epics_base.replace("/",'\\') + dll_path = epics_base + "\\bin\\" + epics_host_arch + try: + os.add_dll_directory(dll_path) + except AttributeError: + # See https://stackoverflow.com/questions/75794403/attributeerror-module-os-has-no-attribute-add-dll-directory + import ctypes + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + cookie = kernel32.AddDllDirectory(dll_path) + if not cookie: + error = ctypes.get_last_error() + raise OSError(f"Failed to add DLL directory: {error}") + from . import _dbapi from ._dbapi import (EPICS_VERSION_STRING, @@ -52,32 +74,9 @@ def _init(iocMain=False): path=os.path.join(XEPICS_BASE, "dbd")) _dbapi._dbd_rrd_base() - with tempfile.NamedTemporaryFile() as F: - F.write(""" -device(longin, INST_IO, pydevsupComIn, "Python Device") -device(longout, INST_IO, pydevsupComOut, "Python Device") - -device(ai, INST_IO, pydevsupComIn, "Python Device") -device(ao, INST_IO, pydevsupComOut, "Python Device") - -device(stringin, INST_IO, pydevsupComIn, "Python Device") -device(stringout, INST_IO, pydevsupComOut, "Python Device") - -device(bi, INST_IO, pydevsupComIn, "Python Device") -device(bo, INST_IO, pydevsupComOut, "Python Device") - -device(mbbi, INST_IO, pydevsupComIn, "Python Device") -device(mbbo, INST_IO, pydevsupComOut, "Python Device") - -device(mbbiDirect, INST_IO, pydevsupComIn, "Python Device") -device(mbboDirect, INST_IO, pydevsupComOut, "Python Device") - -device(waveform, INST_IO, pydevsupComIn, "Python Device") -device(aai, INST_IO, pydevsupComIn, "Python Device") -device(aao, INST_IO, pydevsupComOut, "Python Device") -""".encode('ascii')) - F.flush() - _dbapi.dbReadDatabase(F.name) + dirname = os.path.dirname(__file__) + dbd_name = dirname + "/_dbapi.dbd" + _dbapi.dbReadDatabase(dbd_name) _dbapi._dbd_setup() def _fini(iocMain=False): diff --git a/devsupApp/src/devsup/_dbapi.dbd b/devsupApp/src/devsup/_dbapi.dbd new file mode 100644 index 0000000..b42785a --- /dev/null +++ b/devsupApp/src/devsup/_dbapi.dbd @@ -0,0 +1,21 @@ + +device(longin, INST_IO, pydevsupComIn, "Python Device") +device(longout, INST_IO, pydevsupComOut, "Python Device") + +device(ai, INST_IO, pydevsupComIn, "Python Device") +device(ao, INST_IO, pydevsupComOut, "Python Device") + +device(stringin, INST_IO, pydevsupComIn, "Python Device") +device(stringout, INST_IO, pydevsupComOut, "Python Device") +device(bi, INST_IO, pydevsupComIn, "Python Device") +device(bo, INST_IO, pydevsupComOut, "Python Device") + +device(mbbi, INST_IO, pydevsupComIn, "Python Device") +device(mbbo, INST_IO, pydevsupComOut, "Python Device") + +device(mbbiDirect, INST_IO, pydevsupComIn, "Python Device") +device(mbboDirect, INST_IO, pydevsupComOut, "Python Device") + +device(waveform, INST_IO, pydevsupComIn, "Python Device") +device(aai, INST_IO, pydevsupComIn, "Python Device") +device(aao, INST_IO, pydevsupComOut, "Python Device") diff --git a/devsupApp/src/devsup/test/util.py b/devsupApp/src/devsup/test/util.py index 6177a4b..70d1ff6 100644 --- a/devsupApp/src/devsup/test/util.py +++ b/devsupApp/src/devsup/test/util.py @@ -43,10 +43,11 @@ def setUp(self): _init(iocMain=False) # load base.dbd if self.db is not None: - with tempfile.NamedTemporaryFile() as F: + with tempfile.NamedTemporaryFile(delete=False) as F: F.write(self.db.encode('ascii')) - F.flush() + F.close() _dbapi.dbReadDatabase(F.name) + os.unlink(F.name) if self.autostart: self.iocInit() diff --git a/iocBoot/iocFPM/Makefile b/iocBoot/iocFPM/Makefile index 79c4ce6..25b7be6 100644 --- a/iocBoot/iocFPM/Makefile +++ b/iocBoot/iocFPM/Makefile @@ -1,5 +1,5 @@ TOP = ../.. include $(TOP)/configure/CONFIG -ARCH = linux-x86_64 -TARGETS = envPaths +ARCH = $(EPICS_HOST_ARCH) +TARGETS = envPaths dllPath.bat include $(TOP)/configure/RULES.ioc diff --git a/iocBoot/iocapplmon/Makefile b/iocBoot/iocapplmon/Makefile index 79c4ce6..25b7be6 100644 --- a/iocBoot/iocapplmon/Makefile +++ b/iocBoot/iocapplmon/Makefile @@ -1,5 +1,5 @@ TOP = ../.. include $(TOP)/configure/CONFIG -ARCH = linux-x86_64 -TARGETS = envPaths +ARCH = $(EPICS_HOST_ARCH) +TARGETS = envPaths dllPath.bat include $(TOP)/configure/RULES.ioc diff --git a/iocBoot/iocapplmon/st.cmd b/iocBoot/iocapplmon/st.cmd index 142d051..f345464 100755 --- a/iocBoot/iocapplmon/st.cmd +++ b/iocBoot/iocapplmon/st.cmd @@ -1,4 +1,4 @@ -#!../../bin/linux-x86/softIocPy3.6 +#!../../bin/linux-x86_64/softIocPy3.6 < envPaths diff --git a/iocBoot/iocarchivemon/Makefile b/iocBoot/iocarchivemon/Makefile index 79c4ce6..25b7be6 100644 --- a/iocBoot/iocarchivemon/Makefile +++ b/iocBoot/iocarchivemon/Makefile @@ -1,5 +1,5 @@ TOP = ../.. include $(TOP)/configure/CONFIG -ARCH = linux-x86_64 -TARGETS = envPaths +ARCH = $(EPICS_HOST_ARCH) +TARGETS = envPaths dllPath.bat include $(TOP)/configure/RULES.ioc diff --git a/iocBoot/iocarchivemon/st.cmd b/iocBoot/iocarchivemon/st.cmd index be83af7..632e7a6 100755 --- a/iocBoot/iocarchivemon/st.cmd +++ b/iocBoot/iocarchivemon/st.cmd @@ -1,4 +1,4 @@ -#!../../bin/linux-x86/softIocPy3.6 +#!../../bin/linux-x86_64/softIocPy3.6 < envPaths diff --git a/iocBoot/ioccaputlog/Makefile b/iocBoot/ioccaputlog/Makefile index 79c4ce6..25b7be6 100644 --- a/iocBoot/ioccaputlog/Makefile +++ b/iocBoot/ioccaputlog/Makefile @@ -1,5 +1,5 @@ TOP = ../.. include $(TOP)/configure/CONFIG -ARCH = linux-x86_64 -TARGETS = envPaths +ARCH = $(EPICS_HOST_ARCH) +TARGETS = envPaths dllPath.bat include $(TOP)/configure/RULES.ioc diff --git a/iocBoot/iocweatherbnl/Makefile b/iocBoot/iocweatherbnl/Makefile index 79c4ce6..25b7be6 100644 --- a/iocBoot/iocweatherbnl/Makefile +++ b/iocBoot/iocweatherbnl/Makefile @@ -1,5 +1,5 @@ TOP = ../.. include $(TOP)/configure/CONFIG -ARCH = linux-x86_64 -TARGETS = envPaths +ARCH = $(EPICS_HOST_ARCH) +TARGETS = envPaths dllPath.bat include $(TOP)/configure/RULES.ioc diff --git a/makehelper.py b/makehelper.py index 05fd147..e281f39 100644 --- a/makehelper.py +++ b/makehelper.py @@ -26,26 +26,15 @@ pass out = open(sys.argv[1], 'w') -""" -3.2 sysconfig -3.10 sysconfig.get_path -3.10.13 distutils is deprecated. -3.12 distutils was removed. -""" -if sys.version_info >= (3,10,): - from sysconfig import get_config_var, get_path - incdirs = [get_path("include")] -else: - from distutils.sysconfig import get_config_var, get_python_inc - incdirs = [get_python_inc()] +from sysconfig import get_config_var, get_path, get_python_version -libdir = get_config_var('LIBDIR') or '' +incdirs = [get_path("include")] +if sys.platform == 'win32': + libdir = os.path.join(sys.prefix, 'libs') +else: + libdir = get_config_var('LIBDIR') or '' have_np='NO' - -""" -numpy 1.18, numpy.get_include() -""" try: from numpy import get_include numpy_dir = [get_include()] @@ -54,8 +43,8 @@ except ImportError: pass -print('TARGET_CFLAGS +=',get_config_var('BASECFLAGS'), file=out) -print('TARGET_CXXFLAGS +=',get_config_var('BASECFLAGS'), file=out) +print('TARGET_CFLAGS +=',get_config_var('BASECFLAGS') or '', file=out) +print('TARGET_CXXFLAGS +=',get_config_var('BASECFLAGS') or '', file=out) print('PY_VER :=',get_config_var('VERSION'), file=out) ldver = get_config_var('LDVERSION') @@ -84,4 +73,4 @@ print('PY_OK := YES', file=out) -out.close() +out.close() \ No newline at end of file diff --git a/pyIocApp/Makefile b/pyIocApp/Makefile index 198e833..3dc3914 100644 --- a/pyIocApp/Makefile +++ b/pyIocApp/Makefile @@ -19,6 +19,7 @@ SHRLIB_VERSION = 0 DBD += pyDevSup.dbd pyDevSup$(PY_LD_VER)_SYS_LIBS += python$(PY_LD_VER) +pyDevSup$(PY_LD_VER)_LDFLAGS_WIN32 += -LIBPATH:$(PY_LIBDIRS) pyDevSup$(PY_LD_VER)_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -43,6 +44,7 @@ softIocPy_DBD += system.dbd softIocPy$(PY_VER)_LIBS += pyDevSup$(PY_LD_VER) softIocPy$(PY_VER)_SYS_LIBS += python$(PY_LD_VER) +softIocPy$(PY_VER)_LDFLAGS_WIN32 += -LIBPATH:$(PY_LIBDIRS) # softIocPy_registerRecordDeviceDriver.cpp derives from softIocPy.dbd softIocPy$(PY_VER)_SRCS += softIocPy_registerRecordDeviceDriver.cpp diff --git a/pyIocApp/setup.c b/pyIocApp/setup.c index 61c3861..0e75d51 100644 --- a/pyIocApp/setup.c +++ b/pyIocApp/setup.c @@ -23,6 +23,10 @@ #include #include "pydevsup.h" +#ifdef _WIN32 +#include +#define PATH_MAX _MAX_PATH +#endif static void cleanupPy(void *junk) {