Merge remote-tracking branch 'origin/devel'
* origin/devel: Python init: don't install sig handlers. Fix #5 Fix pyIocApp's Makefile testing cleanup cleanup and inithook minor update doc Base 3.14 py3 travis-ci update test field access and dset start PDB unittest rework to separate out python module separate softIocPy build Makefile updates from p4p # Conflicts: # devsupApp/src/dbapi.c # devsupApp/src/pydevsup.h
This commit is contained in:
15
.travis.yml
15
.travis.yml
@ -14,17 +14,20 @@ install:
|
||||
- ./build-deps.sh
|
||||
script:
|
||||
- make PYTHON=`which python` -j2
|
||||
- if [ "$TEST" = "YES" ]; then make PYTHON=`which python` -j2 nose; fi
|
||||
matrix:
|
||||
include:
|
||||
- python: "2.7"
|
||||
env: BRBASE=3.16 PROF=deb8
|
||||
- python: "2.7"
|
||||
env: BRBASE=3.16 PROF=deb8 CMPLR=clang
|
||||
env: BRBASE=7.0 PROF=deb8 TEST=YES
|
||||
- python: "3.4"
|
||||
env: BRBASE=3.16 PROF=deb8
|
||||
env: BRBASE=7.0 PROF=deb8 TEST=YES
|
||||
- python: "3.5"
|
||||
env: BRBASE=7.0 PROF=deb9 TEST=YES
|
||||
- python: "3.6"
|
||||
env: BRBASE=3.16 PROF=deb9
|
||||
env: BRBASE=7.0 PROF=deb9 TEST=YES
|
||||
- python: "2.7"
|
||||
env: BRBASE=3.15 PROF=deb8
|
||||
env: BRBASE=3.16 PROF=deb8 TEST=YES
|
||||
- python: "2.7"
|
||||
env: BRBASE=3.15 PROF=deb8 TEST=YES
|
||||
- python: "2.7"
|
||||
env: BRBASE=3.14 PROF=deb8
|
||||
|
18
Makefile
18
Makefile
@ -10,14 +10,24 @@ define DIR_template
|
||||
endef
|
||||
$(foreach dir, $(filter-out configure,$(DIRS)),$(eval $(call DIR_template,$(dir))))
|
||||
|
||||
pyIocApp_DEPEND_DIRS += devsupApp
|
||||
|
||||
iocBoot_DEPEND_DIRS += $(filter %App,$(DIRS))
|
||||
|
||||
include $(TOP)/configure/RULES_TOP
|
||||
|
||||
UNINSTALL_DIRS += $(wildcard $(INSTALL_LOCATION)/python*)
|
||||
|
||||
#useful targets includ: doc-html and doc-clean
|
||||
doc-%:
|
||||
PYTHONPATH=$$PWD/python$(PY_VER)/$(EPICS_HOST_ARCH) $(MAKE) -C documentation $*
|
||||
# jump to a sub-directory where CONFIG_PY has been included
|
||||
# can't include CONFIG_PY here as it may not exist yet
|
||||
nose sphinx sh ipython: all
|
||||
$(MAKE) -C devsupApp/src/O.$(EPICS_HOST_ARCH) $@ PYTHON=$(PYTHON)
|
||||
|
||||
doc: doc-html
|
||||
sphinx-clean:
|
||||
$(MAKE) -C documentation clean PYTHON=$(PYTHON)
|
||||
|
||||
sphinx-commit: sphinx
|
||||
touch documentation/_build/html/.nojekyll
|
||||
./commit-gh.sh documentation/_build/html
|
||||
|
||||
.PHONY: nose sphinx sphinx-commit sphinx-clean
|
||||
|
@ -2,24 +2,22 @@ ifneq ($(T_A),)
|
||||
|
||||
PYMODULE ?= YES
|
||||
|
||||
ifeq ($(PY_VER),)
|
||||
$(error Must set PY_VER to select a python version)
|
||||
ifeq ($(PYTHON),)
|
||||
$(error Must set PYTHON to select a python version)
|
||||
endif
|
||||
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY$(PY_VER).$(EPICS_HOST_ARCH).Common
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY$(PY_VER).Common.$(T_A)
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY$(PY_VER).$(EPICS_HOST_ARCH).$(T_A)
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY.$(EPICS_HOST_ARCH).Common
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY.Common.$(T_A)
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY.$(EPICS_HOST_ARCH).$(T_A)
|
||||
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY$(PY_VER).$(EPICS_HOST_ARCH).Common
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY$(PY_VER).Common.$(T_A)
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY$(PY_VER).$(EPICS_HOST_ARCH).$(T_A)
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY.$(EPICS_HOST_ARCH).Common
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY.Common.$(T_A)
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY.$(EPICS_HOST_ARCH).$(T_A)
|
||||
|
||||
ifneq ($(PY_OK),YES)
|
||||
$(error No usable configuration for python$(PY_VER))
|
||||
$(error No usable configuration for $(PYTHON))
|
||||
endif
|
||||
|
||||
PYTHON ?= python$(PY_VER)
|
||||
|
||||
SHRLIB_DEPLIB_DIRS += $(PY_LIBDIRS)
|
||||
PROD_DEPLIB_DIRS += $(PY_LIBDIRS)
|
||||
|
||||
@ -27,18 +25,29 @@ INCLUDES += $(PY_INCDIRS:%=-I%)
|
||||
|
||||
ifeq ($(HAVE_NUMPY),YES)
|
||||
TARGET_CPPFLAGS += -DHAVE_NUMPY
|
||||
else
|
||||
$(error numpy required)
|
||||
endif
|
||||
|
||||
LIB_SYS_LIBS += python$(PY_LD_VER)
|
||||
PROD_SYS_LIBS += python$(PY_LD_VER)
|
||||
#LIB_SYS_LIBS += python$(PY_LD_VER)
|
||||
#PROD_SYS_LIBS += python$(PY_LD_VER)
|
||||
|
||||
PY_INSTALL_DIR = $(INSTALL_LOCATION)/python$(PY_VER)/$(T_A)
|
||||
PY_INSTALL_DIR = $(INSTALL_LOCATION)/python$(PY_LD_VER)/$(T_A)
|
||||
|
||||
ifneq ($(PYMODULE),NO)
|
||||
|
||||
# Python loadables have no prefix (eg 'pymod.so')
|
||||
# and are installed alongsize .py files
|
||||
LOADABLE_SHRLIB_PREFIX =
|
||||
ifneq ($(PYMODULE),NO)
|
||||
|
||||
INSTALL_SHRLIB = $(PY_INSTALL_DIR)
|
||||
|
||||
ifeq ($(OS_CLASS),Darwin)
|
||||
# need -undefined dynamic_lookup
|
||||
LOADABLE_SHRLIB_LDFLAGS = -bundle -flat_namespace -undefined dynamic_lookup
|
||||
LOADABLE_SHRLIB_SUFFIX = .so
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
endif
|
||||
|
@ -37,7 +37,9 @@ PY_VER=2.7
|
||||
|
||||
# Module will be build against this version of the
|
||||
# Python interpreter
|
||||
#PYTHON = python$(PY_VER)
|
||||
PYTHON ?= python$(PY_VER)
|
||||
|
||||
USR_CPPFLAGS += -DUSE_TYPED_RSET
|
||||
|
||||
-include $(TOP)/configure/CONFIG_SITE.local
|
||||
-include $(TOP)/../CONFIG_SITE.local
|
||||
|
@ -2,20 +2,19 @@ TOP=..
|
||||
|
||||
include $(TOP)/configure/CONFIG
|
||||
|
||||
ifeq ($(PY_VER),)
|
||||
$(error Must set PY_VER to select a python version)
|
||||
endif
|
||||
PYTHON ?= python$(PY_VER)
|
||||
|
||||
TARGETS = $(CONFIG_TARGETS)
|
||||
|
||||
ifdef T_A
|
||||
CONFIGS = CONFIG_PY RULES_PY os/CONFIG_PY$(PY_VER).Common.$(T_A)
|
||||
CONFIGS = CONFIG_PY RULES_PY os/CONFIG_PY.Common.$(T_A)
|
||||
endif
|
||||
|
||||
CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS)))
|
||||
|
||||
include $(TOP)/configure/RULES
|
||||
|
||||
os/CONFIG_PY$(PY_VER).Common.$(T_A): $(TOP)/makehelper.py
|
||||
os/CONFIG_PY.Common.$(T_A): $(TOP)/makehelper.py
|
||||
[ -d $(dir $@) ] || $(MKDIR) $(dir $@)
|
||||
$(PYTHON) $< $@
|
||||
|
||||
|
@ -1,80 +1,36 @@
|
||||
TOP=../..
|
||||
|
||||
include $(TOP)/configure/CONFIG
|
||||
PYMODULE = NO
|
||||
include $(TOP)/configure/CONFIG_PY
|
||||
#----------------------------------------
|
||||
# ADD MACRO DEFINITIONS AFTER THIS LINE
|
||||
#=============================
|
||||
|
||||
#=============================
|
||||
# Build the IOC application
|
||||
INSTALL_SHRLIB = $(PY_INSTALL_DIR)/devsup
|
||||
|
||||
LIBRARY = pyDevSup$(PY_LD_VER)
|
||||
LOADABLE_LIBRARY_HOST += _dbapi
|
||||
|
||||
SHRLIB_VERSION = 0
|
||||
TARGETS += $(COMMON_DIR)/pyDevSupCommon.dbd
|
||||
DBDDEPENDS_FILES += pyDevSupCommon.dbd$(DEP)
|
||||
|
||||
DBD += pyDevSup.dbd
|
||||
pyDevSupCommon_DBD += base.dbd
|
||||
|
||||
pyDevSup$(PY_LD_VER)_SYS_LIBS += python$(PY_LD_VER)
|
||||
dbapi_CPPFLAGS += -DXEPICS_ARCH=\"$(T_A)\"
|
||||
dbapi_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||
dbapi_CPPFLAGS += -DXEPICS_BASE=\"$(EPICS_BASE)\"
|
||||
dbapi_CPPFLAGS += -DPYDIR=\"python$(PY_VER)\"
|
||||
|
||||
setup_CPPFLAGS += -DXEPICS_ARCH=\"$(T_A)\"
|
||||
setup_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||
setup_CPPFLAGS += -DXEPICS_BASE=\"$(EPICS_BASE)\"
|
||||
setup_CPPFLAGS += -DPYDIR=\"python$(PY_VER)\"
|
||||
_dbapi_SRCS += dbapi.c
|
||||
_dbapi_SRCS += dbrec.c
|
||||
_dbapi_SRCS += dbfield.c
|
||||
_dbapi_SRCS += dbdset.c
|
||||
_dbapi_SRCS += utest.c
|
||||
|
||||
devsupMain_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||
|
||||
pyDevSup$(PY_LD_VER)_SRCS += setup.c
|
||||
pyDevSup$(PY_LD_VER)_SRCS += dbbase.c
|
||||
pyDevSup$(PY_LD_VER)_SRCS += dbrec.c
|
||||
pyDevSup$(PY_LD_VER)_SRCS += dbfield.c
|
||||
pyDevSup$(PY_LD_VER)_SRCS += dbdset.c
|
||||
|
||||
pyDevSup$(PY_LD_VER)_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
|
||||
PROD_IOC = softIocPy$(PY_VER)
|
||||
PRODNAME = $(addsuffix $(EXE),$(PROD))
|
||||
|
||||
# softIocPy.dbd will be created and installed
|
||||
DBD += softIocPy.dbd
|
||||
|
||||
# softIocPy.dbd will be made up from these files:
|
||||
softIocPy_DBD += base.dbd
|
||||
softIocPy_DBD += pyDevSup.dbd
|
||||
|
||||
softIocPy_DBD += system.dbd
|
||||
|
||||
softIocPy$(PY_VER)_LIBS += pyDevSup$(PY_LD_VER)
|
||||
|
||||
# softIocPy_registerRecordDeviceDriver.cpp derives from softIocPy.dbd
|
||||
softIocPy$(PY_VER)_SRCS += softIocPy_registerRecordDeviceDriver.cpp
|
||||
|
||||
# Build the main IOC entry point on workstation OSs.
|
||||
softIocPy$(PY_VER)_SRCS_DEFAULT += devsupMain.cpp
|
||||
|
||||
ifneq ($(DEVIOCSTATS),)
|
||||
softIocPy_DBD += devIocStats.dbd
|
||||
softIocPy$(PY_VER)_LIBS += devIocStats
|
||||
endif
|
||||
|
||||
ifneq ($(AUTOSAVE),)
|
||||
softIocPy_DBD += asSupport.dbd
|
||||
softIocPy$(PY_VER)_LIBS += autosave
|
||||
endif
|
||||
|
||||
ifneq ($(CAPUTLOG),)
|
||||
softIocPy_DBD += caPutLog.dbd
|
||||
softIocPy$(PY_VER)_LIBS += caPutLog
|
||||
endif
|
||||
|
||||
# Finally link to the EPICS Base libraries
|
||||
softIocPy$(PY_VER)_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
_dbapi_SRCS += pyDevSupCommon_registerRecordDeviceDriver.cpp
|
||||
|
||||
_dbapi_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
PY += devsup/__init__.py
|
||||
PY += devsup/_nullapi.py
|
||||
PY += devsup/db.py
|
||||
PY += devsup/dset.py
|
||||
PY += devsup/hooks.py
|
||||
@ -83,6 +39,10 @@ PY += devsup/util.py
|
||||
PY += devsup/disect.py
|
||||
PY += devsup/ptable.py
|
||||
|
||||
PY += devsup/test/__init__.py
|
||||
PY += devsup/test/util.py
|
||||
PY += devsup/test/test_db.py
|
||||
|
||||
#===========================
|
||||
|
||||
include $(TOP)/configure/RULES
|
||||
@ -98,3 +58,20 @@ pyconfig:
|
||||
@echo "Library path: $(PY_LIBDIRS)"
|
||||
@echo "USR_CPPFLAGS: $(USR_CPPFLAGS)"
|
||||
@echo "USR_LDFLAGS: $(USR_LDFLAGS)"
|
||||
|
||||
ifneq (,$(T_A))
|
||||
nose:
|
||||
PYTHONPATH="${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" $(PYTHON) -m nose -P devsup $(NOSEFLAGS)
|
||||
|
||||
# bounce back down to the sphinx generated Makefile
|
||||
# aren't Makefiles fun...
|
||||
sphinx:
|
||||
PYTHONPATH="${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" $(MAKE) -C $(TOP)/documentation html
|
||||
|
||||
sh:
|
||||
echo "export PYTHONPATH=\$${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" > $(OUTPUT)
|
||||
|
||||
ipython:
|
||||
PYTHONPATH="${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" $(PYTHON) -c "import sys; sys.argv[0] = '$(PYTHON)'; from IPython.terminal.ipapp import launch_new_instance; launch_new_instance()"
|
||||
|
||||
endif
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Global interpreter setup
|
||||
*/
|
||||
|
||||
/* python has its own ideas about which version to support */
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
@ -21,11 +18,15 @@
|
||||
#include <epicsThread.h>
|
||||
#include <epicsExit.h>
|
||||
#include <alarm.h>
|
||||
#include <iocsh.h>
|
||||
#include <iocInit.h>
|
||||
|
||||
#include "pydevsup.h"
|
||||
|
||||
initHookState pyInitLastState;
|
||||
|
||||
extern int pyDevSupCommon_registerRecordDeviceDriver(DBBASE *pbase);
|
||||
|
||||
typedef struct {
|
||||
const initHookState state;
|
||||
const char * const name;
|
||||
@ -104,6 +105,20 @@ void pyfile(const char* file)
|
||||
PyGILState_Release(state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const iocshArg argCode = {"python code", iocshArgString};
|
||||
static const iocshArg argFile = {"file", iocshArgString};
|
||||
|
||||
static const iocshArg* const codeArgs[] = {&argCode};
|
||||
static const iocshArg* const fileArgs[] = {&argFile};
|
||||
|
||||
static const iocshFuncDef codeDef = {"py", 1, codeArgs};
|
||||
static const iocshFuncDef fileDef = {"pyfile", 1, fileArgs};
|
||||
|
||||
static void codeRun(const iocshArgBuf *args){py(args[0].sval);}
|
||||
static void fileRun(const iocshArgBuf *args){pyfile(args[0].sval);}
|
||||
|
||||
initHookState pyInitLastState = (initHookState)-1;
|
||||
|
||||
static void pyhook(initHookState state)
|
||||
@ -139,28 +154,160 @@ fail:
|
||||
PyGILState_Release(gilstate);
|
||||
}
|
||||
|
||||
static
|
||||
PyObject* py_announce(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
static char* names[] = {"state", NULL};
|
||||
int state;
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "i", names, &state))
|
||||
return NULL;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
initHookAnnounce((initHookState)state);
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_iocsh(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
int ret;
|
||||
static char* names[] = {"script", "cmd", NULL};
|
||||
char *script=NULL, *cmd=NULL;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|ss", names, &script, &cmd))
|
||||
return NULL;
|
||||
|
||||
if(!(!script ^ !cmd)) {
|
||||
PyErr_SetString(PyExc_ValueError, "iocsh requires a script file name or command string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
if(script)
|
||||
ret = iocsh(script);
|
||||
else
|
||||
ret = iocshCmd(cmd);
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
return PyInt_FromLong(ret);
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_dbReadDatabase(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
long status;
|
||||
static char* names[] = {"name", "fp", "path", "sub", NULL};
|
||||
char *fname=NULL, *path=NULL, *sub=NULL;
|
||||
int fd=-1;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|siss", names, &fname, &fd, &path, &sub))
|
||||
return NULL;
|
||||
|
||||
if(!((!fname) ^ (fd<0))) {
|
||||
PyErr_SetString(PyExc_ValueError, "dbReadDatabase requires a file name or descriptor");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
if(fname) {
|
||||
status = dbReadDatabase(&pdbbase, fname, path, sub);
|
||||
} else {
|
||||
FILE *ff = fdopen(fd, "r");
|
||||
status = dbReadDatabaseFP(&pdbbase, ff, path, sub);
|
||||
// dbReadDatabaseFP() has called fclose()
|
||||
}
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
if(status) {
|
||||
char buf[30];
|
||||
errSymLookup(status, buf, sizeof(buf));
|
||||
PyErr_SetString(PyExc_RuntimeError, buf);
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_iocInit(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
static char* names[] = {"isolate", NULL};
|
||||
PyObject *pyisolate = Py_True;
|
||||
int isolate, ret;
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|O", names, &pyisolate))
|
||||
return NULL;
|
||||
|
||||
isolate = PyObject_IsTrue(pyisolate);
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
ret = isolate ? iocBuildIsolated() : iocBuild();
|
||||
if(!ret)
|
||||
ret = iocRun();
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
if(ret)
|
||||
return PyErr_Format(PyExc_RuntimeError, "Error %d", ret);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_pyDevSupCommon(PyObject *unused)
|
||||
{
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
pyDevSupCommon_registerRecordDeviceDriver(pdbbase);
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static struct PyMethodDef dbapimethod[] = {
|
||||
{"initHookAnnounce", (PyCFunction)py_announce, METH_VARARGS|METH_KEYWORDS,
|
||||
"initHookAnnounce(state)\n"},
|
||||
{"iocsh", (PyCFunction)py_iocsh, METH_VARARGS|METH_KEYWORDS,
|
||||
"Execute IOC shell script or command"},
|
||||
{"dbReadDatabase", (PyCFunction)py_dbReadDatabase, METH_VARARGS|METH_KEYWORDS,
|
||||
"Load EPICS database file"},
|
||||
{"iocInit", (PyCFunction)py_iocInit, METH_NOARGS,
|
||||
"Initialize IOC"},
|
||||
{"_dbd_setup", (PyCFunction)pyDBD_setup, METH_NOARGS, ""},
|
||||
{"_dbd_rrd_base", (PyCFunction)py_pyDevSupCommon, METH_NOARGS, ""},
|
||||
{"_dbd_cleanup", (PyCFunction)pyDBD_cleanup, METH_NOARGS, ""},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
static struct PyModuleDef dbapimodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_dbapi",
|
||||
"devsup._dbapi",
|
||||
NULL,
|
||||
-1,
|
||||
NULL
|
||||
&dbapimethod
|
||||
};
|
||||
#endif
|
||||
|
||||
/* initialize "magic" builtin module */
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyMODINIT_FUNC PyInit__dbapi(void)
|
||||
#else
|
||||
PyMODINIT_FUNC init_dbapi(void)
|
||||
#endif
|
||||
{
|
||||
PyObject *mod = NULL, *hookdict;
|
||||
PyObject *mod = NULL, *hookdict, *vertup;
|
||||
pystate *st;
|
||||
|
||||
pyDevReasonID = epicsThreadPrivateCreate();
|
||||
|
||||
iocshRegister(&codeDef, &codeRun);
|
||||
iocshRegister(&fileDef, &fileRun);
|
||||
initHookRegister(&pyhook);
|
||||
|
||||
import_array();
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
mod = PyModule_Create(&dbapimodule);
|
||||
#else
|
||||
mod = Py_InitModule("_dbapi", NULL);
|
||||
mod = Py_InitModule("devsup._dbapi", dbapimethod);
|
||||
#endif
|
||||
if(!mod)
|
||||
goto fail;
|
||||
@ -180,43 +327,6 @@ PyMODINIT_FUNC init_dbapi(void)
|
||||
}
|
||||
}
|
||||
|
||||
if(pyField_prepare(mod))
|
||||
goto fail;
|
||||
if(pyRecord_prepare(mod))
|
||||
goto fail;
|
||||
|
||||
MODINIT_RET(mod);
|
||||
|
||||
fail:
|
||||
fprintf(stderr, "Failed to initialize builtin _dbapi module!\n");
|
||||
Py_XDECREF(mod);
|
||||
MODINIT_RET(NULL);
|
||||
}
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
static struct PyModuleDef constantsmodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_dbconstants",
|
||||
NULL,
|
||||
-1,
|
||||
NULL
|
||||
};
|
||||
#endif
|
||||
|
||||
/* initialize "magic" builtin module */
|
||||
PyMODINIT_FUNC init_dbconstants(void)
|
||||
{
|
||||
PyObject *mod = NULL, *vertup;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
mod = PyModule_Create(&constantsmodule);
|
||||
#else
|
||||
mod = Py_InitModule("_dbconstants", NULL);
|
||||
#endif
|
||||
if(!mod)
|
||||
MODINIT_RET(NULL);
|
||||
|
||||
PyModule_AddIntMacro(mod, NO_ALARM);
|
||||
PyModule_AddIntMacro(mod, MINOR_ALARM);
|
||||
PyModule_AddIntMacro(mod, MAJOR_ALARM);
|
||||
@ -279,150 +389,17 @@ PyMODINIT_FUNC init_dbconstants(void)
|
||||
if(vertup)
|
||||
PyModule_AddObject(mod, "pydevver", vertup);
|
||||
|
||||
if(pyField_prepare(mod))
|
||||
goto fail;
|
||||
if(pyRecord_prepare(mod))
|
||||
goto fail;
|
||||
if(pyUTest_prepare(mod))
|
||||
goto fail;
|
||||
|
||||
MODINIT_RET(mod);
|
||||
}
|
||||
|
||||
static void cleanupPy(void *junk)
|
||||
{
|
||||
PyThreadState *state = PyGILState_GetThisThreadState();
|
||||
|
||||
PyEval_RestoreThread(state);
|
||||
|
||||
/* special "fake" hook for shutdown */
|
||||
pyhook((initHookState)9999);
|
||||
|
||||
pyDBD_cleanup();
|
||||
|
||||
pyField_cleanup();
|
||||
|
||||
Py_Finalize();
|
||||
|
||||
epicsThreadPrivateDelete(pyDevReasonID);
|
||||
}
|
||||
|
||||
/* Initialize the interpreter environment
|
||||
*/
|
||||
static void setupPyInit(void)
|
||||
{
|
||||
PyImport_AppendInittab("_dbapi", init_dbapi);
|
||||
PyImport_AppendInittab("_dbconstants", init_dbconstants);
|
||||
PyImport_AppendInittab("_dbbase", init_dbbase);
|
||||
|
||||
Py_Initialize();
|
||||
PyEval_InitThreads();
|
||||
|
||||
(void)PyEval_SaveThread();
|
||||
|
||||
epicsAtExit(&cleanupPy, NULL);
|
||||
}
|
||||
|
||||
static void extendPath(PyObject *list,
|
||||
const char *base,
|
||||
const char *archdir)
|
||||
{
|
||||
PyObject *mod, *ret;
|
||||
|
||||
mod = PyImport_ImportModule("os.path");
|
||||
if(!mod)
|
||||
return;
|
||||
|
||||
ret = PyObject_CallMethod(mod, "join", "sss", base, PYDIR, archdir);
|
||||
if(ret && !PySequence_Contains(list, ret)) {
|
||||
PyList_Insert(list, 0, ret);
|
||||
}
|
||||
Py_XDECREF(ret);
|
||||
Py_DECREF(mod);
|
||||
if(PyErr_Occurred()) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static void insertDefaultPath(PyObject *list)
|
||||
{
|
||||
const char *basedir, *pydevdir, *top, *arch;
|
||||
|
||||
basedir = getenv("EPICS_BASE");
|
||||
if(!basedir)
|
||||
basedir = XEPICS_BASE;
|
||||
pydevdir = getenv("PYDEV_BASE");
|
||||
if(!pydevdir)
|
||||
pydevdir = XPYDEV_BASE;
|
||||
top = getenv("TOP");
|
||||
arch = getenv("ARCH");
|
||||
if(!arch)
|
||||
arch = XEPICS_ARCH;
|
||||
|
||||
assert(PyList_Check(list));
|
||||
assert(PySequence_Check(list));
|
||||
extendPath(list, basedir, arch);
|
||||
extendPath(list, pydevdir, arch);
|
||||
if(top)
|
||||
extendPath(list, top, arch);
|
||||
}
|
||||
|
||||
static void setupPyPath(void)
|
||||
{
|
||||
PyObject *mod, *path = NULL;
|
||||
|
||||
mod = PyImport_ImportModule("sys");
|
||||
if(mod)
|
||||
path = PyObject_GetAttrString(mod, "path");
|
||||
fail:
|
||||
fprintf(stderr, "Failed to initialize builtin _dbapi module!\n");
|
||||
Py_XDECREF(mod);
|
||||
|
||||
if(path) {
|
||||
PyObject *cur;
|
||||
char cwd[PATH_MAX];
|
||||
|
||||
insertDefaultPath(path);
|
||||
|
||||
/* prepend current directory */
|
||||
if(getcwd(cwd, sizeof(cwd)-1)) {
|
||||
cwd[sizeof(cwd)-1] = '\0';
|
||||
cur = PyString_FromString(cwd);
|
||||
if(cur)
|
||||
PyList_Insert(path, 0, cur);
|
||||
Py_XDECREF(cur);
|
||||
}
|
||||
}
|
||||
Py_XDECREF(path);
|
||||
MODINIT_RET(NULL);
|
||||
}
|
||||
|
||||
|
||||
#include <iocsh.h>
|
||||
|
||||
static const iocshArg argCode = {"python code", iocshArgString};
|
||||
static const iocshArg argFile = {"file", iocshArgString};
|
||||
|
||||
static const iocshArg* const codeArgs[] = {&argCode};
|
||||
static const iocshArg* const fileArgs[] = {&argFile};
|
||||
|
||||
static const iocshFuncDef codeDef = {"py", 1, codeArgs};
|
||||
static const iocshFuncDef fileDef = {"pyfile", 1, fileArgs};
|
||||
|
||||
static void codeRun(const iocshArgBuf *args){py(args[0].sval);}
|
||||
static void fileRun(const iocshArgBuf *args){pyfile(args[0].sval);}
|
||||
|
||||
static void pySetupReg(void)
|
||||
{
|
||||
PyGILState_STATE state;
|
||||
|
||||
pyDevReasonID = epicsThreadPrivateCreate();
|
||||
|
||||
setupPyInit();
|
||||
iocshRegister(&codeDef, &codeRun);
|
||||
iocshRegister(&fileDef, &fileRun);
|
||||
initHookRegister(&pyhook);
|
||||
|
||||
state = PyGILState_Ensure();
|
||||
init_dbapi();
|
||||
setupPyPath();
|
||||
if(PyErr_Occurred()) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
PyGILState_Release(state);
|
||||
}
|
||||
|
||||
#include <epicsExport.h>
|
||||
epicsExportRegistrar(pySetupReg);
|
@ -1,136 +0,0 @@
|
||||
|
||||
/* python has its own ideas about which version to support */
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <epicsVersion.h>
|
||||
#include <dbCommon.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <dbAccess.h>
|
||||
#include <initHooks.h>
|
||||
#include <iocsh.h>
|
||||
#include <iocInit.h>
|
||||
|
||||
#include "pydevsup.h"
|
||||
|
||||
static
|
||||
PyObject *py_iocsh(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
int ret;
|
||||
static char* names[] = {"script", "cmd", NULL};
|
||||
char *script=NULL, *cmd=NULL;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|ss", names, &script, &cmd))
|
||||
return NULL;
|
||||
|
||||
if(!(!script ^ !cmd)) {
|
||||
PyErr_SetString(PyExc_ValueError, "iocsh requires a script file name or command string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
if(script)
|
||||
ret = iocsh(script);
|
||||
else
|
||||
ret = iocshCmd(cmd);
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
return PyInt_FromLong(ret);
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_dbReadDatabase(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
long status;
|
||||
static char* names[] = {"name", "fp", "path", "sub", NULL};
|
||||
char *fname=NULL, *path=NULL, *sub=NULL;
|
||||
int fd=-1;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|siss", names, &fname, &fd, &path, &sub))
|
||||
return NULL;
|
||||
|
||||
if(!((!fname) ^ (fd<0))) {
|
||||
PyErr_SetString(PyExc_ValueError, "dbReadDatabase requires a file name or descriptor");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
if(fname)
|
||||
status = dbReadDatabase(&pdbbase, fname, path, sub);
|
||||
else {
|
||||
FILE *ff = fdopen(fd, "r");
|
||||
status = dbReadDatabaseFP(&pdbbase, ff, path, sub);
|
||||
fclose(ff);
|
||||
}
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
if(status) {
|
||||
char buf[30];
|
||||
errSymLookup(status, buf, sizeof(buf));
|
||||
PyErr_SetString(PyExc_RuntimeError, buf);
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_iocInit(PyObject *unused)
|
||||
{
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
iocInit();
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static struct PyMethodDef dbbasemethods[] = {
|
||||
{"iocsh", (PyCFunction)py_iocsh, METH_VARARGS|METH_KEYWORDS,
|
||||
"Execute IOC shell script or command"},
|
||||
{"dbReadDatabase", (PyCFunction)py_dbReadDatabase, METH_VARARGS|METH_KEYWORDS,
|
||||
"Load EPICS database file"},
|
||||
{"iocInit", (PyCFunction)py_iocInit, METH_NOARGS,
|
||||
"Initialize IOC"},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
static struct PyModuleDef dbbasemodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_dbbase",
|
||||
NULL,
|
||||
-1,
|
||||
&dbbasemethods
|
||||
};
|
||||
#endif
|
||||
|
||||
/* initialize "magic" builtin module */
|
||||
PyMODINIT_FUNC init_dbbase(void)
|
||||
{
|
||||
PyObject *mod = NULL, *obj = NULL;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
mod = PyModule_Create(&dbbasemodule);
|
||||
#else
|
||||
mod = Py_InitModule("_dbbase", dbbasemethods);
|
||||
#endif
|
||||
if(!mod)
|
||||
goto fail;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3 || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION>=7)
|
||||
obj = PyCapsule_New(pdbbase, "pdbbase", NULL);
|
||||
#else
|
||||
obj = PyCObject_FromVoidPtrAndDesc(pdbbase, "pdbbase", NULL);
|
||||
#endif
|
||||
if(!obj)
|
||||
goto fail;
|
||||
PyModule_AddObject(mod, "pdbbase", obj);
|
||||
|
||||
MODINIT_RET(mod);
|
||||
fail:
|
||||
Py_XDECREF(obj);
|
||||
Py_XDECREF(mod);
|
||||
fprintf(stderr, "Failed to initialize builtin _dbbase module!\n");
|
||||
MODINIT_RET(NULL);
|
||||
}
|
@ -19,6 +19,8 @@
|
||||
#include <dbScan.h>
|
||||
#include <cantProceed.h>
|
||||
#include <registryFunction.h>
|
||||
#include <iocshRegisterCommon.h>
|
||||
#include <registryCommon.h>
|
||||
#include <aSubRecord.h>
|
||||
|
||||
#include "pydevsup.h"
|
||||
@ -471,8 +473,28 @@ int canIOScanRecord(dbCommon *prec)
|
||||
return !!priv->scanobj;
|
||||
}
|
||||
|
||||
static
|
||||
const dset* pydsets[] = {
|
||||
&pydevsupComSpec.com,
|
||||
&pydevsupComIn.com,
|
||||
&pydevsupComOut.com,
|
||||
};
|
||||
|
||||
static const char* pydsetnames[] = {
|
||||
"pydevsupComSpec",
|
||||
"pydevsupComIn",
|
||||
"pydevsupComOut",
|
||||
};
|
||||
|
||||
PyObject* pyDBD_setup(PyObject *unused)
|
||||
{
|
||||
registerDevices(pdbbase, NELEMENTS(pydsets), pydsetnames, pydsets);
|
||||
registryFunctionAdd("python_asub", (REGISTRYFUNCTION)&python_asub);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/* Called with GIL locked */
|
||||
void pyDBD_cleanup(void)
|
||||
PyObject* pyDBD_cleanup(PyObject *unused)
|
||||
{
|
||||
ELLNODE *cur;
|
||||
inshutdown = 1;
|
||||
@ -497,12 +519,5 @@ void pyDBD_cleanup(void)
|
||||
|
||||
free(priv);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#include <epicsExport.h>
|
||||
|
||||
epicsExportAddress(dset, pydevsupComSpec);
|
||||
epicsExportAddress(dset, pydevsupComIn);
|
||||
epicsExportAddress(dset, pydevsupComOut);
|
||||
|
||||
epicsRegisterFunction(python_asub);
|
||||
|
@ -192,10 +192,6 @@ static PyObject* pyField_getval(pyField *self)
|
||||
return PyErr_Format(PyExc_ValueError, "Error fetching array info for %s.%s",
|
||||
self->addr.precord->name,
|
||||
self->addr.pfldDes->name);
|
||||
else if(noe<1) {
|
||||
PyErr_SetString(PyExc_IndexError, "zero length array");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rawfield = self->addr.pfield;
|
||||
/* get_array_info can modify pfield in >3.15.0.1 */
|
||||
@ -388,8 +384,8 @@ static PyObject *pyField_setlen(pyField *self, PyObject *args)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(len<1 || len > self->addr.no_elements) {
|
||||
PyErr_Format(PyExc_ValueError, "Requested length %ld out of range [1,%lu)",
|
||||
if(len > self->addr.no_elements) {
|
||||
PyErr_Format(PyExc_ValueError, "Requested length %ld out of range [0,%lu)",
|
||||
(long)len, (unsigned long)self->addr.no_elements);
|
||||
return NULL;
|
||||
}
|
||||
@ -449,25 +445,28 @@ static PyObject *pyField_len(pyField *self)
|
||||
|
||||
static PyMethodDef pyField_methods[] = {
|
||||
{"name", (PyCFunction)pyField_name, METH_NOARGS,
|
||||
"Return Names (\"record\",\"field\")"},
|
||||
"name() -> (recname, fldname)\n"},
|
||||
{"fieldinfo", (PyCFunction)pyField_fldinfo, METH_NOARGS,
|
||||
"Field type info\nReturn (type, size, #elements"},
|
||||
"fieldinfo() -> (dbf, elem_size, elem_count"},
|
||||
{"getval", (PyCFunction)pyField_getval, METH_NOARGS,
|
||||
"Returns scalar version of field value"},
|
||||
"getval() -> object\n"},
|
||||
{"putval", (PyCFunction)pyField_putval, METH_VARARGS,
|
||||
"Sets field value from a scalar"},
|
||||
"putval(object)\n"},
|
||||
{"getarray", (PyCFunction)pyField_getarray, METH_NOARGS,
|
||||
"getarray() -> numpy.ndarray\n"
|
||||
"Return a numpy ndarray refering to this field for in-place operations."},
|
||||
{"getarraylen", (PyCFunction)pyField_getlen, METH_NOARGS,
|
||||
"getarraylen() -> int\n"
|
||||
"Return current number of valid elements for array fields."},
|
||||
{"putarraylen", (PyCFunction)pyField_setlen, METH_VARARGS,
|
||||
"putarraylen(int)\n"
|
||||
"Set number of valid elements for array fields."},
|
||||
{"getTime", (PyCFunction)pyField_getTime, METH_NOARGS,
|
||||
"Return link target timestamp as a tuple (sec, nsec)."},
|
||||
"getTime() -> (sec, nsec)."},
|
||||
{"getAlarm", (PyCFunction)pyField_getAlarm, METH_NOARGS,
|
||||
"Return link target alarm condtions as a tuple (severity, status)."},
|
||||
"getAlarm() -> (severity, status)."},
|
||||
{"__len__", (PyCFunction)pyField_len, METH_NOARGS,
|
||||
"Maximum number of elements storable in this field"},
|
||||
"Maximum number of elements storable in this field."},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
@ -569,12 +568,3 @@ int pyField_prepare(PyObject *module)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pyField_cleanup(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for(i=0; i<=DBF_MENU; i++) {
|
||||
Py_XDECREF(dbf2np[i]);
|
||||
dbf2np[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
@ -293,23 +293,32 @@ static PyObject *pyRecord_exit(pyRecord *self, PyObject *args)
|
||||
|
||||
static PyMethodDef pyRecord_methods[] = {
|
||||
{"name", (PyCFunction)pyRecord_name, METH_NOARGS,
|
||||
"Return record name string"},
|
||||
"name() -> str\n\n"
|
||||
"Record name. ::\n"
|
||||
"\n"
|
||||
" R = getRecord(\"my:record:name\")\n"
|
||||
" assert R.name()==\"my:record:name\"\n"},
|
||||
{"rtype", (PyCFunction)pyRecord_rtype, METH_NOARGS,
|
||||
"rtype() -> str\n"
|
||||
"Return record type name string"},
|
||||
{"isPyRecord", (PyCFunction)pyRecord_ispyrec, METH_NOARGS,
|
||||
"isPyRecord() -> bool\n"
|
||||
"Is this record using Python Device."},
|
||||
{"info", (PyCFunction)pyRecord_info, METH_VARARGS,
|
||||
"Lookup info name\ninfo(name, def=None)"},
|
||||
"info(key [,default]) -> str\n"
|
||||
"Lookup info by name\n"
|
||||
":rtype: str\n"
|
||||
":throws: KeyError\n"},
|
||||
{"infos", (PyCFunction)pyRecord_infos, METH_NOARGS,
|
||||
"infos() -> {'name':'value'}\n"
|
||||
"Return a dictionary of all infos for this record."},
|
||||
{"setSevr", (PyCFunction)pyRecord_setSevr, METH_VARARGS|METH_KEYWORDS,
|
||||
"setSevr(sevr=INVALID_ALARM, stat=COMM_ALARM)\n"
|
||||
"Set alarm new alarm severity/status. Record must be locked!"},
|
||||
{"setTime", (PyCFunction)pyRecord_setTime, METH_VARARGS,
|
||||
"Set record timestamp if TSE==-2. Record must be locked!"},
|
||||
{"scan", (PyCFunction)pyRecord_scan, METH_VARARGS|METH_KEYWORDS,
|
||||
"scan(sync=False)\nScan this record. If sync is False then"
|
||||
"a scan request is queued. If sync is True then the record"
|
||||
"is scannined immidately on the current thread."},
|
||||
"scan(sync=False, reason=None, force=0)\n"},
|
||||
{"asyncStart", (PyCFunction)pyRecord_asyncStart, METH_NOARGS,
|
||||
"Begin an asynchronous action. Record must be locked!"},
|
||||
{"asyncFinish", (PyCFunction)pyRecord_asyncFinish, METH_VARARGS|METH_KEYWORDS,
|
||||
|
@ -1,28 +1,86 @@
|
||||
try:
|
||||
import _dbapi
|
||||
HAVE_DBAPI = True
|
||||
except ImportError:
|
||||
import devsup._nullapi as _dbapi
|
||||
HAVE_DBAPI = False
|
||||
import os
|
||||
import atexit
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from _dbconstants import *
|
||||
except ImportError:
|
||||
EPICS_VERSION_STRING = "EPICS 0.0.0.0-0"
|
||||
EPICS_DEV_SNAPSHOT = ""
|
||||
EPICS_SITE_VERSION = "0"
|
||||
EPICS_VERSION = 0
|
||||
EPICS_REVISION = 0
|
||||
EPICS_MODIFICATION = 0
|
||||
EPICS_PATCH_LEVEL = 0
|
||||
from . import _dbapi
|
||||
|
||||
XEPICS_ARCH = "nullos-nullarch"
|
||||
XPYDEV_BASE = "invaliddir"
|
||||
XEPICS_BASE = "invaliddir"
|
||||
|
||||
epicsver = (0,0,0,0,"0","")
|
||||
pydevver = (0,0)
|
||||
|
||||
INVALID_ALARM = UDF_ALARM = 0
|
||||
from ._dbapi import (EPICS_VERSION_STRING,
|
||||
EPICS_DEV_SNAPSHOT,
|
||||
EPICS_SITE_VERSION,
|
||||
EPICS_VERSION,
|
||||
EPICS_REVISION,
|
||||
EPICS_MODIFICATION,
|
||||
EPICS_PATCH_LEVEL,
|
||||
XEPICS_ARCH,
|
||||
XPYDEV_BASE,
|
||||
XEPICS_BASE,
|
||||
epicsver,
|
||||
pydevver,
|
||||
NO_ALARM,
|
||||
MINOR_ALARM,
|
||||
MAJOR_ALARM,
|
||||
READ_ALARM,
|
||||
WRITE_ALARM,
|
||||
HIHI_ALARM,
|
||||
HIGH_ALARM,
|
||||
LOLO_ALARM,
|
||||
LOW_ALARM,
|
||||
STATE_ALARM,
|
||||
COS_ALARM,
|
||||
COMM_ALARM,
|
||||
TIMEOUT_ALARM,
|
||||
HW_LIMIT_ALARM,
|
||||
CALC_ALARM,
|
||||
SCAN_ALARM,
|
||||
LINK_ALARM,
|
||||
SOFT_ALARM,
|
||||
BAD_SUB_ALARM,
|
||||
UDF_ALARM,
|
||||
DISABLE_ALARM,
|
||||
SIMM_ALARM,
|
||||
READ_ACCESS_ALARM,
|
||||
WRITE_ACCESS_ALARM,
|
||||
INVALID_ALARM,
|
||||
)
|
||||
|
||||
__all__ = []
|
||||
|
||||
def _init(iocMain=False):
|
||||
if not iocMain:
|
||||
# we haven't read/register base.dbd
|
||||
_dbapi.dbReadDatabase(os.path.join(XEPICS_BASE, "dbd", "base.dbd"),
|
||||
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)
|
||||
_dbapi._dbd_setup()
|
||||
|
||||
def _fini(iocMain=False):
|
||||
if iocMain:
|
||||
_dbapi.initHookAnnounce(9999) # our magic/fake AtExit hook
|
||||
_dbapi._dbd_cleanup()
|
||||
|
@ -1,205 +0,0 @@
|
||||
|
||||
class _Record(object):
|
||||
"""Handle for record operations
|
||||
|
||||
r = _Record("rec:name")
|
||||
"""
|
||||
|
||||
def __init__(self, rec):
|
||||
pass
|
||||
def name(self):
|
||||
"""Record name string.
|
||||
|
||||
>>> R = getRecord("my:record:name")
|
||||
>>> R.name()
|
||||
"my:record:name"
|
||||
"""
|
||||
def rtype(self):
|
||||
"""Record type name string.
|
||||
|
||||
>>> R = getRecord("my:record:name")
|
||||
>>> R.type()
|
||||
"longin"
|
||||
"""
|
||||
def isPyRecord(self):
|
||||
"""Is this record using Python device support.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
def info(self, key):
|
||||
"""info(key [,default])
|
||||
|
||||
:rtype: str
|
||||
:throws: KeyError
|
||||
|
||||
Lookup record info tag. If no default
|
||||
is provided then an exception is raised
|
||||
if the info key does not exist.
|
||||
"""
|
||||
def infos(self):
|
||||
"""Return a dictionary of all info tags
|
||||
for this record
|
||||
"""
|
||||
|
||||
def setSevr(self, sevr=3, stat=15):
|
||||
"""setSevr(sevr=INVALID_ALARM, stat=COMM_ALARM)
|
||||
|
||||
Signal a new alarm condition. The effect of this
|
||||
call depends on the current alarm condition.
|
||||
|
||||
See :c:func:`recGblSetSevr` in EPICS Base.
|
||||
"""
|
||||
|
||||
def scan(self, sync=False, reason=None, force=0):
|
||||
"""Scan this record.
|
||||
|
||||
:param sync: scan in current thread (``True``), or queue to a worker (``False``).
|
||||
:param reason: Reason object passed to :meth:`process <DeviceSupport.process>` (sync=True only)
|
||||
:param force: Record processing condtion (0=Passive, 1=Force, 2=I/O Intr)
|
||||
:throws: ``RuntimeError`` when ``sync=True``, but ``force`` prevents scanning.
|
||||
|
||||
If ``sync`` is False then a scan request is queued to run in another thread..
|
||||
If ``sync`` is True then the record is scanned immediately on the current thread.
|
||||
|
||||
For ``reason`` argument must be used in conjunction with ``sync=True``
|
||||
on records with Python device support. This provides a means
|
||||
of providing extra contextual information to the record's
|
||||
:meth:`process <DeviceSupport.process>` method.
|
||||
|
||||
``force`` is used to decide if the record will actually be processed,
|
||||
``force=0`` will only process records with SCAN=Passive.
|
||||
``force=1`` will process any record if at all possible.
|
||||
``force=2`` will only process records with Python device support and
|
||||
SCAN=I/O Intr.
|
||||
|
||||
.. important::
|
||||
It is **never** safe to use ``sync=True`` while holding record locks,
|
||||
including from within a *process* method.
|
||||
"""
|
||||
|
||||
def asyncStart(self):
|
||||
"""Start asynchronous processing
|
||||
|
||||
This method may be called from a device support
|
||||
:meth:`process <DeviceSupport.process>` method
|
||||
to indicate that processing will continue
|
||||
later.
|
||||
|
||||
.. important::
|
||||
This method is **only** safe to call within a *process* method.
|
||||
"""
|
||||
def asyncFinish(self, reason=None):
|
||||
"""Indicate that asynchronous processing can complete
|
||||
|
||||
Similar to :meth:`scan`. Used to conclude asynchronous
|
||||
process started with :meth:`asyncStart`.
|
||||
|
||||
Processing is completed on the current thread.
|
||||
|
||||
.. important::
|
||||
This method should **never** be called within
|
||||
a :meth:`process <DeviceSupport.process>` method,
|
||||
or any other context where a Record lock is held.
|
||||
Doing so will result in a deadlock.
|
||||
|
||||
Typically a *reason* will be passed to *process* as a way
|
||||
of indicating that this is the completion of an async action. ::
|
||||
|
||||
AsyncDone = object()
|
||||
class MySup(object):
|
||||
def process(record, reason):
|
||||
if reason is AsyncDone:
|
||||
record.VAL = ... # store result
|
||||
else:
|
||||
threading.Timer(1.0, record.asyncFinish, kwargs={'reason':AsyncDone})
|
||||
record.asyncStart()
|
||||
"""
|
||||
|
||||
class _Field(object):
|
||||
"""Handle for field operations
|
||||
|
||||
f = Field("rec:name.HOPR")
|
||||
|
||||
Field objects implement the buffer protocol.
|
||||
"""
|
||||
def __init__(self, fld):
|
||||
pass
|
||||
def name(self):
|
||||
"""Fetch the record and field names.
|
||||
|
||||
>>> FLD = getRecord("rec").field("FLD")
|
||||
>>> FLD.name()
|
||||
("rec", "FLD")
|
||||
"""
|
||||
def fieldinfo(self):
|
||||
"""(type, size, #elements) = fieldinfo()
|
||||
|
||||
Type is DBF type code
|
||||
size is number of bytes to start a single element
|
||||
#elements is the maximum number of elements the field can hold
|
||||
"""
|
||||
|
||||
def getval(self):
|
||||
"""Fetch the current field value as a scalar or numpy.ndarray.
|
||||
|
||||
:rtype: int, float, str, or ndarray
|
||||
|
||||
Returned type depends of field DBF type.
|
||||
An ``int`` is returned for CHAR, SHORT, LONG, and ENUM.
|
||||
A ``float`` is returned for FLOAT and DOUBLE.
|
||||
A ``str`` is returned for STRING.
|
||||
A ``numpy.ndarray`` is returned for array fields.
|
||||
This array is read-only and has the size of the present valid values.
|
||||
|
||||
.. important::
|
||||
It is only safe to read this ndarray while the record
|
||||
lock is held (ie within :meth:`process <DeviceSupport.process>`).
|
||||
"""
|
||||
|
||||
def putval(self, val):
|
||||
"""Update the field value
|
||||
|
||||
Must be an Int, Float, str, or numpy.ndarray.
|
||||
Strings will be truncated to 39 characters.
|
||||
Arrays must have a size less than or equal to the max element count.
|
||||
Arrays are converted as necessary to the field's native type.
|
||||
"""
|
||||
|
||||
def getarray(self):
|
||||
"""Return a numpy ndarray refering to this field for in-place operations.
|
||||
|
||||
The dtype of the ndarray will correspond to the field's DBF type.
|
||||
Its size will be the **maximum** number of elements.
|
||||
|
||||
.. important::
|
||||
It is only safe to read or write to this ndarray while the record
|
||||
lock is held (ie within :meth:`process <DeviceSupport.process>`).
|
||||
"""
|
||||
|
||||
def getarraylen(self):
|
||||
"""Return the number of active elements for the field.
|
||||
|
||||
>>> F = Field(...)
|
||||
>>> assert len(F)>=F.getarraylen()
|
||||
"""
|
||||
|
||||
def putarraylen(self, len):
|
||||
"""Set the number of active elements in field's array.
|
||||
|
||||
Requires that the underlying field be an array.
|
||||
Must be greater than one and less than or equal to the maximum length of the field.
|
||||
"""
|
||||
|
||||
def getAlarm(self):
|
||||
"""Returns a tuple (severity, status) with the condition of the linked field.
|
||||
|
||||
Only works for fields of type DBF_INLINK.
|
||||
"""
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the maximum number of elements which may be stored in the field.
|
||||
|
||||
This is always 1 for scalar fields.
|
||||
"""
|
||||
|
||||
_hooks = {}
|
@ -3,10 +3,7 @@ import threading, sys, traceback, time
|
||||
|
||||
from devsup.util import Worker, importmod
|
||||
|
||||
try:
|
||||
import _dbapi
|
||||
except ImportError:
|
||||
import _nullapi as _dbapi
|
||||
from . import _dbapi
|
||||
|
||||
_rec_cache = {}
|
||||
_no_such_field = object()
|
||||
@ -263,6 +260,76 @@ class Record(_dbapi._Record):
|
||||
|
||||
super(Record, self).setTime(sec, nsec)
|
||||
|
||||
def scan(self, *args, **kws):
|
||||
"""scan(sync=False, reason=None, force=0)
|
||||
Scan this record.
|
||||
|
||||
:param sync: scan in current thread (``True``), or queue to a worker (``False``).
|
||||
:param reason: Reason object passed to :meth:`process <DeviceSupport.process>` (sync=True only)
|
||||
:param force: Record processing condtion (0=Passive, 1=Force, 2=I/O Intr)
|
||||
:throws: ``RuntimeError`` when ``sync=True``, but ``force`` prevents scanning.
|
||||
|
||||
If ``sync`` is False then a scan request is queued to run in another thread..
|
||||
If ``sync`` is True then the record is scanned immediately on the current thread.
|
||||
|
||||
For ``reason`` argument must be used in conjunction with ``sync=True``
|
||||
on records with Python device support. This provides a means
|
||||
of providing extra contextual information to the record's
|
||||
:meth:`process <DeviceSupport.process>` method.
|
||||
|
||||
``force`` is used to decide if the record will actually be processed,
|
||||
``force=0`` will only process records with SCAN=Passive.
|
||||
``force=1`` will process any record if at all possible.
|
||||
``force=2`` will only process records with Python device support and
|
||||
SCAN=I/O Intr.
|
||||
|
||||
.. important::
|
||||
It is **never** safe to use ``sync=True`` while holding record locks,
|
||||
including from within a *process* method.
|
||||
"""
|
||||
return _dbapi._Record.scan(self, *args, **kws)
|
||||
|
||||
def asyncStart(self):
|
||||
"""Start asynchronous processing
|
||||
|
||||
This method may be called from a device support
|
||||
:meth:`process <DeviceSupport.process>` method
|
||||
to indicate that processing will continue
|
||||
later.
|
||||
|
||||
.. important::
|
||||
This method is **only** safe to call within a *process* method.
|
||||
"""
|
||||
return _dbapi._Record.asyncStart(self)
|
||||
|
||||
def asyncFinish(self, reason=None):
|
||||
"""Indicate that asynchronous processing can complete
|
||||
|
||||
Similar to :meth:`scan`. Used to conclude asynchronous
|
||||
process started with :meth:`asyncStart`.
|
||||
|
||||
Processing is completed on the current thread.
|
||||
|
||||
.. important::
|
||||
This method should **never** be called within
|
||||
a :meth:`process <DeviceSupport.process>` method,
|
||||
or any other context where a Record lock is held.
|
||||
Doing so will result in a deadlock.
|
||||
|
||||
Typically a *reason* will be passed to *process* as a way
|
||||
of indicating that this is the completion of an async action. ::
|
||||
|
||||
AsyncDone = object()
|
||||
class MySup(object):
|
||||
def process(record, reason):
|
||||
if reason is AsyncDone:
|
||||
record.VAL = ... # store result
|
||||
else:
|
||||
threading.Timer(1.0, record.asyncFinish, kwargs={'reason':AsyncDone})
|
||||
record.asyncStart()
|
||||
"""
|
||||
return _dbapi._Record.asyncFinish(self, reason=reason)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
F = self.field(name)
|
||||
@ -341,5 +408,8 @@ def processLink(name, lstr):
|
||||
modname, args = parts[0], parts[1] if len(parts)>1 else None
|
||||
else:
|
||||
args = lstr
|
||||
modname, _sep, attr = modname.partition('|')
|
||||
mod = importmod(modname)
|
||||
if attr:
|
||||
mod = getattr(mod, attr)
|
||||
return rec, mod.build(rec, args)
|
||||
|
@ -4,10 +4,7 @@ import traceback
|
||||
from functools import wraps
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import _dbapi
|
||||
except ImportError:
|
||||
import devsup._nullapi as _dbapi
|
||||
from . import _dbapi
|
||||
|
||||
__all__ = [
|
||||
"hooknames",
|
||||
@ -45,7 +42,7 @@ def initHook(state):
|
||||
|
||||
@initHook("AfterIocRunning")
|
||||
def myfn():
|
||||
# do stuff
|
||||
pass
|
||||
"""
|
||||
def _add(fn):
|
||||
addHook(state, fn)
|
||||
|
@ -7,8 +7,8 @@ import threading, inspect
|
||||
|
||||
_tables = {}
|
||||
|
||||
from devsup.db import IOScanListThread
|
||||
from devsup import INVALID_ALARM, UDF_ALARM
|
||||
from .db import IOScanListThread
|
||||
from . import INVALID_ALARM, UDF_ALARM
|
||||
|
||||
__all__ = [
|
||||
'Parameter',
|
||||
|
0
devsupApp/src/devsup/test/__init__.py
Normal file
0
devsupApp/src/devsup/test/__init__.py
Normal file
165
devsupApp/src/devsup/test/test_db.py
Normal file
165
devsupApp/src/devsup/test/test_db.py
Normal file
@ -0,0 +1,165 @@
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
import numpy
|
||||
from numpy.testing import assert_array_almost_equal, assert_array_equal
|
||||
|
||||
from ..db import getRecord
|
||||
from .. import _dbapi
|
||||
from .. import _init
|
||||
|
||||
from .util import IOCHelper
|
||||
|
||||
# short-circuit warning from base_registerRecordDeviceDriver()
|
||||
os.environ['TOP'] = _dbapi.XPYDEV_BASE # external code use devsup.XPYDEV_BASE
|
||||
|
||||
class TestScan(IOCHelper):
|
||||
db = """
|
||||
record(longout, src) {
|
||||
field(OUT, "tgt PP")
|
||||
}
|
||||
record(longin, "tgt") {}
|
||||
"""
|
||||
autostart = True
|
||||
|
||||
def test_link(self):
|
||||
src, tgt = getRecord('src'), getRecord('tgt')
|
||||
|
||||
with src:
|
||||
src.VAL = 42
|
||||
self.assertEqual(src.VAL, 42)
|
||||
|
||||
with tgt:
|
||||
self.assertEqual(tgt.VAL, 0)
|
||||
|
||||
src.scan(sync=True) # lock and dbProcess() on this thread
|
||||
|
||||
with tgt:
|
||||
self.assertEqual(tgt.VAL, 42)
|
||||
|
||||
class TestField(IOCHelper):
|
||||
db = """
|
||||
record(ai, "rec:ai") {
|
||||
field(VAL , "4.2")
|
||||
field(RVAL, "42")
|
||||
}
|
||||
record(stringin, "rec:si") {
|
||||
field(VAL, "")
|
||||
}
|
||||
record(waveform, "rec:wf:a") {
|
||||
field(FTVL, "DOUBLE")
|
||||
field(NELM, "10")
|
||||
}
|
||||
record(waveform, "rec:wf:s") {
|
||||
field(FTVL, "STRING")
|
||||
field(NELM, "10")
|
||||
}
|
||||
"""
|
||||
|
||||
def test_ai(self):
|
||||
rec = getRecord("rec:ai")
|
||||
|
||||
with rec:
|
||||
self.assertEqual(rec.VAL, 4.2)
|
||||
self.assertEqual(rec.RVAL, 42)
|
||||
rec.VAL = 5.2
|
||||
rec.RVAL = 52
|
||||
self.assertEqual(rec.VAL, 5.2)
|
||||
self.assertEqual(rec.RVAL, 52)
|
||||
|
||||
rec.VAL += 1.0
|
||||
self.assertEqual(rec.VAL, 6.2)
|
||||
|
||||
def test_si(self):
|
||||
rec = getRecord("rec:si")
|
||||
|
||||
with rec:
|
||||
self.assertEqual(rec.VAL, "")
|
||||
|
||||
rec.VAL = "test"
|
||||
self.assertEqual(rec.VAL, "test")
|
||||
|
||||
rec.VAL = ""
|
||||
self.assertEqual(rec.VAL, "")
|
||||
|
||||
# implicitly truncates
|
||||
rec.VAL = "This is a really long string which should be truncated"
|
||||
self.assertEqual(rec.VAL, "This is a really long string which shou")
|
||||
|
||||
# TODO: test unicode
|
||||
|
||||
def test_wf_float(self):
|
||||
rec = getRecord("rec:wf:a")
|
||||
|
||||
with rec:
|
||||
assert_array_almost_equal(rec.VAL, [])
|
||||
|
||||
rec.VAL = numpy.arange(5)
|
||||
assert_array_almost_equal(rec.VAL, numpy.arange(5))
|
||||
|
||||
rec.VAL = numpy.arange(10)
|
||||
assert_array_almost_equal(rec.VAL, numpy.arange(10))
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
rec.VAL = numpy.arange(15)
|
||||
|
||||
rec.VAL = []
|
||||
assert_array_almost_equal(rec.VAL, [])
|
||||
|
||||
# in-place modification
|
||||
fld = rec.field('VAL')
|
||||
fld.putarraylen(5)
|
||||
arr = fld.getarray()
|
||||
self.assertEqual(arr.shape, (10,)) # size of NELM
|
||||
arr[:5] = numpy.arange(5) # we only fill in the part in use
|
||||
arr[2] = 42
|
||||
|
||||
assert_array_almost_equal(rec.VAL, [0, 1, 42, 3, 4])
|
||||
|
||||
def test_wf_string(self):
|
||||
rec = getRecord("rec:wf:s")
|
||||
|
||||
with rec:
|
||||
assert_array_equal(rec.VAL, numpy.asarray([], dtype='S40'))
|
||||
|
||||
rec.VAL = ["zero", "", "one", "This is a really long string which should be truncated", "", "last"]
|
||||
|
||||
assert_array_equal(rec.VAL,
|
||||
numpy.asarray(["zero", "", "one", "This is a really long string which shoul", "", "last"], dtype='S40'))
|
||||
|
||||
|
||||
class TestDset(IOCHelper):
|
||||
db = """
|
||||
record(longin, "rec:li") {
|
||||
field(DTYP, "Python Device")
|
||||
field(INP , "@devsup.test.test_db|TestDset foo bar")
|
||||
}
|
||||
"""
|
||||
|
||||
class Increment(object):
|
||||
def process(self, rec, reason):
|
||||
rec.VAL += 1
|
||||
def detach(self, rec):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def build(klass, rec, args):
|
||||
if rec.name()=='rec:li':
|
||||
return klass.Increment()
|
||||
else:
|
||||
raise RuntimeError("Unsupported")
|
||||
|
||||
def test_increment(self):
|
||||
rec = getRecord('rec:li')
|
||||
|
||||
with rec:
|
||||
self.assertEqual(rec.VAL, 0)
|
||||
self.assertEqual(rec.UDF, 1)
|
||||
|
||||
rec.scan(sync=True)
|
||||
|
||||
with rec:
|
||||
self.assertEqual(rec.VAL, 1)
|
||||
self.assertEqual(rec.UDF, 0)
|
74
devsupApp/src/devsup/test/util.py
Normal file
74
devsupApp/src/devsup/test/util.py
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
import numpy
|
||||
from numpy.testing import assert_array_almost_equal, assert_array_equal
|
||||
|
||||
from ..db import getRecord
|
||||
from .. import _dbapi
|
||||
from .. import _init
|
||||
|
||||
__all__ = (
|
||||
'IOCHelper',
|
||||
)
|
||||
|
||||
class IOCHelper(unittest.TestCase):
|
||||
"""Test case run in an IOC. ::
|
||||
|
||||
from devsup.db import getRecord
|
||||
from devsup.test.util impmort IOCHelper
|
||||
class TestScan(IOCHelper): # sub-class of unittest.TestCase
|
||||
db = \"\"\"
|
||||
record(longout, foo) {}
|
||||
\"\"\"
|
||||
autostart = True
|
||||
|
||||
def test_link(self):
|
||||
rec = getRecord('foo')
|
||||
with rec: # dbScanLock()
|
||||
self.assertEqual(rec.VAL, 0)
|
||||
"""
|
||||
# DB definition to be used. May include eg. 'record(ai, "blah") {}'
|
||||
db = None
|
||||
# Whether to automatically run iocInit() before test methods
|
||||
# whether iocInit() has been called
|
||||
autostart = True
|
||||
running = False
|
||||
|
||||
def setUp(self):
|
||||
print("testdbPrepare()")
|
||||
_dbapi._UTest.testdbPrepare()
|
||||
_init(iocMain=False) # load base.dbd
|
||||
|
||||
if self.db is not None:
|
||||
with tempfile.NamedTemporaryFile() as F:
|
||||
F.write(self.db.encode('ascii'))
|
||||
F.flush()
|
||||
_dbapi.dbReadDatabase(F.name)
|
||||
|
||||
if self.autostart:
|
||||
self.iocInit()
|
||||
|
||||
def tearDown(self):
|
||||
self.iocShutdown();
|
||||
print("testdbCleanup()")
|
||||
_dbapi.initHookAnnounce(9999) # our magic/fake AtExit hook
|
||||
_dbapi._UTest.testdbCleanup()
|
||||
|
||||
def iocInit(self):
|
||||
"""If not autostart, then this must be called before runtime database access is possible
|
||||
"""
|
||||
if not self.running:
|
||||
print("testIocInitOk")
|
||||
_dbapi._UTest.testIocInitOk()
|
||||
self.running = True
|
||||
|
||||
def iocShutdown(self):
|
||||
"""Call to stop IOC scanning processes. Happens automatically during test tearDown
|
||||
"""
|
||||
if self.running:
|
||||
print("testIocShutdownOk")
|
||||
_dbapi._UTest.testIocShutdownOk()
|
||||
self.running = False
|
@ -1,26 +0,0 @@
|
||||
registrar(pySetupReg)
|
||||
|
||||
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")
|
||||
|
||||
function(python_asub)
|
||||
|
@ -15,17 +15,19 @@
|
||||
|
||||
#endif
|
||||
|
||||
PyMODINIT_FUNC init_dbbase(void);
|
||||
struct dbCommon;
|
||||
|
||||
void pyDBD_cleanup(void);
|
||||
PyObject* pyDBD_setup(PyObject *unused);
|
||||
PyObject* pyDBD_cleanup(PyObject *unused);
|
||||
|
||||
int pyUTest_prepare(PyObject *module);
|
||||
|
||||
int pyField_prepare(PyObject *module);
|
||||
void pyField_cleanup(void);
|
||||
|
||||
int pyRecord_prepare(PyObject *module);
|
||||
|
||||
int isPyRecord(dbCommon *);
|
||||
int canIOScanRecord(dbCommon *);
|
||||
int isPyRecord(struct dbCommon *);
|
||||
int canIOScanRecord(struct dbCommon *);
|
||||
|
||||
extern epicsThreadPrivateId pyDevReasonID;
|
||||
|
||||
|
140
devsupApp/src/utest.c
Normal file
140
devsupApp/src/utest.c
Normal file
@ -0,0 +1,140 @@
|
||||
|
||||
/* python has its own ideas about which version to support */
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <epicsVersion.h>
|
||||
#include <dbEvent.h>
|
||||
#include <iocInit.h>
|
||||
#include <errlog.h>
|
||||
|
||||
#if EPICS_VERSION>3 || (EPICS_VERSION==3 && EPICS_REVISION>=15)
|
||||
# include <dbUnitTest.h>
|
||||
# define HAVE_DBTEST
|
||||
#endif
|
||||
|
||||
#include "pydevsup.h"
|
||||
|
||||
#ifdef HAVE_DBTEST
|
||||
static dbEventCtx testEvtCtx;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
} UTest;
|
||||
|
||||
static PyTypeObject UTest_type = {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
#else
|
||||
PyObject_HEAD_INIT(NULL)
|
||||
0,
|
||||
#endif
|
||||
"_dbapi._UTest",
|
||||
sizeof(UTest),
|
||||
};
|
||||
|
||||
static PyObject* utest_prepare(PyObject *unused)
|
||||
{
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
//testdbPrepare(); doesn't do anything essential for us as of 7.0.2
|
||||
} Py_END_ALLOW_THREADS
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject* utest_init(PyObject *unused)
|
||||
{
|
||||
#ifdef HAVE_DBTEST
|
||||
int ret;
|
||||
if(testEvtCtx)
|
||||
return PyErr_Format(PyExc_RuntimeError, "Missing testIocShutdownOk()");
|
||||
|
||||
// like, testIocInitOk() without testAbort()
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
eltc(0);
|
||||
ret = iocBuildIsolated() || iocRun();
|
||||
eltc(1);
|
||||
} Py_END_ALLOW_THREADS
|
||||
if(ret) {
|
||||
return PyErr_Format(PyExc_RuntimeError, "iocInit fails with %d", ret);
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
testEvtCtx=db_init_events();
|
||||
} Py_END_ALLOW_THREADS
|
||||
if(!testEvtCtx) {
|
||||
iocShutdown();
|
||||
return PyErr_Format(PyExc_RuntimeError, "iocInit fails create dbEvent context");
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
ret = db_start_events(testEvtCtx, "CAS-test-py", NULL, NULL, epicsThreadPriorityCAServerLow);
|
||||
} Py_END_ALLOW_THREADS
|
||||
if(ret!=DB_EVENT_OK) {
|
||||
db_close_events(testEvtCtx);
|
||||
testEvtCtx = NULL;
|
||||
iocShutdown();
|
||||
return PyErr_Format(PyExc_RuntimeError, "db_start_events fails with %d", ret);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
return PyErr_Format(PyExc_RuntimeError, "Requires Base >=3.15");
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyObject* utest_shutdown(PyObject *unused)
|
||||
{
|
||||
#ifdef HAVE_DBTEST
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
//testIocShutdownOk();
|
||||
db_close_events(testEvtCtx);
|
||||
testEvtCtx = NULL;
|
||||
iocShutdown();
|
||||
} Py_END_ALLOW_THREADS
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
return PyErr_Format(PyExc_RuntimeError, "Requires Base >=3.15");
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyObject* utest_cleanup(PyObject *unused)
|
||||
{
|
||||
#ifdef HAVE_DBTEST
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
testdbCleanup();
|
||||
errlogFlush();
|
||||
} Py_END_ALLOW_THREADS
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
return PyErr_Format(PyExc_RuntimeError, "Requires Base >=3.15");
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyMethodDef UTest_methods[] = {
|
||||
{"testdbPrepare", (PyCFunction)&utest_prepare, METH_STATIC|METH_NOARGS, ""},
|
||||
{"testIocInitOk", (PyCFunction)&utest_init, METH_STATIC|METH_NOARGS, ""},
|
||||
{"testIocShutdownOk", (PyCFunction)&utest_shutdown, METH_STATIC|METH_NOARGS, ""},
|
||||
{"testdbCleanup", (PyCFunction)&utest_cleanup, METH_STATIC|METH_NOARGS, ""},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
int pyUTest_prepare(PyObject *module)
|
||||
{
|
||||
PyObject *typeobj=(PyObject*)&UTest_type;
|
||||
|
||||
UTest_type.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE;
|
||||
UTest_type.tp_methods = UTest_methods;
|
||||
|
||||
if(PyType_Ready(&UTest_type)<0)
|
||||
return -1;
|
||||
|
||||
Py_INCREF(typeobj);
|
||||
if(PyModule_AddObject(module, "_UTest", typeobj)) {
|
||||
Py_DECREF(typeobj);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -108,7 +108,7 @@ In somefile.c ::
|
||||
PyMODINIT_FUNC init_myextname(void)
|
||||
|
||||
Installing for several Python versions
|
||||
------------------------------------
|
||||
--------------------------------------
|
||||
|
||||
The recipe for building and installing the pyDevSup module
|
||||
for several python version side by side is ::
|
||||
|
@ -28,23 +28,39 @@ libdirs = [get_config_var('LIBDIR')]
|
||||
have_np='NO'
|
||||
try:
|
||||
from numpy.distutils.misc_util import get_numpy_include_dirs
|
||||
incdirs += get_numpy_include_dirs()
|
||||
incdirs = get_numpy_include_dirs()+incdirs
|
||||
have_np='YES'
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
incdirs = [get_python_inc()]+get_numpy_include_dirs()
|
||||
libdirs = [get_config_var('LIBDIR')]
|
||||
|
||||
print('TARGET_CFLAGS +=',get_config_var('BASECFLAGS'), file=out)
|
||||
print('TARGET_CXXFLAGS +=',get_config_var('BASECFLAGS'), file=out)
|
||||
|
||||
print('PY_VER :=',get_config_var('VERSION'), file=out)
|
||||
print('PY_LD_VER :=',get_config_var('LDVERSION') or get_config_var('VERSION'), file=out)
|
||||
ldver = get_config_var('LDVERSION')
|
||||
if ldver is None:
|
||||
ldver = get_config_var('VERSION')
|
||||
if get_config_var('Py_DEBUG'):
|
||||
ldver = ldver+'_d'
|
||||
print('PY_LD_VER :=',ldver, file=out)
|
||||
print('PY_INCDIRS :=',' '.join(incdirs), file=out)
|
||||
print('PY_LIBDIRS :=',' '.join(libdirs), file=out)
|
||||
print('HAVE_NUMPY :=',have_np, file=out)
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
print('HAVE_ASYNCIO := NO', file=out)
|
||||
else:
|
||||
print('HAVE_ASYNCIO := YES', file=out)
|
||||
|
||||
try:
|
||||
import cothread
|
||||
except ImportError:
|
||||
print('HAVE_COTHREAD := NO', file=out)
|
||||
else:
|
||||
print('HAVE_COTHREAD := YES', file=out)
|
||||
|
||||
print('PY_OK := YES', file=out)
|
||||
|
||||
out.close()
|
||||
|
77
pyIocApp/Makefile
Normal file
77
pyIocApp/Makefile
Normal file
@ -0,0 +1,77 @@
|
||||
TOP=..
|
||||
|
||||
include $(TOP)/configure/CONFIG
|
||||
PYMODULE = NO
|
||||
include $(TOP)/configure/CONFIG_PY
|
||||
#----------------------------------------
|
||||
# ADD MACRO DEFINITIONS AFTER THIS LINE
|
||||
#=============================
|
||||
|
||||
#=============================
|
||||
# Build the IOC application
|
||||
|
||||
USR_CPPFLAGS += -I$(TOP)/devsupApp/src
|
||||
|
||||
LIBRARY = pyDevSup$(PY_LD_VER)
|
||||
|
||||
SHRLIB_VERSION = 0
|
||||
|
||||
DBD += pyDevSup.dbd
|
||||
|
||||
pyDevSup$(PY_LD_VER)_SYS_LIBS += python$(PY_LD_VER)
|
||||
|
||||
pyDevSup$(PY_LD_VER)_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
setup_CPPFLAGS += -DXEPICS_ARCH=\"$(T_A)\"
|
||||
setup_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||
setup_CPPFLAGS += -DXEPICS_BASE=\"$(EPICS_BASE)\"
|
||||
setup_CPPFLAGS += -DPYDIR=\"python$(PY_LD_VER)\"
|
||||
|
||||
pyDevSup$(PY_LD_VER)_SRCS += setup.c
|
||||
|
||||
PROD_IOC = softIocPy$(PY_VER)
|
||||
PRODNAME = $(addsuffix $(EXE),$(PROD))
|
||||
|
||||
# softIocPy.dbd will be created and installed
|
||||
DBD += softIocPy.dbd
|
||||
|
||||
# softIocPy.dbd will be made up from these files:
|
||||
softIocPy_DBD += base.dbd
|
||||
softIocPy_DBD += pyDevSup.dbd
|
||||
|
||||
softIocPy_DBD += system.dbd
|
||||
|
||||
softIocPy$(PY_VER)_LIBS += pyDevSup$(PY_LD_VER)
|
||||
|
||||
# softIocPy_registerRecordDeviceDriver.cpp derives from softIocPy.dbd
|
||||
softIocPy$(PY_VER)_SRCS += softIocPy_registerRecordDeviceDriver.cpp
|
||||
|
||||
# Build the main IOC entry point on workstation OSs.
|
||||
softIocPy$(PY_VER)_SRCS_DEFAULT += devsupMain.cpp
|
||||
|
||||
devsupMain_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||
|
||||
ifneq ($(DEVIOCSTATS),)
|
||||
softIocPy_DBD += devIocStats.dbd
|
||||
softIocPy$(PY_VER)_LIBS += devIocStats
|
||||
endif
|
||||
|
||||
ifneq ($(AUTOSAVE),)
|
||||
softIocPy_DBD += asSupport.dbd
|
||||
softIocPy$(PY_VER)_LIBS += autosave
|
||||
endif
|
||||
|
||||
ifneq ($(CAPUTLOG),)
|
||||
softIocPy_DBD += caPutLog.dbd
|
||||
softIocPy$(PY_VER)_LIBS += caPutLog
|
||||
endif
|
||||
|
||||
# Finally link to the EPICS Base libraries
|
||||
softIocPy$(PY_VER)_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
#===========================
|
||||
|
||||
include $(TOP)/configure/RULES
|
||||
include $(TOP)/configure/RULES_PY
|
||||
#----------------------------------------
|
||||
# ADD RULES AFTER THIS LINE
|
1
pyIocApp/pyDevSup.dbd
Normal file
1
pyIocApp/pyDevSup.dbd
Normal file
@ -0,0 +1 @@
|
||||
registrar(pySetupReg)
|
135
pyIocApp/setup.c
Normal file
135
pyIocApp/setup.c
Normal file
@ -0,0 +1,135 @@
|
||||
/* Global interpreter setup
|
||||
*/
|
||||
|
||||
/* python has its own ideas about which version to support */
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
|
||||
#include <Python.h>
|
||||
#ifdef HAVE_NUMPY
|
||||
#include <numpy/ndarrayobject.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <epicsVersion.h>
|
||||
#include <dbCommon.h>
|
||||
#include <dbAccess.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <dbScan.h>
|
||||
#include <initHooks.h>
|
||||
#include <epicsThread.h>
|
||||
#include <epicsExit.h>
|
||||
#include <alarm.h>
|
||||
|
||||
#include "pydevsup.h"
|
||||
|
||||
static void cleanupPy(void *junk)
|
||||
{
|
||||
PyThreadState *state = PyGILState_GetThisThreadState();
|
||||
|
||||
PyEval_RestoreThread(state);
|
||||
|
||||
if(PyRun_SimpleString("import devsup\n"
|
||||
"devsup._fini(iocMain=True)\n"
|
||||
)) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
Py_Finalize(); // calls python atexit hooks
|
||||
}
|
||||
|
||||
static void extendPath(PyObject *list,
|
||||
const char *base,
|
||||
const char *archdir)
|
||||
{
|
||||
PyObject *mod, *ret;
|
||||
|
||||
mod = PyImport_ImportModule("os.path");
|
||||
if(!mod)
|
||||
return;
|
||||
|
||||
ret = PyObject_CallMethod(mod, "join", "sss", base, PYDIR, archdir);
|
||||
if(ret && !PySequence_Contains(list, ret)) {
|
||||
PyList_Insert(list, 0, ret);
|
||||
}
|
||||
Py_XDECREF(ret);
|
||||
Py_DECREF(mod);
|
||||
if(PyErr_Occurred()) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static void insertDefaultPath(PyObject *list)
|
||||
{
|
||||
const char *basedir, *pydevdir, *top, *arch;
|
||||
|
||||
basedir = getenv("EPICS_BASE");
|
||||
if(!basedir)
|
||||
basedir = XEPICS_BASE;
|
||||
pydevdir = getenv("PYDEV_BASE");
|
||||
if(!pydevdir)
|
||||
pydevdir = XPYDEV_BASE;
|
||||
top = getenv("TOP");
|
||||
arch = getenv("ARCH");
|
||||
if(!arch)
|
||||
arch = XEPICS_ARCH;
|
||||
|
||||
assert(PyList_Check(list));
|
||||
assert(PySequence_Check(list));
|
||||
extendPath(list, basedir, arch);
|
||||
extendPath(list, pydevdir, arch);
|
||||
if(top)
|
||||
extendPath(list, top, arch);
|
||||
}
|
||||
|
||||
static void setupPyPath(void)
|
||||
{
|
||||
PyObject *mod, *path = NULL;
|
||||
|
||||
mod = PyImport_ImportModule("sys");
|
||||
if(mod)
|
||||
path = PyObject_GetAttrString(mod, "path");
|
||||
Py_XDECREF(mod);
|
||||
|
||||
if(path) {
|
||||
PyObject *cur;
|
||||
char cwd[PATH_MAX];
|
||||
|
||||
insertDefaultPath(path);
|
||||
|
||||
/* prepend current directory */
|
||||
if(getcwd(cwd, sizeof(cwd)-1)) {
|
||||
cwd[sizeof(cwd)-1] = '\0';
|
||||
cur = PyString_FromString(cwd);
|
||||
if(cur)
|
||||
PyList_Insert(path, 0, cur);
|
||||
Py_XDECREF(cur);
|
||||
}
|
||||
}
|
||||
Py_XDECREF(path);
|
||||
}
|
||||
|
||||
static void pySetupReg(void)
|
||||
{
|
||||
Py_InitializeEx(0);
|
||||
PyEval_InitThreads();
|
||||
|
||||
setupPyPath();
|
||||
|
||||
if(PyRun_SimpleString("import devsup\n"
|
||||
"devsup._init(iocMain=True)\n"
|
||||
)) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
(void)PyEval_SaveThread();
|
||||
|
||||
epicsAtExit(&cleanupPy, NULL);
|
||||
}
|
||||
|
||||
#include <epicsExport.h>
|
||||
epicsExportRegistrar(pySetupReg);
|
3
test.cmd
3
test.cmd
@ -3,7 +3,6 @@
|
||||
py "import logging"
|
||||
py "logging.basicConfig(level=logging.DEBUG)"
|
||||
|
||||
py "import devsup; print devsup.HAVE_DBAPI"
|
||||
py "import sys; sys.path.insert(0,'${PWD}/testApp')"
|
||||
py "print sys.path"
|
||||
|
||||
@ -23,4 +22,4 @@ dbLoadRecords("db/test6.db","P=tst:,TNAME=tsum")
|
||||
iocInit()
|
||||
|
||||
# Start Reference tracker
|
||||
py "from devsup import disect; disect.periodic(10)"
|
||||
#py "from devsup import disect; disect.periodic(10)"
|
||||
|
@ -27,11 +27,7 @@ class WfSup(object):
|
||||
x=self.x[:N]
|
||||
|
||||
# calculate inplace: uniform(0.5,2.0)*sin(pha*x)+2
|
||||
val[:] = x
|
||||
val[:] *= pha
|
||||
np.sin(val, out=val)
|
||||
val[:]*=uniform(0.5,2.0)
|
||||
val[:]+=2
|
||||
val[:] = np.sin(x*pha)*uniform(0.5,2.0) + 2
|
||||
|
||||
self.fld.putarraylen(N)
|
||||
|
||||
|
Reference in New Issue
Block a user