mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 05:55:28 +00:00
[3413] Several build steps no longer require python3
- *.spec files in src/lib/dns/tests/testsdata are now included in dist - src/lib/util/pyunittests is removed - src/lib/util/python trimmed down a lot - fix for missing dhcp6_shutdown_test.sh in src/bin/dhcp6 - many python macros in configure.ac removed (more of them to be removed in Makefiles)
This commit is contained in:
162
configure.ac
162
configure.ac
@@ -321,141 +321,6 @@ else
|
||||
AC_SUBST(pkgpyexecdir)
|
||||
fi
|
||||
|
||||
# We need to store the default pyexecdir in a separate variable so that
|
||||
# we can specify in Makefile.am the install directory of various BIND 10
|
||||
# python scripts and loadable modules; in Makefile.am we cannot replace
|
||||
# $(pyexecdir) using itself, e.g, this doesn't work:
|
||||
# pyexecdir = $(pyexecdir)/isc/some_module
|
||||
# The separate variable makes this setup possible as follows:
|
||||
# pyexecdir = $(PYTHON_SITEPKG_DIR)/isc/some_module
|
||||
PYTHON_SITEPKG_DIR=${pyexecdir}
|
||||
AC_SUBST(PYTHON_SITEPKG_DIR)
|
||||
|
||||
# This will be commonly used in various Makefile.am's that need to generate
|
||||
# python log messages.
|
||||
PYTHON_LOGMSGPKG_DIR="\$(top_builddir)/src/lib/python/isc/log_messages"
|
||||
AC_SUBST(PYTHON_LOGMSGPKG_DIR)
|
||||
|
||||
# This is python package paths commonly used in python tests. See
|
||||
# README of log_messages for why it's included.
|
||||
# lib/dns/python/.libs is necessary because __init__.py of isc package
|
||||
# automatically imports isc.datasrc, which then requires the DNS loadable
|
||||
# module. #2145 should eliminate the need for it.
|
||||
COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_builddir)/src/lib/python/isc/cc:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python:\$(abs_top_builddir)/src/lib/dns/python/.libs"
|
||||
AC_SUBST(COMMON_PYTHON_PATH)
|
||||
|
||||
# Check for python development environments
|
||||
if test -x ${PYTHON}-config; then
|
||||
PYTHON_INCLUDES=`${PYTHON}-config --includes`
|
||||
|
||||
# Add any '-L..." flags to PYTHON_LDFLAGS. We first make a copy of
|
||||
# python-config --ldflags, removing any spaces and tabs
|
||||
# between "-L" and its argument (some instances of python-config
|
||||
# insert a space, which would confuse the code below).
|
||||
# Notes: if -L isn't contained at all we can simply skip this process,
|
||||
# so we only go through the flag if it's contained; also, protecting
|
||||
# the output with [] seems necessary for environment to avoid getting
|
||||
# an empty output accidentally.
|
||||
python_config_ldflags=[`${PYTHON}-config --ldflags | ${SED} -ne 's/\([ \t]*-L\)[ ]*\([^ \t]*[ \t]*\)/\1\2/gp'`]
|
||||
for flag in $python_config_ldflags; do
|
||||
flag=`echo $flag | ${SED} -ne 's/^\(\-L.*\)$/\1/p'`
|
||||
if test "X${flag}" != X; then
|
||||
PYTHON_LDFLAGS="$PYTHON_LDFLAGS ${flag}"
|
||||
fi
|
||||
done
|
||||
# on some platforms, ${PYTHON}-config --ldflags doesn't provide a -L
|
||||
# option while having the library under a non trivial directory.
|
||||
# as a workaround we try the "lib" sub directory under the common
|
||||
# prefix for this python.
|
||||
if test -z "${PYTHON_LDFLAGS}"; then
|
||||
PYTHON_LDFLAGS="-L`${PYTHON}-config --prefix`/lib"
|
||||
fi
|
||||
else
|
||||
if test "X$PYTHON_INCLUDES" = X -o "X$PYTHON_LDFLAGS" = X; then
|
||||
AC_MSG_WARN([${PYTHON}-config does not exist or is not executable, so we could not detect python development environment. Your system may require an additional package (e.g. "python3-dev"). Alternatively, if you are sure you have python headers and libraries, define PYTHON_INCLUDES and PYTHON_LDFLAGS and run this script.])
|
||||
fi
|
||||
fi
|
||||
|
||||
# Some OSes including NetBSD don't install libpython.so in a well known path.
|
||||
# To avoid requiring dynamic library path with our python wrapper loadable
|
||||
# modules, we embed the path to the modules when possible. We do this even
|
||||
# when the path is known in the common operational environment (e.g. when
|
||||
# it's stored in a common "hint" file) for simplicity.
|
||||
if test "x$ISC_RPATH_FLAG" != "x"; then
|
||||
python_rpath=
|
||||
for flag in ${PYTHON_LDFLAGS}; do
|
||||
python_rpath="${python_rpath} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
|
||||
done
|
||||
PYTHON_LDFLAGS="${PYTHON_LDFLAGS} ${python_rpath}"
|
||||
fi
|
||||
|
||||
AC_SUBST(PYTHON_INCLUDES)
|
||||
AC_SUBST(PYTHON_LDFLAGS)
|
||||
|
||||
CPPFLAGS_SAVED="$CPPFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS ${PYTHON_INCLUDES}"
|
||||
AC_CHECK_HEADERS([Python.h],, AC_MSG_ERROR([Missing Python.h]))
|
||||
CPPFLAGS="$CPPFLAGS_SAVED"
|
||||
|
||||
# Check for python library. Needed for Python-wrapper libraries.
|
||||
LDFLAGS_SAVED="$LDFLAGS"
|
||||
LDFLAGS="$LDFLAGS $PYTHON_LDFLAGS"
|
||||
python_bin="python${PYTHON_VERSION}"
|
||||
AC_CHECK_LIB($python_bin, main, python_lib=$python_bin, python_lib=no)
|
||||
if test $python_lib != "no"; then
|
||||
PYTHON_LIB="-l$python_lib"
|
||||
fi
|
||||
AC_SUBST(PYTHON_LIB)
|
||||
LDFLAGS=$LDFLAGS_SAVED
|
||||
|
||||
# Python 3.2 changed the return type of internal hash function to
|
||||
# Py_hash_t and some platforms (such as Solaris) strictly check for long
|
||||
# vs Py_hash_t. So we detect and use the appropriate return type.
|
||||
# Remove this test (and associated changes in pydnspp_config.h.in) when
|
||||
# we require Python 3.2.
|
||||
have_py_hash_t=0
|
||||
CPPFLAGS_SAVED="$CPPFLAGS"
|
||||
CPPFLAGS=${PYTHON_INCLUDES}
|
||||
AC_MSG_CHECKING(for Py_hash_t)
|
||||
AC_TRY_COMPILE([#include <Python.h>
|
||||
Py_hash_t h;],,
|
||||
[AC_MSG_RESULT(yes)
|
||||
have_py_hash_t=1],
|
||||
[AC_MSG_RESULT(no)])
|
||||
CPPFLAGS="$CPPFLAGS_SAVED"
|
||||
HAVE_PY_HASH_T=$have_py_hash_t
|
||||
AC_SUBST(HAVE_PY_HASH_T)
|
||||
|
||||
# (g++ only check)
|
||||
# Python 3.2 has an unused parameter in one of its headers. This
|
||||
# has been reported, but not fixed as of yet, so we check if we need
|
||||
# to set -Wno-unused-parameter.
|
||||
if test "X$GXX" = "Xyes" -a "$werror_ok" = 1; then
|
||||
CPPFLAGS_SAVED="$CPPFLAGS"
|
||||
CPPFLAGS=${PYTHON_INCLUDES}
|
||||
CXXFLAGS_SAVED="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
|
||||
AC_MSG_CHECKING([whether we need -Wno-unused-parameter for python])
|
||||
AC_TRY_COMPILE(
|
||||
[#include <Python.h>],
|
||||
[],
|
||||
[AC_MSG_RESULT(no)],
|
||||
[
|
||||
CXXFLAGS="$CXXFLAGS -Wno-unused-parameter"
|
||||
AC_TRY_COMPILE([#include <Python.h>],
|
||||
[],
|
||||
[AC_MSG_RESULT(yes)
|
||||
PYTHON_CXXFLAGS="${PYTHON_CXXFLAGS} -Wno-unused-parameter"
|
||||
AC_SUBST(PYTHON_CXXFLAGS)
|
||||
],
|
||||
[AC_MSG_ERROR([Can't compile against Python.h. If you're using MacOS X and have installed Python with Homebrew, see http://kea.isc.org/wiki/SystemNotesMacOSX])]
|
||||
)
|
||||
]
|
||||
)
|
||||
CXXFLAGS="$CXXFLAGS_SAVED"
|
||||
CPPFLAGS="$CPPFLAGS_SAVED"
|
||||
fi
|
||||
|
||||
# produce PIC unless we disable shared libraries. need this for python bindings.
|
||||
if test $enable_shared != "no" -a "X$GXX" = "Xyes"; then
|
||||
B10_CXXFLAGS="$B10_CXXFLAGS -fPIC"
|
||||
@@ -528,29 +393,6 @@ AC_TRY_COMPILE([
|
||||
AC_DEFINE(HAVE_SA_LEN, 1, [Define to 1 if sockaddr has a sa_len member, and corresponding sin_len and sun_len])],
|
||||
AC_MSG_RESULT(no))
|
||||
|
||||
AC_ARG_WITH(pycoverage,
|
||||
[ --with-pycoverage[=PROGRAM] enable python code coverage using the specified coverage], pycoverage="$withval", pycoverage="no")
|
||||
if test "$pycoverage" = "no" ; then
|
||||
# just run the tests normally with python
|
||||
PYCOVERAGE_RUN="${PYTHON}"
|
||||
USE_PYCOVERAGE="no"
|
||||
elif test "$pycoverage" = "yes" ; then
|
||||
PYCOVERAGE="coverage"
|
||||
PYCOVERAGE_RUN="${PYCOVERAGE} run --branch --append"
|
||||
USE_PYCOVERAGE="yes"
|
||||
else
|
||||
PYCOVERAGE="$pycoverage"
|
||||
PYCOVERAGE_RUN="${PYCOVERAGE} run --branch --append"
|
||||
USE_PYCOVERAGE="yes"
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_PYTHON_COVERAGE, test x$USE_PYCOVERAGE != xno)
|
||||
AC_SUBST(PYCOVERAGE)
|
||||
AC_SUBST(PYCOVERAGE_RUN)
|
||||
AC_SUBST(USE_PYCOVERAGE)
|
||||
|
||||
|
||||
|
||||
|
||||
enable_gtest="no"
|
||||
GTEST_INCLUDES=
|
||||
|
||||
@@ -1463,11 +1305,8 @@ AC_CONFIG_FILES([compatcheck/Makefile
|
||||
src/lib/testutils/testdata/Makefile
|
||||
src/lib/util/io/Makefile
|
||||
src/lib/util/Makefile
|
||||
src/lib/util/python/doxygen2pydoc.py
|
||||
src/lib/util/python/gen_wiredata.py
|
||||
src/lib/util/python/Makefile
|
||||
src/lib/util/python/mkpywrapper.py
|
||||
src/lib/util/pyunittests/Makefile
|
||||
src/lib/util/tests/Makefile
|
||||
src/lib/util/threads/Makefile
|
||||
src/lib/util/threads/tests/Makefile
|
||||
@@ -1488,7 +1327,6 @@ AC_CONFIG_FILES([compatcheck/Makefile
|
||||
chmod +x src/lib/log/tests/local_file_test.sh
|
||||
chmod +x src/lib/log/tests/logger_lock_test.sh
|
||||
chmod +x src/lib/log/tests/severity_test.sh
|
||||
chmod +x src/lib/python/isc/log/tests/log_console.py
|
||||
chmod +x src/lib/util/python/doxygen2pydoc.py
|
||||
chmod +x src/lib/util/python/gen_wiredata.py
|
||||
chmod +x src/lib/util/python/mkpywrapper.py
|
||||
|
@@ -6,7 +6,7 @@ SHTESTS += dhcp6_reconfigure_test.sh
|
||||
SHTESTS += dhcp6_sigterm_test.sh
|
||||
SHTESTS += dhcp6_sigint_test.sh
|
||||
endif
|
||||
EXTRA_DIST = $(SHTESTS)
|
||||
EXTRA_DIST = $(SHTESTS) dhcp6_shutdown_test.sh
|
||||
|
||||
# Explicitly specify paths to dynamic libraries required by loadable python
|
||||
# modules. That is required on Mac OS systems. Otherwise we will get exception
|
||||
|
77
src/lib/dns/tests/testdata/Makefile.am
vendored
77
src/lib/dns/tests/testdata/Makefile.am
vendored
@@ -118,5 +118,82 @@ EXTRA_DIST += broken.zone
|
||||
EXTRA_DIST += origincheck.txt
|
||||
EXTRA_DIST += omitcheck.txt
|
||||
|
||||
# Generated .wire files
|
||||
EXTRA_DIST += edns_toWire1.wire edns_toWire2.wire
|
||||
EXTRA_DIST += edns_toWire3.wire edns_toWire4.wire
|
||||
EXTRA_DIST += message_fromWire10.wire
|
||||
EXTRA_DIST += message_fromWire11.wire message_fromWire12.wire
|
||||
EXTRA_DIST += message_fromWire13.wire message_fromWire14.wire
|
||||
EXTRA_DIST += message_fromWire15.wire message_fromWire16.wire
|
||||
EXTRA_DIST += message_fromWire17.wire message_fromWire18.wire
|
||||
EXTRA_DIST += message_fromWire19.wire message_fromWire20.wire
|
||||
EXTRA_DIST += message_fromWire21.wire message_fromWire22.wire
|
||||
EXTRA_DIST += message_toWire1 message_toWire2.wire message_toWire3.wire
|
||||
EXTRA_DIST += message_toWire4.wire message_toWire5.wire
|
||||
EXTRA_DIST += message_toText1.txt message_toText1.wire
|
||||
EXTRA_DIST += message_toText2.txt message_toText2.wire
|
||||
EXTRA_DIST += message_toText3.txt message_toText3.wire
|
||||
EXTRA_DIST += name_toWire5.wire name_toWire6.wire
|
||||
EXTRA_DIST += rdatafields1.wire rdatafields2.wire rdatafields3.wire
|
||||
EXTRA_DIST += rdatafields4.wire rdatafields5.wire rdatafields6.wire
|
||||
EXTRA_DIST += rdata_dnskey_fromWire.wire rdata_dnskey_empty_keydata_fromWire.wire
|
||||
EXTRA_DIST += rdata_nsec_fromWire4.wire rdata_nsec_fromWire5.wire
|
||||
EXTRA_DIST += rdata_nsec_fromWire6.wire rdata_nsec_fromWire7.wire
|
||||
EXTRA_DIST += rdata_nsec_fromWire8.wire rdata_nsec_fromWire9.wire
|
||||
EXTRA_DIST += rdata_nsec_fromWire10.wire
|
||||
EXTRA_DIST += rdata_nsec_fromWire16.wire
|
||||
EXTRA_DIST += rdata_nsec3param_fromWire2.wire
|
||||
EXTRA_DIST += rdata_nsec3param_fromWire11.wire
|
||||
EXTRA_DIST += rdata_nsec3param_fromWire13.wire
|
||||
EXTRA_DIST += rdata_nsec3_fromWire2.wire rdata_nsec3_fromWire3
|
||||
EXTRA_DIST += rdata_nsec3_fromWire4.wire rdata_nsec3_fromWire5.wire
|
||||
EXTRA_DIST += rdata_nsec3_fromWire6.wire rdata_nsec3_fromWire7.wire
|
||||
EXTRA_DIST += rdata_nsec3_fromWire8.wire rdata_nsec3_fromWire9.wire
|
||||
EXTRA_DIST += rdata_nsec3_fromWire10.wire rdata_nsec3_fromWire11.wire
|
||||
EXTRA_DIST += rdata_nsec3_fromWire12.wire rdata_nsec3_fromWire13.wire
|
||||
EXTRA_DIST += rdata_nsec3_fromWire14.wire rdata_nsec3_fromWire15.wire
|
||||
EXTRA_DIST += rdata_nsec3_fromWire16.wire rdata_nsec3_fromWire17.wire
|
||||
EXTRA_DIST += rdata_rrsig_fromWire2.wire
|
||||
EXTRA_DIST += rdata_rp_fromWire1.wire rdata_rp_fromWire2.wire
|
||||
EXTRA_DIST += rdata_rp_fromWire3.wire rdata_rp_fromWire4.wire
|
||||
EXTRA_DIST += rdata_rp_fromWire5.wire rdata_rp_fromWire6.wire
|
||||
EXTRA_DIST += rdata_rp_toWire1.wire rdata_rp_toWire2.wire
|
||||
EXTRA_DIST += rdata_sshfp_fromWire1.wire rdata_sshfp_fromWire2.wire
|
||||
EXTRA_DIST += rdata_sshfp_fromWire3.wire rdata_sshfp_fromWire4.wire
|
||||
EXTRA_DIST += rdata_sshfp_fromWire5.wire rdata_sshfp_fromWire6.wire
|
||||
EXTRA_DIST += rdata_sshfp_fromWire7.wire rdata_sshfp_fromWire8.wire
|
||||
EXTRA_DIST += rdata_afsdb_fromWire1.wire rdata_afsdb_fromWire2.wire
|
||||
EXTRA_DIST += rdata_afsdb_fromWire3.wire rdata_afsdb_fromWire4.wire
|
||||
EXTRA_DIST += rdata_afsdb_fromWire5.wire
|
||||
EXTRA_DIST += rdata_afsdb_toWire1.wire rdata_afsdb_toWire2.wire
|
||||
EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.wire
|
||||
EXTRA_DIST += rdata_minfo_fromWire1.wire rdata_minfo_fromWire2.wire
|
||||
EXTRA_DIST += rdata_minfo_fromWire3.wire rdata_minfo_fromWire4.wire
|
||||
EXTRA_DIST += rdata_minfo_fromWire5.wire rdata_minfo_fromWire6.wire
|
||||
EXTRA_DIST += rdata_minfo_toWire1.wire rdata_minfo_toWire2.wire
|
||||
EXTRA_DIST += rdata_minfo_toWireUncompressed1.wire
|
||||
EXTRA_DIST += rdata_minfo_toWireUncompressed2.wire
|
||||
EXTRA_DIST += rdata_txt_fromWire1 rdata_txt_fromWire2.wire
|
||||
EXTRA_DIST += rdata_txt_fromWire3.wire rdata_txt_fromWire4.wire
|
||||
EXTRA_DIST += rdata_txt_fromWire5.wire rdata_unknown_fromWire
|
||||
EXTRA_DIST += rdata_tlsa_fromWire3.wire rdata_tlsa_fromWire4.wire
|
||||
EXTRA_DIST += rdata_tlsa_fromWire5.wire rdata_tlsa_fromWire6.wire
|
||||
EXTRA_DIST += rdata_tlsa_fromWire7.wire rdata_tlsa_fromWire8.wire
|
||||
EXTRA_DIST += rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire
|
||||
EXTRA_DIST += rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire
|
||||
EXTRA_DIST += rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire
|
||||
EXTRA_DIST += rdata_tsig_fromWire7.wire rdata_tsig_fromWire8.wire
|
||||
EXTRA_DIST += rdata_tsig_fromWire9.wire
|
||||
EXTRA_DIST += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
|
||||
EXTRA_DIST += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
|
||||
EXTRA_DIST += rdata_tsig_toWire5.wire
|
||||
EXTRA_DIST += rdata_caa_fromWire1.wire rdata_caa_fromWire2.wire
|
||||
EXTRA_DIST += rdata_caa_fromWire3.wire rdata_caa_fromWire4.wire
|
||||
EXTRA_DIST += tsigrecord_toWire1.wire tsigrecord_toWire2.wire
|
||||
EXTRA_DIST += tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire
|
||||
EXTRA_DIST += tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire
|
||||
EXTRA_DIST += tsig_verify7.wire tsig_verify8.wire tsig_verify9.wire
|
||||
EXTRA_DIST += tsig_verify10.wire
|
||||
|
||||
.spec.wire:
|
||||
$(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
|
||||
|
@@ -1,4 +1,4 @@
|
||||
SUBDIRS = . io unittests tests pyunittests python threads
|
||||
SUBDIRS = . io unittests tests python threads
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
|
||||
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
|
||||
@@ -41,7 +41,6 @@ libkea_util_la_SOURCES += encode/binary_from_base16.h
|
||||
libkea_util_la_SOURCES += random/qid_gen.h random/qid_gen.cc
|
||||
libkea_util_la_SOURCES += random/random_number_generator.h
|
||||
|
||||
EXTRA_DIST = python/pycppwrapper_util.h
|
||||
libkea_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
|
||||
CLEANFILES = *.gcno *.gcda
|
||||
|
||||
|
@@ -10,14 +10,3 @@ libkea_util_io_la_SOURCES += pktinfo_utilities.h
|
||||
libkea_util_io_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
|
||||
|
||||
CLEANFILES = *.gcno *.gcda
|
||||
|
||||
pyexec_LTLIBRARIES = libutil_io_python.la
|
||||
# Python prefers .so, while some OSes (specifically MacOS) use a different
|
||||
# suffix for dynamic objects. -module is necessary to work this around.
|
||||
libutil_io_python_la_LDFLAGS = -module -avoid-version
|
||||
libutil_io_python_la_SOURCES = fdshare_python.cc
|
||||
libutil_io_python_la_LIBADD = libkea-util-io.la
|
||||
libutil_io_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
|
||||
# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
|
||||
# placed after -Wextra defined in AM_CXXFLAGS
|
||||
libutil_io_python_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
|
||||
|
@@ -1,98 +0,0 @@
|
||||
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#include <structmember.h>
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include "fd_share.h"
|
||||
|
||||
|
||||
static PyObject*
|
||||
fdshare_recv_fd(PyObject*, PyObject* args) {
|
||||
int sock, fd;
|
||||
if (!PyArg_ParseTuple(args, "i", &sock)) {
|
||||
return (NULL);
|
||||
}
|
||||
fd = isc::util::io::recv_fd(sock);
|
||||
return (Py_BuildValue("i", fd));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
fdshare_send_fd(PyObject*, PyObject* args) {
|
||||
int sock, fd, result;
|
||||
if (!PyArg_ParseTuple(args, "ii", &sock, &fd)) {
|
||||
return (NULL);
|
||||
}
|
||||
result = isc::util::io::send_fd(sock, fd);
|
||||
return (Py_BuildValue("i", result));
|
||||
}
|
||||
|
||||
static PyMethodDef fdshare_Methods[] = {
|
||||
{"send_fd", fdshare_send_fd, METH_VARARGS, ""},
|
||||
{"recv_fd", fdshare_recv_fd, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
static PyModuleDef bind10_fdshare_python = {
|
||||
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
|
||||
"bind10_fdshare",
|
||||
"Python bindings for fdshare",
|
||||
-1,
|
||||
fdshare_Methods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit_libutil_io_python(void) {
|
||||
PyObject *mod = PyModule_Create(&bind10_fdshare_python);
|
||||
if (mod == NULL) {
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
PyObject* FD_SYSTEM_ERROR = Py_BuildValue("i",
|
||||
isc::util::io::FD_SYSTEM_ERROR);
|
||||
if (FD_SYSTEM_ERROR == NULL) {
|
||||
Py_XDECREF(mod);
|
||||
return (NULL);
|
||||
}
|
||||
int ret = PyModule_AddObject(mod, "FD_SYSTEM_ERROR", FD_SYSTEM_ERROR);
|
||||
if (ret == -1) {
|
||||
Py_XDECREF(FD_SYSTEM_ERROR);
|
||||
Py_XDECREF(mod);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
PyObject* FD_OTHER_ERROR = Py_BuildValue("i",
|
||||
isc::util::io::FD_OTHER_ERROR);
|
||||
if (FD_OTHER_ERROR == NULL) {
|
||||
Py_XDECREF(mod);
|
||||
return (NULL);
|
||||
}
|
||||
ret = PyModule_AddObject(mod, "FD_OTHER_ERROR", FD_OTHER_ERROR);
|
||||
if (-1 == ret) {
|
||||
Py_XDECREF(FD_OTHER_ERROR);
|
||||
Py_XDECREF(mod);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
return (mod);
|
||||
}
|
||||
|
3
src/lib/util/python/.gitignore
vendored
3
src/lib/util/python/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
/doxygen2pydoc.py
|
||||
/gen_wiredata.py
|
||||
/mkpywrapper.py
|
@@ -1,3 +1,2 @@
|
||||
noinst_SCRIPTS = doxygen2pydoc.py gen_wiredata.py mkpywrapper.py const2hdr.py \
|
||||
pythonize_constants.py
|
||||
EXTRA_DIST = const2hdr.py pythonize_constants.py
|
||||
noinst_SCRIPTS = const2hdr.py gen_wiredata.py
|
||||
EXTRA_DIST = const2hdr.py
|
||||
|
@@ -1,680 +0,0 @@
|
||||
#!@PYTHON@
|
||||
|
||||
# Copyright (C) 2011 Internet Systems Consortium.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
|
||||
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
||||
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
||||
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
r"""
|
||||
A helper to semi-auto generate Python docstring text from C++ Doxygen
|
||||
documentation.
|
||||
|
||||
This script converts an XML-format doxygen documentation for C++ library
|
||||
into a template Python docstring for the corresponding Python version
|
||||
of the library. While it's not perfect and you'll still need to edit the
|
||||
output by hand, but past experiments showed the script produces a pretty
|
||||
good template. It will help provide more compatible documentation for
|
||||
both C++ and Python versions of library from a unified source (C++ Doxygen
|
||||
documentation) with minimizing error-prone and boring manual conversion.
|
||||
|
||||
HOW TO USE IT
|
||||
|
||||
1. Generate XML output by doxygen. Use bind10/doc/Doxyfile-xml:
|
||||
|
||||
% cd bind10/doc
|
||||
% doxygen Doxyfile-xml
|
||||
(XML files will be generated under bind10/doc/html/xml)
|
||||
|
||||
2. Identify the xml file of the conversion target (C++ class, function, etc)
|
||||
|
||||
This is a bit tricky. You'll probably need to do manual search.
|
||||
For example, to identify the xml file for a C++ class
|
||||
isc::datasrc::memory::ZoneWriter, you might do:
|
||||
|
||||
% cd bind10/doc/html/xml
|
||||
% grep ZoneWriter *.xml | grep 'kind="class"'
|
||||
index.xml: <compound refid="d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter" kind="class"><name>isc::datasrc::memory::ZoneWriter</name>
|
||||
|
||||
In this case the file under the d4/d3c directory (with .xml suffix) would
|
||||
be the file you're looking for.
|
||||
|
||||
3. Run this script for the xml file:
|
||||
|
||||
% python3 doxygen2pydoc.py <top_srcdir>/doc/html/xml/d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter.xml > output.cc
|
||||
|
||||
The template content is dumped to standard out (redirected to file
|
||||
"output.cc" in this example).
|
||||
|
||||
Sometimes the script produces additional output to standard error,
|
||||
like this:
|
||||
|
||||
Replaced camelCased terms:
|
||||
resetMemorySegment => reset_memory_segment
|
||||
getConfiguration => get_configuration
|
||||
|
||||
In BIND 10 naming convention for methods is different for C++ and
|
||||
Python. This script uses some heuristic guess to convert the
|
||||
C++-style method names to likely Python-style ones, and the converted
|
||||
method names are used in the dumped template. In many cases the guessed
|
||||
names are correct, but you should check this list and make adjustments
|
||||
by hand if necessary.
|
||||
|
||||
If there's no standard error output, this type of conversion didn't
|
||||
happen.
|
||||
|
||||
4. Edit and copy the template
|
||||
|
||||
The dumped template has the following organization:
|
||||
|
||||
namespace {
|
||||
#ifdef COPY_THIS_TO_MAIN_CC
|
||||
{ "cleanup", ZoneWriter_cleanup, METH_NOARGS,
|
||||
ZoneWriter_cleanup_doc },
|
||||
{ "install", ZoneWriter_install, METH_NOARGS,
|
||||
ZoneWriter_install_doc },
|
||||
{ "load", ZoneWriter_load, METH_VARARGS,
|
||||
ZoneWriter_load_doc },
|
||||
#endif // COPY_THIS_TO_MAIN_CC
|
||||
|
||||
const char* const ZoneWriter_doc = "\
|
||||
...
|
||||
";
|
||||
|
||||
const char* const ZoneWriter_install_doc = "\
|
||||
...
|
||||
";
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
The ifdef-ed block is a template for class methods information
|
||||
to be added to the corresponding PyMethodDef structure array
|
||||
(your wrapper C++ source would have something like ZoneWriter_methods
|
||||
of this type). These lines should be copied there. As long as
|
||||
the method names and corresponding wrapper function (such as
|
||||
ZoneWriter_cleanup) are correct you shouldn't have to edit this part
|
||||
(and they would be normally correct, unless the guessed method name
|
||||
conversion was needed).
|
||||
|
||||
The rest of the content is a sequence of constant C-string variables.
|
||||
Usually the first variable corresponds to the class description, and
|
||||
the rest are method descriptions (note that ZoneWriter_install_doc
|
||||
is referenced from the ifdef-ed block). The content of this part
|
||||
would generally make sense, but you'll often need to make some
|
||||
adjsutments by hand. A common examples of such adjustment is to
|
||||
replace "NULL" with "None". Also, it's not uncommon that some part
|
||||
of the description simply doesn't apply to the Python version or
|
||||
that Python specific notes are needed. So go through the description
|
||||
carefully and make necessary changes. A common practice is to add
|
||||
comments for a summary of adjustments like this:
|
||||
|
||||
// Modifications:
|
||||
// NULL->None
|
||||
// - removed notes about derived classes (which doesn't apply for python)
|
||||
const char* const ZoneWriter_doc = "\
|
||||
...
|
||||
";
|
||||
This note will help next time you need to auto-generate and edit the
|
||||
template (probably because the original C++ document is updated).
|
||||
|
||||
You can simply copy this part to the main C++ wrapper file, but since
|
||||
it's relatively large a common practice is to maintain it in a separate
|
||||
file that is exclusively included from the main file: if the name of
|
||||
the main file is zonewriter_python.cc, the pydoc strings would be copied
|
||||
in zonewriter_python_inc.cc, and the main file would have this line:
|
||||
|
||||
#include "zonewriter_inc.cc"
|
||||
|
||||
(In case you are C++ language police: it's okay to use the unnamed
|
||||
name space for a file to be included because it's essentially a part
|
||||
of the single .cc file, not expected to be included by others).
|
||||
|
||||
In either case, the ifdef-ed part should be removed.
|
||||
|
||||
ADVANCED FEATURES
|
||||
|
||||
You can use a special "xmlonly" doxygen command in C++ doxygent document
|
||||
in order to include Python code excerpt (while hiding it from the doxygen
|
||||
output for the C++ version). This command will be converted to
|
||||
a special XML tag in the XML output.
|
||||
|
||||
The block enclosed by \xmlonly and \endxmlonly should contain
|
||||
a verbatim XML tag named "pythonlisting", in which the python code should
|
||||
be placed.
|
||||
/// \code
|
||||
/// Name name("example.com");
|
||||
/// std::cout << name.toText() << std::endl;
|
||||
/// \endcode
|
||||
///
|
||||
/// \xmlonly <pythonlisting>
|
||||
/// name = Name("example.com")
|
||||
/// print(name.to_text())
|
||||
/// </pythonlisting> \endxmlonly
|
||||
|
||||
Note that there must be a blank line between \endcode and \xmlonly.
|
||||
doxygen2pydoc assume the pythonlisting tag is in a separate <para> node.
|
||||
This blank ensures doxygen will produce the XML file that meets the
|
||||
assumption.
|
||||
|
||||
INTERNAL MEMO (incomplete, and not very unredable yet)
|
||||
|
||||
This simplified utility assumes the following structure:
|
||||
...
|
||||
<compounddef ...>
|
||||
<compoundname>isc::dns::TSIGError</compoundname>
|
||||
<sectiondef kind="user-defined">
|
||||
constructor, destructor
|
||||
</sectiondef>
|
||||
<sectiondef kind="public-type">
|
||||
..
|
||||
</sectiondef>
|
||||
<sectiondef kind="public-func">
|
||||
<memberdef kind="function"...>
|
||||
<type>return type (if any)</type>
|
||||
<argsstring>(...) [const]</argsstring>
|
||||
<name>method name</name>
|
||||
<briefdescription>method's brief description</briefdescription>
|
||||
<detaileddescription>
|
||||
<para>...</para>...
|
||||
<para>
|
||||
<parameterlist kind="exception">
|
||||
<parameteritem>
|
||||
<parameternamelist>
|
||||
<parametername>Exception name</parametername>
|
||||
</parameternamelist>
|
||||
<parameterdescription>
|
||||
<para>exception desc</para>
|
||||
</parameterdescription>
|
||||
</parameteritem>
|
||||
</parameterlist>
|
||||
<parameterlist kind="param">
|
||||
<parameteritem>
|
||||
<parameternamelist>
|
||||
<parametername>param name</parametername>
|
||||
</parameternamelist>
|
||||
<parameterdescription>
|
||||
<para>param desc</para>
|
||||
</parameterdescription>
|
||||
</parameteritem>
|
||||
...
|
||||
</parameterlist>
|
||||
<simplesect kind="return">Return value</simplesect>
|
||||
</para>
|
||||
</detaileddescription>
|
||||
</memberdef>
|
||||
</sectiondef>
|
||||
<sectiondef kind="public-static-attrib|user-defined">
|
||||
<memberdef kind="variable"...>
|
||||
<name>class-specific-constant</name>
|
||||
<initializer>value</initializer>
|
||||
<brief|detaileddescription>paragraph(s)</brief|detaileddescription>
|
||||
</sectiondef>
|
||||
<briefdescription>
|
||||
class's brief description
|
||||
</briefdescription>
|
||||
<detaileddescription>
|
||||
class's detailed description
|
||||
</detaileddescription>
|
||||
</compounddef>
|
||||
"""
|
||||
|
||||
import re, string, sys, textwrap
|
||||
from xml.dom.minidom import parse
|
||||
from textwrap import fill, dedent, TextWrapper
|
||||
|
||||
camel_replacements = {}
|
||||
member_functions = []
|
||||
constructors = []
|
||||
class_variables = []
|
||||
|
||||
RE_CAMELTERM = re.compile('([\s\.]|^)[a-z]+[A-Z]\S*')
|
||||
RE_SIMPLECAMEL = re.compile("([a-z])([A-Z])")
|
||||
RE_CAMELAFTERUPPER = re.compile("([A-Z])([A-Z])([a-z])")
|
||||
|
||||
class Paragraph:
|
||||
TEXT = 0
|
||||
ITEMIZEDLIST = 1
|
||||
CPPLISTING = 2
|
||||
PYLISTING = 3
|
||||
VERBATIM = 4
|
||||
|
||||
def __init__(self, xml_node):
|
||||
if len(xml_node.getElementsByTagName("pythonlisting")) > 0:
|
||||
self.type = self.PYLISTING
|
||||
self.text = re.sub("///", "", get_text(xml_node))
|
||||
elif len(xml_node.getElementsByTagName("verbatim")) > 0:
|
||||
self.type = self.VERBATIM
|
||||
self.text = get_text(xml_node)
|
||||
elif len(xml_node.getElementsByTagName("programlisting")) > 0:
|
||||
# We ignore node containing a "programlisting" tag.
|
||||
# They are C++ example code, and we are not interested in them
|
||||
# in pydoc.
|
||||
self.type = self.CPPLISTING
|
||||
elif len(xml_node.getElementsByTagName("itemizedlist")) > 0:
|
||||
self.type = self.ITEMIZEDLIST
|
||||
self.items = []
|
||||
for item in xml_node.getElementsByTagName("listitem"):
|
||||
self.items.append(get_text(item))
|
||||
else:
|
||||
self.type = self.TEXT
|
||||
|
||||
# A single textual paragraph could have multiple simple sections
|
||||
# if it contains notes.
|
||||
|
||||
self.texts = []
|
||||
subnodes = []
|
||||
for child in xml_node.childNodes:
|
||||
if child.nodeType == child.ELEMENT_NODE and \
|
||||
child.nodeName == 'simplesect' and \
|
||||
child.getAttribute('kind') == 'note':
|
||||
if len(subnodes) > 0:
|
||||
self.texts.append(get_text_fromnodelist(subnodes))
|
||||
subnodes = []
|
||||
subtext = 'Note: '
|
||||
for t in child.childNodes:
|
||||
subtext += get_text(t)
|
||||
self.texts.append(subtext)
|
||||
else:
|
||||
subnodes.append(child)
|
||||
if len(subnodes) > 0:
|
||||
self.texts.append(get_text_fromnodelist(subnodes))
|
||||
|
||||
def dump(self, f, wrapper):
|
||||
if self.type == self.CPPLISTING:
|
||||
return
|
||||
elif self.type == self.ITEMIZEDLIST:
|
||||
for item in self.items:
|
||||
item_wrapper = TextWrapper(\
|
||||
initial_indent=wrapper.initial_indent + "- ",
|
||||
subsequent_indent=wrapper.subsequent_indent + " ")
|
||||
dump_filled_text(f, item_wrapper, item)
|
||||
f.write("\\n\\\n")
|
||||
elif self.type == self.TEXT:
|
||||
for text in self.texts:
|
||||
if text != self.texts[0]:
|
||||
f.write("\\n\\\n")
|
||||
dump_filled_text(f, wrapper, text)
|
||||
f.write("\\n\\\n")
|
||||
else:
|
||||
dump_filled_text(f, None, self.text)
|
||||
f.write("\\n\\\n")
|
||||
f.write("\\n\\\n")
|
||||
|
||||
class NamedItem:
|
||||
def __init__(self, name, desc):
|
||||
self.name = name
|
||||
self.desc = desc
|
||||
|
||||
def dump(self, f, wrapper):
|
||||
# we use deeper indent inside the item list.
|
||||
new_initial_indent = wrapper.initial_indent + " " * 2
|
||||
new_subsequent_indent = wrapper.subsequent_indent + " " * (2 + 11)
|
||||
local_wrapper = TextWrapper(initial_indent=new_initial_indent,
|
||||
subsequent_indent=new_subsequent_indent)
|
||||
|
||||
# concatenate name and description with a fixed width (up to 10 chars)
|
||||
# for the name, and wrap the entire text, then dump it to file.
|
||||
dump_filled_text(f, local_wrapper, "%-10s %s" % (self.name, self.desc))
|
||||
f.write("\\n\\\n")
|
||||
|
||||
class FunctionDefinition:
|
||||
# function types
|
||||
CONSTRUCTOR = 0
|
||||
COPY_CONSTRUCTOR = 1
|
||||
DESTRUCTOR = 2
|
||||
ASSIGNMENT_OP = 3
|
||||
OTHER = 4
|
||||
|
||||
def __init__(self):
|
||||
self.type = self.OTHER
|
||||
self.name = None
|
||||
self.pyname = None
|
||||
self.args = ""
|
||||
self.ret_type = None
|
||||
self.brief_desc = None
|
||||
self.detailed_desc = []
|
||||
self.exceptions = []
|
||||
self.parameters = []
|
||||
self.returns = None
|
||||
self.have_param = False
|
||||
|
||||
def dump_doc(self, f, wrapper=TextWrapper()):
|
||||
f.write(self.pyname + "(" + self.args + ")")
|
||||
if self.ret_type is not None:
|
||||
f.write(" -> " + self.ret_type)
|
||||
f.write("\\n\\\n\\n\\\n")
|
||||
|
||||
if self.brief_desc is not None:
|
||||
dump_filled_text(f, wrapper, self.brief_desc)
|
||||
f.write("\\n\\\n\\n\\\n")
|
||||
|
||||
for para in self.detailed_desc:
|
||||
para.dump(f, wrapper)
|
||||
|
||||
if len(self.exceptions) > 0:
|
||||
f.write(wrapper.fill("Exceptions:") + "\\n\\\n")
|
||||
for ex_desc in self.exceptions:
|
||||
ex_desc.dump(f, wrapper)
|
||||
f.write("\\n\\\n")
|
||||
if len(self.parameters) > 0:
|
||||
f.write(wrapper.fill("Parameters:") + "\\n\\\n")
|
||||
for param_desc in self.parameters:
|
||||
param_desc.dump(f, wrapper)
|
||||
f.write("\\n\\\n")
|
||||
if self.returns is not None:
|
||||
dump_filled_text(f, wrapper, "Return Value(s): " + self.returns)
|
||||
f.write("\\n\\\n")
|
||||
|
||||
def dump_pymethod_def(self, f, class_name):
|
||||
f.write(' { "' + self.pyname + '", ')
|
||||
f.write(class_name + '_' + self.name + ', ')
|
||||
if len(self.parameters) == 0:
|
||||
f.write('METH_NOARGS,\n')
|
||||
else:
|
||||
f.write('METH_VARARGS,\n')
|
||||
f.write(' ' + class_name + '_' + self.name + '_doc },\n')
|
||||
|
||||
class VariableDefinition:
|
||||
def __init__(self, nodelist):
|
||||
self.value = None
|
||||
self.brief_desc = None
|
||||
self.detailed_desc = []
|
||||
|
||||
for node in nodelist:
|
||||
if node.nodeName == "name":
|
||||
self.name = get_text(node)
|
||||
elif node.nodeName == "initializer":
|
||||
self.value = get_text(node)
|
||||
elif node.nodeName == "briefdescription":
|
||||
self.brief_desc = get_text(node)
|
||||
elif node.nodeName == "detaileddescription":
|
||||
for para in node.childNodes:
|
||||
if para.nodeName != "para":
|
||||
# ignore surrounding empty nodes
|
||||
continue
|
||||
self.detailed_desc.append(Paragraph(para))
|
||||
|
||||
def dump_doc(self, f, wrapper=TextWrapper()):
|
||||
name_value = self.name
|
||||
if self.value is not None:
|
||||
name_value += ' = ' + self.value
|
||||
dump_filled_text(f, wrapper, name_value)
|
||||
f.write('\\n\\\n')
|
||||
|
||||
desc_initial_indent = wrapper.initial_indent + " "
|
||||
desc_subsequent_indent = wrapper.subsequent_indent + " "
|
||||
desc_wrapper = TextWrapper(initial_indent=desc_initial_indent,
|
||||
subsequent_indent=desc_subsequent_indent)
|
||||
if self.brief_desc is not None:
|
||||
dump_filled_text(f, desc_wrapper, self.brief_desc)
|
||||
f.write("\\n\\\n\\n\\\n")
|
||||
|
||||
for para in self.detailed_desc:
|
||||
para.dump(f, desc_wrapper)
|
||||
|
||||
def dump_filled_text(f, wrapper, text):
|
||||
"""Fill given text using wrapper, and dump it to the given file
|
||||
appending an escaped CR at each end of line.
|
||||
"""
|
||||
filled_text = wrapper.fill(text) if wrapper is not None else text
|
||||
f.write("".join(re.sub("\n", r"\\n\\\n", filled_text)))
|
||||
|
||||
def camel_to_lowerscores(matchobj):
|
||||
oldtext = matchobj.group(0)
|
||||
newtext = re.sub(RE_SIMPLECAMEL, r"\1_\2", oldtext)
|
||||
newtext = re.sub(RE_CAMELAFTERUPPER, r"\1_\2\3", newtext)
|
||||
newtext = newtext.lower()
|
||||
camel_replacements[oldtext] = newtext
|
||||
return newtext.lower()
|
||||
|
||||
def cpp_to_python(text):
|
||||
text = text.replace("::", ".")
|
||||
text = text.replace('"', '\\"')
|
||||
|
||||
# convert camelCase to "_"-concatenated format
|
||||
# (e.g. getLength -> get_length)
|
||||
return re.sub(RE_CAMELTERM, camel_to_lowerscores, text)
|
||||
|
||||
def convert_type_name(type_name):
|
||||
"""Convert C++ type name to python type name using common conventions"""
|
||||
# strip off leading 'const' and trailing '&/*'
|
||||
type_name = re.sub("^const\S*", "", type_name)
|
||||
type_name = re.sub("\S*[&\*]$", "", type_name)
|
||||
|
||||
# We often typedef smart pointers as [Const]TypePtr. Convert them to
|
||||
# just "Type"
|
||||
type_name = re.sub("^Const", "", type_name)
|
||||
type_name = re.sub("Ptr$", "", type_name)
|
||||
|
||||
if type_name == "std::string":
|
||||
return "string"
|
||||
if re.search(r"(int\d+_t|size_t)", type_name):
|
||||
return "integer"
|
||||
return type_name
|
||||
|
||||
def get_text(root, do_convert=True):
|
||||
"""Recursively extract bare text inside the specified node (root),
|
||||
concatenate all extracted text and return the result.
|
||||
"""
|
||||
nodelist = root.childNodes
|
||||
rc = []
|
||||
for node in nodelist:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
if do_convert:
|
||||
rc.append(cpp_to_python(node.data))
|
||||
else:
|
||||
rc.append(node.data)
|
||||
elif node.nodeType == node.ELEMENT_NODE:
|
||||
rc.append(get_text(node))
|
||||
# return the result, removing any leading newlines (that often happens for
|
||||
# brief descriptions, which will cause lines not well aligned)
|
||||
return re.sub("^(\n*)", "", ''.join(rc))
|
||||
|
||||
def get_text_fromnodelist(nodelist, do_convert=True):
|
||||
"""Recursively extract bare text inside the specified node (root),
|
||||
concatenate all extracted text and return the result.
|
||||
"""
|
||||
rc = []
|
||||
for node in nodelist:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
if do_convert:
|
||||
rc.append(cpp_to_python(node.data))
|
||||
else:
|
||||
rc.append(node.data)
|
||||
elif node.nodeType == node.ELEMENT_NODE:
|
||||
rc.append(get_text(node))
|
||||
# return the result, removing any leading newlines (that often happens for
|
||||
# brief descriptions, which will cause lines not well aligned)
|
||||
return re.sub("^(\n*)", "", ''.join(rc))
|
||||
|
||||
def parse_parameters(nodelist):
|
||||
rc = []
|
||||
for node in nodelist:
|
||||
if node.nodeName != "parameteritem":
|
||||
continue
|
||||
# for simplicity, we assume one parametername and one
|
||||
# parameterdescription for each parameter.
|
||||
name = get_text(node.getElementsByTagName("parametername")[0])
|
||||
desc = get_text(node.getElementsByTagName("parameterdescription")[0])
|
||||
rc.append(NamedItem(name, desc))
|
||||
return rc
|
||||
|
||||
def parse_function_description(func_def, nodelist):
|
||||
for node in nodelist:
|
||||
# nodelist contains beginning and ending empty text nodes.
|
||||
# ignore them (otherwise they cause disruption below).
|
||||
if node.nodeName != "para":
|
||||
continue
|
||||
|
||||
if node.getElementsByTagName("parameterlist"):
|
||||
# within this node there may be exception list, parameter list,
|
||||
# and description for return value. parse and store them
|
||||
# seprately.
|
||||
for paramlist in node.getElementsByTagName("parameterlist"):
|
||||
if paramlist.getAttribute("kind") == "exception":
|
||||
func_def.exceptions = \
|
||||
parse_parameters(paramlist.childNodes)
|
||||
elif paramlist.getAttribute("kind") == "param":
|
||||
func_def.parameters = \
|
||||
parse_parameters(paramlist.childNodes)
|
||||
if node.getElementsByTagName("simplesect"):
|
||||
simplesect = node.getElementsByTagName("simplesect")[0]
|
||||
if simplesect.getAttribute("kind") == "return":
|
||||
func_def.returns = get_text(simplesect)
|
||||
else:
|
||||
# for normal text, python listing and itemized list, append them
|
||||
# to the list of paragraphs
|
||||
func_def.detailed_desc.append(Paragraph(node))
|
||||
|
||||
def parse_function(func_def, class_name, nodelist):
|
||||
for node in nodelist:
|
||||
if node.nodeName == "name":
|
||||
func_def.name = get_text(node, False)
|
||||
func_def.pyname = cpp_to_python(func_def.name)
|
||||
elif node.nodeName == "argsstring":
|
||||
# extract parameter names only, assuming they immediately follow
|
||||
# their type name + space, and are immeidatelly followed by
|
||||
# either "," or ")". If it's a pointer or reference, */& is
|
||||
# prepended to the parameter name without a space:
|
||||
# e.g. (int var1, char *var2, Foo &var3)
|
||||
args = get_text(node, False)
|
||||
# extract parameter names, possibly with */&
|
||||
func_def.args = ', '.join(re.findall(r"\s(\S+)[,)]", args))
|
||||
# then remove any */& symbols
|
||||
func_def.args = re.sub("[\*&]", "", func_def.args)
|
||||
elif node.nodeName == "type" and node.hasChildNodes():
|
||||
func_def.ret_type = convert_type_name(get_text(node, False))
|
||||
elif node.nodeName == "param":
|
||||
func_def.have_param = True
|
||||
elif node.nodeName == "briefdescription":
|
||||
func_def.brief_desc = get_text(node)
|
||||
elif node.nodeName == "detaileddescription":
|
||||
parse_function_description(func_def, node.childNodes)
|
||||
# identify the type of function using the name and arg
|
||||
if func_def.name == class_name and \
|
||||
re.search("^\(const " + class_name + " &[^,]*$", args):
|
||||
# This function is ClassName(const ClassName& param), which is
|
||||
# the copy constructor.
|
||||
func_def.type = func_def.COPY_CONSTRUCTOR
|
||||
elif func_def.name == class_name:
|
||||
# if it's not the copy ctor but the function name == class name,
|
||||
# it's a constructor.
|
||||
func_def.type = func_def.CONSTRUCTOR
|
||||
elif func_def.name == "~" + class_name:
|
||||
func_def.type = func_def.DESTRUCTOR
|
||||
elif func_def.name == "operator=":
|
||||
func_def.type = func_def.ASSIGNMENT_OP
|
||||
|
||||
# register the definition to the approriate list
|
||||
if func_def.type == func_def.CONSTRUCTOR:
|
||||
constructors.append(func_def)
|
||||
elif func_def.type == func_def.OTHER:
|
||||
member_functions.append(func_def)
|
||||
|
||||
def parse_functions(class_name, nodelist):
|
||||
for node in nodelist:
|
||||
if node.nodeName == "memberdef" and \
|
||||
node.getAttribute("kind") == "function":
|
||||
func_def = FunctionDefinition()
|
||||
parse_function(func_def, class_name, node.childNodes)
|
||||
|
||||
def parse_class_variables(class_name, nodelist):
|
||||
for node in nodelist:
|
||||
if node.nodeName == "memberdef" and \
|
||||
node.getAttribute("kind") == "variable":
|
||||
class_variables.append(VariableDefinition(node.childNodes))
|
||||
|
||||
def dump(f, class_name, class_brief_doc, class_detailed_doc):
|
||||
f.write("namespace {\n")
|
||||
|
||||
f.write('#ifdef COPY_THIS_TO_MAIN_CC\n')
|
||||
for func in member_functions:
|
||||
func.dump_pymethod_def(f, class_name)
|
||||
f.write('#endif // COPY_THIS_TO_MAIN_CC\n\n')
|
||||
|
||||
f.write("const char* const " + class_name + '_doc = "\\\n')
|
||||
if class_brief_doc is not None:
|
||||
f.write("".join(re.sub("\n", r"\\n\\\n", fill(class_brief_doc))))
|
||||
f.write("\\n\\\n")
|
||||
f.write("\\n\\\n")
|
||||
if len(class_detailed_doc) > 0:
|
||||
for para in class_detailed_doc:
|
||||
para.dump(f, wrapper=TextWrapper())
|
||||
|
||||
# dump constructors
|
||||
for func in constructors:
|
||||
indent = " " * 4
|
||||
func.dump_doc(f, wrapper=TextWrapper(initial_indent=indent,
|
||||
subsequent_indent=indent))
|
||||
|
||||
# dump class variables
|
||||
if len(class_variables) > 0:
|
||||
f.write("Class constant data:\\n\\\n")
|
||||
for var in class_variables:
|
||||
var.dump_doc(f)
|
||||
|
||||
f.write("\";\n")
|
||||
|
||||
for func in member_functions:
|
||||
f.write("\n")
|
||||
f.write("const char* const " + class_name + "_" + func.name + \
|
||||
"_doc = \"\\\n");
|
||||
func.dump_doc(f)
|
||||
f.write("\";\n")
|
||||
|
||||
f.write("} // unnamed namespace") # close namespace
|
||||
|
||||
if __name__ == '__main__':
|
||||
dom = parse(sys.argv[1])
|
||||
class_elements = dom.getElementsByTagName("compounddef")[0].childNodes
|
||||
class_brief_doc = None
|
||||
class_detailed_doc = []
|
||||
for node in class_elements:
|
||||
if node.nodeName == "compoundname":
|
||||
# class name is the last portion of the period-separated fully
|
||||
# qualified class name. (this should exist)
|
||||
class_name = re.split("\.", get_text(node))[-1]
|
||||
if node.nodeName == "briefdescription":
|
||||
# we assume a brief description consists at most one para
|
||||
class_brief_doc = get_text(node)
|
||||
elif node.nodeName == "detaileddescription":
|
||||
# a detaild description consists of one or more paragraphs
|
||||
for para in node.childNodes:
|
||||
if para.nodeName != "para": # ignore surrounding empty nodes
|
||||
continue
|
||||
class_detailed_doc.append(Paragraph(para))
|
||||
elif node.nodeName == "sectiondef" and \
|
||||
node.getAttribute("kind") == "public-func":
|
||||
parse_functions(class_name, node.childNodes)
|
||||
elif node.nodeName == "sectiondef" and \
|
||||
node.getAttribute("kind") == "public-static-attrib":
|
||||
parse_class_variables(class_name, node.childNodes)
|
||||
elif node.nodeName == "sectiondef" and \
|
||||
node.getAttribute("kind") == "user-defined":
|
||||
# there are two possiblities: functions and variables
|
||||
for child in node.childNodes:
|
||||
if child.nodeName != "memberdef":
|
||||
continue
|
||||
if child.getAttribute("kind") == "function":
|
||||
parse_function(FunctionDefinition(), class_name,
|
||||
child.childNodes)
|
||||
elif child.getAttribute("kind") == "variable":
|
||||
class_variables.append(VariableDefinition(child.childNodes))
|
||||
|
||||
dump(sys.stdout, class_name, class_brief_doc, class_detailed_doc)
|
||||
|
||||
if len(camel_replacements) > 0:
|
||||
sys.stderr.write("Replaced camelCased terms:\n")
|
||||
for oldterm in camel_replacements.keys():
|
||||
sys.stderr.write("%s => %s\n" % (oldterm,
|
||||
camel_replacements[oldterm]))
|
@@ -1,100 +0,0 @@
|
||||
#!@PYTHON@
|
||||
|
||||
# Copyright (C) 2011 Internet Systems Consortium.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
|
||||
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
||||
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
||||
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""This utility program generates a C++ header and implementation files
|
||||
that can be used as a template of C++ python binding for a C++ class.
|
||||
|
||||
Usage: ./mkpywrapper.py ClassName
|
||||
(the script should be run on this directory)
|
||||
|
||||
It will generate two files: classname_python.h and classname_python.cc,
|
||||
many of whose definitions are in the namespace isc::MODULE_NAME::python.
|
||||
By default MODULE_NAME will be 'dns' (because this tool is originally
|
||||
intended to be used for the C++ python binding of the DNS library), but
|
||||
can be changed via the -m command line option.
|
||||
|
||||
The generated files contain code fragments that are commonly used in
|
||||
C++ python binding implementations. It will define a class named
|
||||
s_ClassName which is a derived class of PyModule and can meet the
|
||||
requirement of the CPPPyObjectContainer template class (see
|
||||
pycppwrapper_util.h). It also defines (and declares in the header file)
|
||||
"classname_type", which is of PyTypeObject and is intended to be used
|
||||
to define details of the python bindings for the ClassName class.
|
||||
|
||||
In many cases the header file can be used as a startpoint of the
|
||||
binding development without modification. But you may want to make
|
||||
ClassName::cppobj a constant variable (and you should if you can).
|
||||
Many definitions of classname_python.cc should also be able to be used
|
||||
just as defined, but some will need to be changed or removed. In
|
||||
particular, you should at least adjust ClassName_init(). You'll
|
||||
probably also need to add more definitions to that file to provide
|
||||
complete features of the C++ class.
|
||||
"""
|
||||
|
||||
import datetime, string, sys
|
||||
from optparse import OptionParser
|
||||
|
||||
# Remember the current year to produce the copyright boilerplate
|
||||
YEAR = datetime.date.today().timetuple()[0]
|
||||
|
||||
def dump_file(out_file, temp_file, class_name, module):
|
||||
for line in temp_file.readlines():
|
||||
line = line.replace("@YEAR@", str(YEAR))
|
||||
line = line.replace("@CPPCLASS@_H", class_name.upper() + "_H")
|
||||
line = line.replace("@CPPCLASS@", class_name)
|
||||
line = line.replace("@cppclass@", class_name.lower())
|
||||
line = line.replace("@MODULE@", module)
|
||||
out_file.write(line)
|
||||
|
||||
def dump_wrappers(class_name, output, module):
|
||||
try:
|
||||
if output == "-":
|
||||
header_file = sys.stdout
|
||||
else:
|
||||
header_file = open(output + "_python.h", "w")
|
||||
header_template_file = open("wrapper_template.h", "r")
|
||||
if output == "-":
|
||||
impl_file = sys.stdout
|
||||
else:
|
||||
impl_file = open(output + "_python.cc", "w")
|
||||
impl_template_file = open("wrapper_template.cc", "r")
|
||||
except:
|
||||
sys.stderr.write('Failed to open C++ file(s)\n')
|
||||
sys.exit(1)
|
||||
dump_file(header_file, header_template_file, class_name, module)
|
||||
dump_file(impl_file, impl_template_file, class_name, module)
|
||||
|
||||
usage = '''usage: %prog [options] class_name'''
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.add_option('-o', '--output', action='store', dest='output',
|
||||
default=None, metavar='FILE',
|
||||
help='prefix of output file names [default: derived from the class name]')
|
||||
parser.add_option('-m', '--module', action='store', dest='module',
|
||||
default='dns',
|
||||
help='C++ module name of the wrapper (for namespaces) [default: dns]')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if len(args) == 0:
|
||||
parser.error('input file is missing')
|
||||
|
||||
class_name = args[0]
|
||||
if not options.output:
|
||||
options.output = class_name.lower()
|
||||
|
||||
dump_wrappers(class_name, options.output, options.module)
|
@@ -1,335 +0,0 @@
|
||||
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef PYCPPWRAPPER_UTIL_H
|
||||
#define PYCPPWRAPPER_UTIL_H 1
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
/**
|
||||
* @file pycppwrapper_util.h
|
||||
* @short Shared definitions for python/C(++) API
|
||||
*
|
||||
* This utility defines a set of convenient wrappers for the python C API
|
||||
* to use it safely from our C++ bindings. The python C API has many pitfalls
|
||||
* such as not-so-consistent reference count policies. Also, many existing
|
||||
* examples are careless about error handling. It's easy to find on the net
|
||||
* example (even of "production use") python extensions like this:
|
||||
*
|
||||
* \code
|
||||
* new_exception = PyErr_NewException("mymodule.Exception", NULL, NULL);
|
||||
* // new_exception can be NULL, in which case the call to
|
||||
* // PyModule_AddObject will cause a surprising disruption.
|
||||
* PyModule_AddObject(mymodule, "Exception", new_exception); \endcode
|
||||
*
|
||||
* When using the python C API with C++, we should also be careful about
|
||||
* exception safety. The underlying C++ code (including standard C++ libraries
|
||||
* and memory allocation) can throw exceptions, in which case we need to
|
||||
* make sure any intermediate python objects are cleaned up (we also need to
|
||||
* catch the C++ exceptions inside the binding and convert them to python
|
||||
* errors, but that's a different subject). This is not a trivial task
|
||||
* because the python objects are represented as bare C pointers (so there's
|
||||
* no destructor) and we need to address the exception safety along with python
|
||||
* reference counters (so we cannot naively apply standard smart pointers).
|
||||
*
|
||||
* This utility tries to help address these issues.
|
||||
*
|
||||
* Also, it's intentional that this is a header-only utility. This way the
|
||||
* C++ loadable module won't depend on another C++ library (which is not
|
||||
* necessarily wrong, but would increase management cost such as link-time
|
||||
* troubles only for a small utility feature).
|
||||
*/
|
||||
|
||||
namespace isc {
|
||||
namespace util {
|
||||
namespace python {
|
||||
|
||||
/// This is thrown inside this utility when it finds a NULL pointer is passed
|
||||
/// when it should not be NULL.
|
||||
class PyCPPWrapperException : public isc::Exception {
|
||||
public:
|
||||
PyCPPWrapperException(const char* file, size_t line, const char* what) :
|
||||
isc::Exception(file, line, what) {}
|
||||
};
|
||||
|
||||
/// This helper class is similar to the standard autoptr and manages PyObject
|
||||
/// using some kind of RAII techniques. It is, however, customized for the
|
||||
/// python C API.
|
||||
///
|
||||
/// A PyObjectContainer object is constructed with a pointer to PyObject,
|
||||
/// which is often just created dynamically. The caller will eventually
|
||||
/// attach the object to a different python object (often a module or class)
|
||||
/// via specific methods or directly return it to the python interpreter.
|
||||
///
|
||||
/// There are two cases in destructing the object: with or without decreasing
|
||||
/// a reference to the PyObject. If the object is intended to be an argument
|
||||
/// to another python C library that increases the reference to the object for
|
||||
/// itself, we should normally release our own reference; otherwise the
|
||||
/// reference will leak and the object won't be garbage collected. Also, when
|
||||
/// an unexpected error happens in the form of C++ exception, we should
|
||||
/// release the reference to prevent resource leak.
|
||||
///
|
||||
/// In some other cases, we should simply give our reference to the caller.
|
||||
/// That is the case when the created object itself is a return value of
|
||||
/// an extended python method written in the C++ binding. Likewise, some
|
||||
/// python C library functions "steal" the reference. In these cases we
|
||||
/// should not decrease the reference; otherwise it would cause duplicate free.
|
||||
///
|
||||
/// By default, the destructor of this class releases the reference to the
|
||||
/// PyObject. If this behavior is desirable, you can extract the original
|
||||
/// bare pointer to the PyObject by the \c get() method. If you don't want
|
||||
/// the reference to be decreased, the original bare pointer should be
|
||||
/// extracted using the \c release() method.
|
||||
///
|
||||
/// In some other cases, it would be convenient if it's possible to create
|
||||
/// an "empty" container and reset it with a Python object later.
|
||||
/// For example, we may want to create a temporary Python object in the
|
||||
/// middle of a function and make sure that it's valid within the rest of
|
||||
/// the function scope, while we want to make sure its reference is released
|
||||
/// when the function returns (either normally or as a result of exception).
|
||||
/// To allow this scenario, this class defines the default constructor
|
||||
/// and the \c reset() method. The default constructor allows the class
|
||||
/// object with an "empty" (NULL) Python object, while \c reset() allows
|
||||
/// the stored object to be replaced with a new one. If there's a valid
|
||||
/// object was already set, \c reset() releases its reference.
|
||||
/// In general, it's safer to construct the container object with a valid
|
||||
/// Python object pointer. The use of the default constructor and
|
||||
/// \c reset() should therefore be restricted to cases where it's
|
||||
/// absolutely necessary.
|
||||
///
|
||||
/// There are two convenience methods for commonly used operations:
|
||||
/// \c installAsClassVariable() to add the PyObject as a class variable
|
||||
/// and \c installToModule to add the PyObject to a specified python module.
|
||||
/// These methods (at least to some extent) take care of the reference to
|
||||
/// the object (either release or keep) depending on the usage context so
|
||||
/// that the user don't have to worry about it.
|
||||
///
|
||||
/// On construction, this class expects the pointer can be NULL.
|
||||
/// If it happens it immediately throws a \c PyCPPWrapperException exception.
|
||||
/// This behavior is to convert failures in the python C API (such as
|
||||
/// PyObject_New() returning NULL) to C++ exception so that we can unify
|
||||
/// error handling in the style of C++ exceptions.
|
||||
///
|
||||
/// Examples 1: To create a tuple of two python objects, do this:
|
||||
///
|
||||
/// \code
|
||||
/// try {
|
||||
/// PyObjectContainer container0(Py_BuildValue("I", 0));
|
||||
/// PyObjectContainer container1(Py_BuildValue("s", cppobj.toText().c_str()));
|
||||
/// return (Py_BuildValue("OO", container0.get(), container1.get()));
|
||||
/// } catch { ... set python exception, etc ... } \endcode
|
||||
///
|
||||
/// Commonly deployed buggy implementation to achieve this would be like this:
|
||||
/// \code
|
||||
/// return (Py_BuildValue("OO", Py_BuildValue("I", 0),
|
||||
/// Py_BuildValue("s", cppobj.toText().c_str())));
|
||||
/// \endcode
|
||||
/// One clear bug of this code is that references to the element objects of
|
||||
/// the tuple will leak.
|
||||
/// (Assuming \c cppobj.toText() can throw) this code is also not exception
|
||||
/// safe; if \c cppobj.toText() throws the reference to the first object
|
||||
/// will leak, even if the code tried to do the necessary cleanup in the
|
||||
/// successful case.
|
||||
/// Further, this code naively passes the result of the first two calls to
|
||||
/// \c Py_BuildValue() to the third one even if they can be NULL.
|
||||
/// In this specific case, it happens to be okay because \c Py_BuildValue()
|
||||
/// accepts NULL and treats it as an indication of error. But not all
|
||||
/// python C library works that way (remember, the API is so inconsistent)
|
||||
/// and we need to refer to the API manual every time we have to worry about
|
||||
/// passing a NULL object to a library function. We'd certainly like to
|
||||
/// avoid such development overhead. The code using \c PyObjectContainer
|
||||
/// addresses all these problems.
|
||||
///
|
||||
/// Examples 2: Install a (constant) variable to a class.
|
||||
///
|
||||
/// \code
|
||||
/// try {
|
||||
/// // installClassVariable is a wrapper of
|
||||
/// // PyObjectContainer::installAsClassVariable. See below.
|
||||
/// installClassVariable(myclass_type, "SOME_CONSTANT",
|
||||
/// Py_BuildValue("I", 0));
|
||||
/// } catch { ... }
|
||||
/// \endcode
|
||||
///
|
||||
/// Examples 3: Install a custom exception to a module.
|
||||
///
|
||||
/// \code
|
||||
/// PyObject* new_exception; // publicly visible
|
||||
/// ...
|
||||
/// try {
|
||||
/// new_exception = PyErr_NewException("mymodule.NewException",
|
||||
/// NULL, NULL);
|
||||
/// PyObjectContainer(new_exception).installToModule(mymodule,
|
||||
/// "NewException");
|
||||
/// } catch { ... }
|
||||
/// \endcode
|
||||
///
|
||||
/// Note that \c installToModule() keeps the reference to \c new_exception
|
||||
/// by default. This is a common practice when we introduce a custom
|
||||
/// exception in a python biding written in C/C++. See the code comment
|
||||
/// of the method for more details.
|
||||
struct PyObjectContainer {
|
||||
PyObjectContainer() : obj_(NULL) {}
|
||||
PyObjectContainer(PyObject* obj) : obj_(obj) {
|
||||
if (obj_ == NULL) {
|
||||
isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, "
|
||||
"probably due to short memory");
|
||||
}
|
||||
}
|
||||
~PyObjectContainer() {
|
||||
if (obj_ != NULL) {
|
||||
Py_DECREF(obj_);
|
||||
}
|
||||
}
|
||||
void reset(PyObject* obj) {
|
||||
if (obj == NULL) {
|
||||
isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, "
|
||||
"probably due to short memory");
|
||||
}
|
||||
if (obj_ != NULL) {
|
||||
Py_DECREF(obj_);
|
||||
}
|
||||
obj_ = obj;
|
||||
}
|
||||
PyObject* get() {
|
||||
return (obj_);
|
||||
}
|
||||
PyObject* release() {
|
||||
PyObject* ret = obj_;
|
||||
obj_ = NULL;
|
||||
return (ret);
|
||||
}
|
||||
|
||||
// Install the enclosed PyObject to the specified python class 'pyclass'
|
||||
// as a variable named 'name'.
|
||||
void installAsClassVariable(PyTypeObject& pyclass, const char* name) {
|
||||
if (PyDict_SetItemString(pyclass.tp_dict, name, obj_) < 0) {
|
||||
isc_throw(PyCPPWrapperException, "Failed to set a class variable, "
|
||||
"probably due to short memory");
|
||||
}
|
||||
// Ownership successfully transferred to the class object. We'll let
|
||||
// it be released in the destructor.
|
||||
}
|
||||
|
||||
// Install the enclosed PyObject to the specified module 'mod' as an
|
||||
// object named 'name'.
|
||||
// By default, this method explicitly keeps the reference to the object
|
||||
// even after the module "steals" it. To cancel this behavior and give
|
||||
// the reference to the module completely, the third parameter 'keep_ref'
|
||||
// should be set to false.
|
||||
void installToModule(PyObject* mod, const char* name,
|
||||
bool keep_ref = true)
|
||||
{
|
||||
if (PyModule_AddObject(mod, name, obj_) < 0) {
|
||||
isc_throw(PyCPPWrapperException, "Failed to add an object to "
|
||||
"module, probably due to short memory");
|
||||
}
|
||||
// PyModule_AddObject has "stolen" the reference, so unless we
|
||||
// have to retain it ourselves we don't (shouldn't) decrease it.
|
||||
// However, we actually often need to keep our own reference because
|
||||
// objects added to a module are often referenced via non local
|
||||
// C/C++ variables in various places of the C/C++ code. In order
|
||||
// for the code to run safely even if some buggy/evil python program
|
||||
// performs 'del mod.obj', we need the extra reference. See, e.g.:
|
||||
// http://docs.python.org/py3k/c-api/init.html#Py_Initialize
|
||||
// http://mail.python.org/pipermail/python-dev/2005-June/054238.html
|
||||
if (keep_ref) {
|
||||
Py_INCREF(obj_);
|
||||
}
|
||||
obj_ = NULL;
|
||||
}
|
||||
|
||||
protected:
|
||||
PyObject* obj_;
|
||||
};
|
||||
|
||||
/// This templated class is a derived class of \c PyObjectContainer and
|
||||
/// manages C++-class based python objects.
|
||||
///
|
||||
/// The template parameter \c PYSTRUCT must be a derived class (structure) of
|
||||
/// \c PyObject that has a member variable named \c cppobj, which must be a
|
||||
/// a pointer to \c CPPCLASS (the second template parameter).
|
||||
///
|
||||
/// For example, to define a custom python class based on a C++ class, MyClass,
|
||||
/// we'd define a class (struct) named \c s_MyClass like this:
|
||||
/// \code
|
||||
/// class s_MyClass : public PyObject {
|
||||
/// public:
|
||||
/// s_MyClass() : cppobj(NULL) {}
|
||||
/// MyClass* cppobj;
|
||||
/// };
|
||||
/// \endcode
|
||||
///
|
||||
/// And, to build and return a python version of MyClass object, write the
|
||||
/// following C++ code:
|
||||
/// \code
|
||||
/// typedef CPPPyObjectContainer<s_MyClass, MyClass> MyContainer;
|
||||
/// try {
|
||||
/// // below, myclass_type is of \c PyTypeObject that defines
|
||||
/// // a python class (type) for MyClass
|
||||
/// MyContainer container(PyObject_New(s_MyClass, myclass_type));
|
||||
/// container.set(new MyClass());
|
||||
/// return (container.release()); // give the reference to the caller
|
||||
/// } catch { ... }
|
||||
/// \endcode
|
||||
///
|
||||
/// This code prevents bugs like NULL pointer dereference when \c PyObject_New
|
||||
/// fails or resource leaks when new'ing \c MyClass results in an exception.
|
||||
/// Note that we use \c release() (derived from the base class) instead of
|
||||
/// \c get(); in this case we should simply pass the reference generated in
|
||||
/// \c PyObject_New() to the caller.
|
||||
template <typename PYSTRUCT, typename CPPCLASS>
|
||||
struct CPPPyObjectContainer : public PyObjectContainer {
|
||||
explicit CPPPyObjectContainer(PYSTRUCT* obj) : PyObjectContainer(obj) {}
|
||||
|
||||
// This method associates a C++ object with the corresponding python
|
||||
// object enclosed in this class.
|
||||
void set(CPPCLASS* value) {
|
||||
if (value == NULL) {
|
||||
isc_throw(PyCPPWrapperException, "Unexpected NULL C++ object, "
|
||||
"probably due to short memory");
|
||||
}
|
||||
static_cast<PYSTRUCT*>(obj_)->cppobj = value;
|
||||
}
|
||||
|
||||
// This is a convenience short cut to associate a C++ object with the
|
||||
// python object and install it to the specified python class \c pyclass
|
||||
// as a variable named \c name.
|
||||
void installAsClassVariable(PyTypeObject& pyclass, const char* name,
|
||||
CPPCLASS* value)
|
||||
{
|
||||
set(value);
|
||||
PyObjectContainer::installAsClassVariable(pyclass, name);
|
||||
}
|
||||
};
|
||||
|
||||
/// A shortcut function to install a python class variable.
|
||||
///
|
||||
/// It installs a python object \c obj to a specified class \c pyclass
|
||||
/// as a variable named \c name.
|
||||
inline void
|
||||
installClassVariable(PyTypeObject& pyclass, const char* name, PyObject* obj) {
|
||||
PyObjectContainer(obj).installAsClassVariable(pyclass, name);
|
||||
}
|
||||
|
||||
} // namespace python
|
||||
} // namespace util
|
||||
} // namespace isc
|
||||
#endif // PYCPPWRAPPER_UTIL_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
@@ -1,57 +0,0 @@
|
||||
# Copyright (C) 2013 Internet Systems Consortium.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
|
||||
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
||||
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
||||
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
This script takes a C++ file with constants and converts it to a python
|
||||
module. However, the syntax it parses is very limited (it doesn't understand
|
||||
C++ at all, it just looks for lines containing the equal sign and strips
|
||||
what it thinks might be type).
|
||||
|
||||
The purpose is to keep the same values of constants in C++ and python. This
|
||||
saves the work of keeping the constants in sync manually and is less error
|
||||
prone.
|
||||
'''
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
sys.stderr.write("Usage: python3 ./pythonize_constants.py input.cc output.py\n")
|
||||
sys.exit(1)
|
||||
|
||||
[filename_in, filename_out] = sys.argv[1:3]
|
||||
|
||||
# Ignore preprocessor, namespaces and the ends of namespaces.
|
||||
ignore = re.compile('^(#|namespace|})')
|
||||
comment = re.compile('^//(.*)')
|
||||
constant = re.compile('^[a-zA-Z].*?([a-zA-Z_0-9]+\\s*=.*);')
|
||||
|
||||
with open(filename_in) as file_in, open(filename_out, "w") as file_out:
|
||||
file_out.write("# This file is generated from " + filename_in + "\n" +
|
||||
"# by the pythonize_constants.py script.\n" +
|
||||
"# Do not edit, all changes will be lost.\n\n")
|
||||
for line in file_in:
|
||||
if ignore.match(line):
|
||||
continue
|
||||
# Mangle comments to be python-like
|
||||
line = comment.sub('#\\1', line)
|
||||
# Extract the constant.
|
||||
|
||||
# TODO: We may want to do something with the true vs. True and
|
||||
# NULL vs. None and such. Left out for now, since none are in
|
||||
# the input file currently.
|
||||
line = constant.sub('\\1', line)
|
||||
|
||||
file_out.write(line)
|
@@ -1,291 +0,0 @@
|
||||
// Copyright (C) @YEAR@ Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// Enable this if you use s# variants with PyArg_ParseTuple(), see
|
||||
// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
|
||||
//#define PY_SSIZE_T_CLEAN
|
||||
|
||||
// Python.h needs to be placed at the head of the program file, see:
|
||||
// http://docs.python.org/py3k/extending/extending.html#a-simple-example
|
||||
#include <Python.h>
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <util/python/pycppwrapper_util.h>
|
||||
|
||||
#include "@cppclass@_python.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace isc::util::python;
|
||||
using namespace isc::@MODULE@;
|
||||
using namespace isc::@MODULE@::python;
|
||||
|
||||
//
|
||||
// @CPPCLASS@
|
||||
//
|
||||
|
||||
// Trivial constructor.
|
||||
s_@CPPCLASS@::s_@CPPCLASS@() : cppobj(NULL) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Shortcut type which would be convenient for adding class variables safely.
|
||||
typedef CPPPyObjectContainer<s_@CPPCLASS@, @CPPCLASS@> @CPPCLASS@Container;
|
||||
|
||||
@REMOVE_THIS_ON_RELEASE@
|
||||
// This is a template of typical code logic of python class initialization
|
||||
// with C++ backend. You'll need to adjust it according to details of the
|
||||
// actual C++ class.
|
||||
int
|
||||
@CPPCLASS@_init(PyObject* po_self, PyObject* args, PyObject*) {
|
||||
s_@CPPCLASS@* self = static_cast<s_@CPPCLASS@*>(po_self);
|
||||
try {
|
||||
if (PyArg_ParseTuple(args, "REPLACE ME")) {
|
||||
@REMOVE_THIS_ON_RELEASE@
|
||||
// YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
|
||||
self->cppobj = new @CPPCLASS@(/*NECESSARY PARAMS*/);
|
||||
return (0);
|
||||
}
|
||||
} catch (const exception& ex) {
|
||||
const string ex_what = "Failed to construct @CPPCLASS@ object: " +
|
||||
string(ex.what());
|
||||
PyErr_SetString(po_IscException, ex_what.c_str());
|
||||
return (-1);
|
||||
} catch (...) {
|
||||
PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
@REMOVE_THIS_ON_RELEASE@
|
||||
// If we are here PyArg_ParseTuple() failed and TypeError should have
|
||||
// been set. If the constructor is more complicated and the control
|
||||
// could reach this point for other reasons, an appropriate Python
|
||||
// exception should be set by PyErr_SetString.
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
@REMOVE_THIS_ON_RELEASE@
|
||||
// This is a template of typical code logic of python object destructor.
|
||||
// In many cases you can use it without modification, but check that carefully.
|
||||
void
|
||||
@CPPCLASS@_destroy(PyObject* po_self) {
|
||||
s_@CPPCLASS@* self = static_cast<s_@CPPCLASS@*>(po_self);
|
||||
delete self->cppobj;
|
||||
self->cppobj = NULL;
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
@REMOVE_THIS_ON_RELEASE@
|
||||
// This should be able to be used without modification as long as the
|
||||
// underlying C++ class has toText().
|
||||
PyObject*
|
||||
@CPPCLASS@_toText(PyObject* po_self) {
|
||||
const s_@CPPCLASS@* self = static_cast<const s_@CPPCLASS@*>(po_self);
|
||||
try {
|
||||
// toText() could throw, so we need to catch any exceptions below.
|
||||
return (Py_BuildValue("s", self->cppobj->toText().c_str()));
|
||||
} catch (const exception& ex) {
|
||||
const string ex_what =
|
||||
"Failed to convert @CPPCLASS@ object to text: " +
|
||||
string(ex.what());
|
||||
PyErr_SetString(po_IscException, ex_what.c_str());
|
||||
} catch (...) {
|
||||
PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
|
||||
"converting @CPPCLASS@ object to text");
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
@CPPCLASS@_str(PyObject* self) {
|
||||
// Simply call the to_text method we already defined
|
||||
return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
|
||||
const_cast<char*>("")));
|
||||
}
|
||||
|
||||
@REMOVE_THIS_ON_RELEASE@
|
||||
// This is quite specific isc.dns. For other wrappers this should probably
|
||||
// be removed.
|
||||
PyObject* @CPPCLASS@_toWire(PyObject* self, PyObject* args) {
|
||||
}
|
||||
|
||||
PyObject*
|
||||
@CPPCLASS@_richcmp(PyObject* po_self, PyObject* po_other, const int op) {
|
||||
const s_@CPPCLASS@* const self = static_cast<const s_@CPPCLASS@*>(po_self);
|
||||
const s_@CPPCLASS@* const other =
|
||||
static_cast<const s_@CPPCLASS@*>(po_other);
|
||||
|
||||
bool c = false;
|
||||
|
||||
// Check for null and if the types match. If different type,
|
||||
// simply return False
|
||||
if (other == NULL || (self->ob_type != other->ob_type)) {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
// Only equals and not equals here, unorderable type
|
||||
switch (op) {
|
||||
case Py_LT:
|
||||
PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
|
||||
return (NULL);
|
||||
case Py_LE:
|
||||
PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
|
||||
return (NULL);
|
||||
case Py_EQ:
|
||||
c = (*self->cppobj == *other->cppobj);
|
||||
break;
|
||||
case Py_NE:
|
||||
c = (*self->cppobj != *other->cppobj);
|
||||
break;
|
||||
case Py_GT:
|
||||
PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
|
||||
return (NULL);
|
||||
case Py_GE:
|
||||
PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
|
||||
return (NULL);
|
||||
}
|
||||
if (c) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// This list contains the actual set of functions we have in
|
||||
// python. Each entry has
|
||||
// 1. Python method name
|
||||
// 2. Our static function here
|
||||
// 3. Argument type
|
||||
// 4. Documentation
|
||||
PyMethodDef @CPPCLASS@_methods[] = {
|
||||
{ "to_text", @CPPCLASS@_toText, METH_NOARGS,
|
||||
@CPPCLASS@_toText_doc },
|
||||
|
||||
@REMOVE_THIS_ON_RELEASE@
|
||||
// This is quite specific isc.dns. For other wrappers this should probably
|
||||
// be removed:
|
||||
{ "to_wire", @CPPCLASS@_toWire, METH_VARARGS,
|
||||
@CPPCLASS@_toWire_doc },
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
} // end of unnamed namespace
|
||||
|
||||
namespace isc {
|
||||
namespace @MODULE@ {
|
||||
namespace python {
|
||||
// This defines the complete type for reflection in python and
|
||||
// parsing of PyObject* to s_@CPPCLASS@
|
||||
// Most of the functions are not actually implemented and NULL here.
|
||||
PyTypeObject @cppclass@_type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"@MODULE@.@CPPCLASS@",
|
||||
sizeof(s_@CPPCLASS@), // tp_basicsize
|
||||
0, // tp_itemsize
|
||||
@CPPCLASS@_destroy, // tp_dealloc
|
||||
NULL, // tp_print
|
||||
NULL, // tp_getattr
|
||||
NULL, // tp_setattr
|
||||
NULL, // tp_reserved
|
||||
NULL, // tp_repr
|
||||
NULL, // tp_as_number
|
||||
NULL, // tp_as_sequence
|
||||
NULL, // tp_as_mapping
|
||||
NULL, // tp_hash
|
||||
NULL, // tp_call
|
||||
// THIS MAY HAVE TO BE CHANGED TO NULL:
|
||||
@CPPCLASS@_str, // tp_str
|
||||
NULL, // tp_getattro
|
||||
NULL, // tp_setattro
|
||||
NULL, // tp_as_buffer
|
||||
Py_TPFLAGS_DEFAULT, // tp_flags
|
||||
@CPPCLASS@_doc,
|
||||
NULL, // tp_traverse
|
||||
NULL, // tp_clear
|
||||
// THIS MAY HAVE TO BE CHANGED TO NULL:
|
||||
@CPPCLASS@_richcmp, // tp_richcompare
|
||||
0, // tp_weaklistoffset
|
||||
NULL, // tp_iter
|
||||
NULL, // tp_iternext
|
||||
@CPPCLASS@_methods, // tp_methods
|
||||
NULL, // tp_members
|
||||
NULL, // tp_getset
|
||||
NULL, // tp_base
|
||||
NULL, // tp_dict
|
||||
NULL, // tp_descr_get
|
||||
NULL, // tp_descr_set
|
||||
0, // tp_dictoffset
|
||||
@CPPCLASS@_init, // tp_init
|
||||
NULL, // tp_alloc
|
||||
PyType_GenericNew, // tp_new
|
||||
NULL, // tp_free
|
||||
NULL, // tp_is_gc
|
||||
NULL, // tp_bases
|
||||
NULL, // tp_mro
|
||||
NULL, // tp_cache
|
||||
NULL, // tp_subclasses
|
||||
NULL, // tp_weaklist
|
||||
NULL, // tp_del
|
||||
0 // tp_version_tag
|
||||
};
|
||||
|
||||
// Module Initialization, all statics are initialized here
|
||||
bool
|
||||
initModulePart_@CPPCLASS@(PyObject* mod) {
|
||||
// We initialize the static description object with PyType_Ready(),
|
||||
// then add it to the module. This is not just a check! (leaving
|
||||
// this out results in segmentation faults)
|
||||
if (PyType_Ready(&@cppclass@_type) < 0) {
|
||||
return (false);
|
||||
}
|
||||
void* p = &@cppclass@_type;
|
||||
if (PyModule_AddObject(mod, "@CPPCLASS@", static_cast<PyObject*>(p)) < 0) {
|
||||
return (false);
|
||||
}
|
||||
Py_INCREF(&@cppclass@_type);
|
||||
|
||||
@REMOVE_THIS_ON_RELEASE@
|
||||
// The following template is the typical procedure for installing class
|
||||
// variables. If the class doesn't have a class variable, remove the
|
||||
// entire try-catch clauses.
|
||||
try {
|
||||
// Constant class variables
|
||||
installClassVariable(@cppclass@_type, "REPLACE_ME",
|
||||
Py_BuildValue("REPLACE ME"));
|
||||
} catch (const exception& ex) {
|
||||
const string ex_what =
|
||||
"Unexpected failure in @CPPCLASS@ initialization: " +
|
||||
string(ex.what());
|
||||
PyErr_SetString(po_IscException, ex_what.c_str());
|
||||
return (false);
|
||||
} catch (...) {
|
||||
PyErr_SetString(PyExc_SystemError,
|
||||
"Unexpected failure in @CPPCLASS@ initialization");
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
create@CPPCLASS@Object(const @CPPCLASS@& source) {
|
||||
@CPPCLASS@Container container(PyObject_New(s_@CPPCLASS@,
|
||||
&@cppclass@_type));
|
||||
container.set(new @CPPCLASS@(source));
|
||||
return (container.release());
|
||||
}
|
||||
} // namespace python
|
||||
} // namespace @MODULE@
|
||||
} // namespace isc
|
@@ -1,59 +0,0 @@
|
||||
// Copyright (C) @YEAR@ Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef PYTHON_@CPPCLASS@_H
|
||||
#define PYTHON_@CPPCLASS@_H 1
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
namespace isc {
|
||||
namespace @MODULE@ {
|
||||
class @CPPCLASS@;
|
||||
|
||||
namespace python {
|
||||
|
||||
// The s_* Class simply covers one instantiation of the object
|
||||
class s_@CPPCLASS@ : public PyObject {
|
||||
public:
|
||||
s_@CPPCLASS@();
|
||||
@CPPCLASS@* cppobj;
|
||||
};
|
||||
|
||||
extern PyTypeObject @cppclass@_type;
|
||||
|
||||
bool initModulePart_@CPPCLASS@(PyObject* mod);
|
||||
|
||||
// Note: this utility function works only when @CPPCLASS@ is a copy
|
||||
// constructable.
|
||||
// And, it would only be useful when python binding needs to create this
|
||||
// object frequently. Otherwise, it would (or should) probably be better to
|
||||
// remove the declaration and definition of this function.
|
||||
//
|
||||
/// This is a simple shortcut to create a python @CPPCLASS@ object (in the
|
||||
/// form of a pointer to PyObject) with minimal exception safety.
|
||||
/// On success, it returns a valid pointer to PyObject with a reference
|
||||
/// counter of 1; if something goes wrong it throws an exception (it never
|
||||
/// returns a NULL pointer).
|
||||
/// This function is expected to be called within a try block
|
||||
/// followed by necessary setup for python exception.
|
||||
PyObject* create@CPPCLASS@Object(const @CPPCLASS@& source);
|
||||
|
||||
} // namespace python
|
||||
} // namespace @MODULE@
|
||||
} // namespace isc
|
||||
#endif // PYTHON_@CPPCLASS@_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
@@ -1,22 +0,0 @@
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
|
||||
AM_CPPFLAGS += $(BOOST_INCLUDES)
|
||||
AM_CXXFLAGS = $(B10_CXXFLAGS)
|
||||
|
||||
noinst_LTLIBRARIES = pyunittests_util.la
|
||||
|
||||
pyunittests_util_la_SOURCES = pyunittests_util.cc
|
||||
pyunittests_util_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
|
||||
pyunittests_util_la_LDFLAGS = $(PYTHON_LDFLAGS)
|
||||
# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
|
||||
# placed after -Wextra defined in AM_CXXFLAGS
|
||||
pyunittests_util_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
|
||||
|
||||
# Python prefers .so, while some OSes (specifically MacOS) use a different
|
||||
# suffix for dynamic objects. -module is necessary to work this around.
|
||||
pyunittests_util_la_LDFLAGS += -module -avoid-version
|
||||
pyunittests_util_la_LIBADD = $(top_builddir)/src/lib/util/libkea-util.la
|
||||
pyunittests_util_la_LIBADD += $(PYTHON_LIB)
|
||||
|
||||
# hack to trigger libtool to not create a convenience archive,
|
||||
# resulting in shared modules
|
||||
pyunittests_util_la_LDFLAGS += -rpath /nowhere
|
@@ -1,84 +0,0 @@
|
||||
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// see util/time_utilities.h
|
||||
namespace isc {
|
||||
namespace util {
|
||||
namespace detail {
|
||||
extern int64_t (*gettimeFunction)();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
int64_t fake_current_time;
|
||||
|
||||
int64_t
|
||||
getFakeTime() {
|
||||
return (fake_current_time);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
fixCurrentTime(PyObject*, PyObject* args) {
|
||||
PyObject* maybe_none;
|
||||
if (PyArg_ParseTuple(args, "L", &fake_current_time)) {
|
||||
isc::util::detail::gettimeFunction = getFakeTime;
|
||||
} else if (PyArg_ParseTuple(args, "O", &maybe_none) &&
|
||||
maybe_none == Py_None) {
|
||||
isc::util::detail::gettimeFunction = NULL;
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid arguments to "
|
||||
"pyunittests_util.fix_current_time");
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
PyErr_Clear();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyMethodDef PyUnittestsUtilMethods[] = {
|
||||
{ "fix_current_time", fixCurrentTime, METH_VARARGS,
|
||||
"Fix the current system time at the specified (fake) value.\n\n"
|
||||
"This is useful for testing modules that depend on the current time.\n"
|
||||
"Note that it only affects C++ modules that use gettimeWrapper() "
|
||||
"defined in libutil, which allows a hook for testing.\n"
|
||||
"If an integer (signed 64bit) is given, the current time will be fixed "
|
||||
"to that value; if None is specified (which is the default) the use of "
|
||||
"faked time will be canceled."
|
||||
},
|
||||
{ NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
PyModuleDef pyunittests_util = {
|
||||
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
|
||||
"pyunittests_util",
|
||||
"This module is a collection of utilities useful for testing "
|
||||
"the BIND 10 C++ binding modules.",
|
||||
-1,
|
||||
PyUnittestsUtilMethods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
} // end of unnamed namespace
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit_pyunittests_util(void) {
|
||||
return (PyModule_Create(&pyunittests_util));
|
||||
}
|
Reference in New Issue
Block a user