Commit 070bb499 authored by T.J. Corona's avatar T.J. Corona

Add pybind11 thirdparty submodule; add pybind generator scripts + doc

parent a89b8c3d
[submodule "thirdparty/pybind11"]
path = thirdparty/pybind11
url = https://github.com/pybind/pybind11.git
......@@ -83,7 +83,12 @@ option(SMTK_NO_SYSTEM_BOOST "Allow boost to search for system installed boost" O
# Option to build Qt ui compoments for attributes
option(SMTK_ENABLE_TESTING "Enable Testing" ON)
option(SMTK_ENABLE_EXAMPLES "Enable Examples" OFF)
option(SMTK_ENABLE_PYTHON_WRAPPING "Build Python Wrappings using Shiboken" OFF)
option(SMTK_ENABLE_PYTHON_WRAPPING "Build Python Wrappings" OFF)
cmake_dependent_option(
SMTK_USE_PYBIND11
"Build Python Wrappings using Pybind11 instead of Shiboken"
OFF
SMTK_ENABLE_PYTHON_WRAPPING OFF)
# Provide system packagers with the ability to install SMTK
# to the system's Python site package directory. The default
# is off so that people building relocatable bundles (such as
......@@ -278,25 +283,41 @@ else()
"${SMTK_BINARY_DIR}/thirdparty")
endif()
################################################################################
# Build third party libraries
################################################################################
add_subdirectory(thirdparty)
################################################################################
# Wrapping Related Settings
################################################################################
if(SMTK_ENABLE_PYTHON_WRAPPING)
include(UseShiboken)
if(SMTK_USE_PYBIND11)
find_package(PythonLibs REQUIRED)
else()
include(UseShiboken)
endif()
find_package(PythonInterp 2.7 REQUIRED)
# Set up environment variables needed to import the python modules for tests.
if (PYTHONINTERP_FOUND AND SMTK_ENABLE_TESTING)
set(required_python_modules
shiboken)
set(shiboken_PYTHONPATH
"${SHIBOKEN_PYTHONPATH}")
if (WIN32)
get_filename_component(shiboken_runtime_dir "${SHIBOKEN_BINARY}" PATH)
else ()
get_filename_component(shiboken_runtime_dir "${SHIBOKEN_LIBRARY}" PATH)
endif ()
set(shiboken_libpath
"${shiboken_runtime_dir}")
set(required_python_modules)
if (NOT SMTK_USE_PYBIND11)
list(APPEND required_python_modules
shiboken)
set(shiboken_PYTHONPATH
"${SHIBOKEN_PYTHONPATH}")
if (WIN32)
get_filename_component(shiboken_runtime_dir "${SHIBOKEN_BINARY}" PATH)
else ()
get_filename_component(shiboken_runtime_dir "${SHIBOKEN_LIBRARY}" PATH)
endif ()
set(shiboken_libpath
"${shiboken_runtime_dir}")
endif()
if (SMTK_ENABLE_PARAVIEW_SUPPORT)
list(APPEND required_python_modules
......@@ -395,7 +416,7 @@ if(SMTK_ENABLE_PYTHON_WRAPPING)
# Add the path to the build tree's compiled modules.
list(APPEND smtk_pythonpaths
"${CMAKE_BINARY_DIR}")
"${CMAKE_BINARY_DIR}${envsep}${CMAKE_BINARY_DIR}/lib")
if (CMAKE_RUNTIME_OUTPUT_DIRECTORY)
list(APPEND smtk_libpaths
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
......@@ -413,7 +434,7 @@ if(SMTK_ENABLE_PYTHON_WRAPPING)
string(REPLACE ";" "\;" smtk_pythonpath_env "${smtk_pythonpath_env}")
string(REPLACE ";" "\;" smtk_libpath_env "${smtk_libpath_env}")
set(smtk_python_test_environment
"PYTHONPATH=${smtk_pythonpath_env}"
"PYTHONPATH=${smtk_pythonpath_env}:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
"${libpath_envvar}=${smtk_libpath_env}")
function (smtk_add_test_python name file)
......@@ -499,12 +520,6 @@ install(
${CMAKE_INSTALL_LIBDIR}/cmake/SMTK
)
################################################################################
# Build third party libraries
################################################################################
add_subdirectory(thirdparty)
################################################################################
# Include Dirs Settings
################################################################################
......
.. _generating-pybind11-bindings:
Generating pybind11 bindings
=====================
SMTK's pybind11_ bindings are generated via python scripts that use
pygccxml_ to construct a C++ header file corresponding to each header
file in SMTK, and a C++ source file for each namespace in
SMTK. Generally, python modules are a reflection of C++ namespaces
(which are generally contained within a subdirectory). The generated
pybind11 C++ files for each module are in the ``pybind`` subdirectory of
the module.
To generate a C++ header file for a new SMTK class, use
``[smtk-root-directory]/utilities/python/cpp_to_pybind11.py`` with the
appropriate arguments for the header file, project root directory,
include directories and generated file prefix; the module's binding
source file (also located in in the ``pybind`` subdirectory of the
module) must then be updated to call the functions defined in the
generated header file. To generate all of the C++ headers and the module C++
source file for a namespace at once, use
``[smtk-root-directory]/utilities/python/generate_pybind11_module.py``
with the appropriate arguments for the module directory, project root
directory and include directories.
The generated bindings should be treated as a starting point for
customization to create a more *pythonic* interface.
.. _pybind11: http://pybind11.readthedocs.io
.. _pygccxml: http://pygccxml.readthedocs.io
.. _smtk-bindings:
******************
SMTK's Bindings
******************
SMTK is written in C++ and uses pybind11_ to generate python bindings.
.. toctree::
:maxdepth: 3
generating-pybind11-bindings.rst
.. _pybind11: http://pybind11.readthedocs.io
......@@ -56,6 +56,7 @@ prepare and submit changes to SMTK.
attribute/index.rst
model/index.rst
mesh/index.rst
bindings/index.rst
administration.rst
contributing.rst
......
add_subdirectory(cJSON)
if(SMTK_USE_PYBIND11)
add_subdirectory(pybind11)
endif()
if (NOT SMTK_USE_SYSTEM_MOAB)
#guard against MOAB polluting the cmake C/CXX/Fortran flags
set(save_CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
......
Subproject commit 2b92a49115657712c7dd924248df2286e6143b8d
This diff is collapsed.
#=========================================================================
# Copyright (c) Kitware, Inc.
# All rights reserved.
# See LICENSE.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even
# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the above copyright notice for more information.
#=========================================================================
""" generate_pybind11_module.py:
Reads a directory containing C++ header files and creates the necessary C++
files to define the pybind11 wrappings for a python module.
"""
import os
import sys
from cpp_to_pybind11 import *
from toposort import toposort_flatten
if __name__ == '__main__':
import argparse
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-I','--include-dirs',
help='Add an include directory to the parser',
default="")
arg_parser.add_argument('-i','--input-directory',
help='<Required> Input directory',
required=True)
arg_parser.add_argument('-m','--module',
help='<Required> Module name',
required=True)
arg_parser.add_argument('-o','--output-directory',
help='Output directory',
required=True)
arg_parser.add_argument('-p','--prefix',
help='File name prefix',
default='Pybind')
arg_parser.add_argument('-s','--project-source-dir',
help='Project source directory',
default=".")
arg_parser.add_argument('-v','--verbose',
help='Print out generated wrapping code',
action='store_true')
args = arg_parser.parse_args()
if not os.path.exists(args.input_directory):
raise IOError("No directory \"%s\"" % args.input_directory)
if not args.include_dirs:
args.include_dirs = args.input_directory
def stream_with_line_breaks(stream):
def write(string):
stream.write(string.replace('>>','> >'))
stream.write('\n')
def write_verbose(string):
write(string)
print string.replace('>>','> >')
if args.verbose:
return write_verbose
else:
return write
wrapped_objects = {}
header_files = [os.path.join(args.input_directory, f) \
for f in os.listdir(args.input_directory) \
if os.path.splitext(f)[1] == ".h"]
output_files = []
for header_file in header_files:
output_file = os.path.join(args.output_directory,
args.prefix + os.path.basename(header_file))
output_files.append(output_file)
if not os.path.exists(os.path.dirname(output_file)):
try:
os.makedirs(os.path.dirname(output_file))
except OSError as exc: # Guard against race condition
if exc.errno != errno.EEXIST:
raise
with open(output_file, 'w') as f:
stream = stream_with_line_breaks(f)
wrapped_objects.update(
parse_file(header_file, args.project_source_dir,
args.include_dirs, "", stream))
filename = os.path.join(args.output_directory, args.prefix +
os.path.basename(args.input_directory).capitalize() + '.cxx')
with open(filename, 'w') as f:
stream = stream_with_line_breaks(f)
# output file preamble
stream("""//=========================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//=========================================================================
""")
stream("#include <utility>")
stream("#include <pybind11/pybind11.h>")
stream("")
stream("namespace py = pybind11;")
stream("")
stream("template <typename T, typename... Args>")
stream("using PySharedPtrClass = py::class_<T, std::shared_ptr<T>, Args...>;")
stream("")
for output_file in output_files:
stream("#include \"%s\"" % os.path.basename(output_file))
stream("")
stream('PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);')
stream("")
stream("PYBIND11_PLUGIN(%s)" % args.module)
stream("{")
stream(" py::module m(\"%s\", \"<description>\");" % args.module)
modules = set()
for obj in wrapped_objects:
parent = "m"
for ns in get_scope(obj):
if ns not in modules:
stream(" py::module %s = %s.def_submodule(\"%s\", \"<description>\");" % (ns, parent, ns))
modules.add(ns)
parent = ns
stream("")
stream(" // The order of these function calls is important! It was determined by")
stream(" // comparing the dependencies of each of the wrapped objects.")
topological_mapping = {}
for obj in wrapped_objects:
dependencies = set()
if type(obj).__name__.find('class_t') != -1:
for base_class in obj.recursive_bases:
parent = base_class.related_class
inherits = base_class.declaration_path
enable_shared = 'enable_shared_from_this<%s>' \
% full_class_name(obj)
if inherits != ['::', 'std', enable_shared]:
dependencies.add(parent)
topological_mapping[obj] = dependencies
sorted_objs = toposort_flatten(topological_mapping)
for obj in sorted_objs:
if obj not in wrapped_objects:
continue
scope = get_scope(obj)
signature = wrapped_objects[obj]
module = scope[-1] if len(scope) > 0 else "m"
if type(obj).__name__.find('class_t') != -1:
parent = get_parent(obj)
stream(" %s %s = %s(%s);" % (bind_class_name(obj),
mangled_name(obj), signature,
module))
else:
stream(" %s(%s);" % (signature, module))
stream("")
stream(" return m.ptr();")
stream("}")
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment