2
0
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:
Tomek Mrugalski
2014-06-03 16:34:54 +02:00
parent 53aec1df97
commit a288439190
16 changed files with 81 additions and 1908 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 $@ $<

View File

@@ -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

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -1,3 +0,0 @@
/doxygen2pydoc.py
/gen_wiredata.py
/mkpywrapper.py

View File

@@ -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

View File

@@ -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]))

View File

@@ -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)

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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));
}