Source code for instant.codegeneration

"""This module contains helper functions for code generation."""

# Copyright (C) 2010, 2013 Kent-Andre Mardal
# Copyright (C) 2008 Martin Sandve Alnes
# Copyright (C) 2009 Ilmar Wilbers
# Copyright (C) 2013 Garth N. Wells
#
# This file is part of Instant.
#
# Instant is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Instant is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Instant. If not, see <http://www.gnu.org/licenses/>.
#
# Alternatively, Instant may be distributed under the terms of the BSD license.

import sys
import re, os, io
from .output import instant_assert, instant_warning, instant_debug, write_file
from .config import get_swig_binary


[docs]def mapstrings(format, sequence): return "\n".join(format % i for i in sequence)
[docs]def reindent(code): '''Reindent a multiline string to allow easier to read syntax. Each line will be indented relative to the first non-empty line. Start the first line without text like shown in this example:: code = reindent(""" Foo Bar Blatti Ping """) makes all indentation relative to Foo. ''' lines = code.split("\n") space = "" # Get initial spaces from first non-empty line: for l in lines: if l: r = re.search(r"^( [ ]*)", l) if r is not None: space = r.groups()[0] break if not space: return code n = len(space) instant_assert(space == " "*n, "Logic breach in reindent.") return "\n".join(re.sub(r"^%s" % space, "", l) for l in lines)
[docs]def write_interfacefile(filename, modulename, code, init_code, additional_definitions, additional_declarations, system_headers, local_headers, wrap_headers, arrays): """Generate a SWIG interface file. Intended for internal library use. The input arguments are as follows: - modulename (Name of the module) - code (Code to be wrapped) - init_code (Code to put in the init section of the interface file) - additional_definitions (Definitions to be placed in initial block with C code as well as in the main section of the SWIG interface file) - additional_declarations (Declarations to be placed in the main section of the SWIG interface file) - system_headers (A list of system headers with declarations needed by the wrapped code) - local_headers (A list of local headers with declarations needed by the wrapped code) - wrap_headers (A list of local headers that will be included in the code and wrapped by SWIG) - arrays (A nested list, the inner lists describing the different arrays) The result of this function is that a SWIG interface with the name modulename.i is written to the current directory. """ instant_debug("Generating SWIG interface file '%s'." % filename) # create typemaps typemaps = "" valid_types = ['float', 'double', 'short', 'int', 'long', 'long long', 'unsigned short', 'unsigned int', 'unsigned long', 'unsigned long long'] for a in arrays: if isinstance(a, tuple): a = list(a) DATA_TYPE = 'double' for vt in valid_types: if vt in a: DATA_TYPE = vt a.remove(vt) if 'in' in a: # input arrays a.remove('in') instant_assert(len(a) > 1 and len(a) < 5, "Wrong number of elements in input array") if len(a) == 2: # 1-dimensional arrays, i.e. vectors typemaps += reindent(""" %%apply (int DIM1, %(dtype)s* IN_ARRAY1) {(int %(n1)s, %(dtype)s* %(array)s)}; """ % { 'n1' : a[0], 'array' : a[1], 'dtype' : DATA_TYPE }) elif len(a) == 3: # 2-dimensional arrays, i.e. matrices typemaps += reindent(""" %%apply (int DIM1, int DIM2, %(dtype)s* IN_ARRAY2) {(int %(n1)s, int %(n2)s, %(dtype)s* %(array)s)}; """ % { 'n1' : a[0], 'n2' : a[1], 'array' : a[2], 'dtype' : DATA_TYPE }) else: # 3-dimensional arrays, i.e. tensors typemaps += reindent(""" %%apply (int DIM1, int DIM2, int DIM3, %(dtype)s* IN_ARRAY3) {(int %(n1)s, int %(n2)s, int %(n3)s, %(dtype)s* %(array)s)}; """ % { 'n1' : a[0], 'n2' : a[1], 'n3' : a[2], 'array' : a[3], 'dtype' : DATA_TYPE }) elif 'out' in a: # output arrays a.remove('out') instant_assert(len(a) == 2, "Output array must be 1-dimensional") # 1-dimensional arrays, i.e. vectors typemaps += reindent(""" %%apply (int DIM1, %(dtype)s* ARGOUT_ARRAY1) {(int %(n1)s, %(dtype)s* %(array)s)}; """ % { 'n1' : a[0], 'array' : a[1], 'dtype' : DATA_TYPE }) else: # in-place arrays instant_assert(len(a) > 1 and len(a) < 5, "Wrong number of elements in output array") if 'multi' in a: # n-dimensional arrays, i.e. tensors > 3-dimensional a.remove('multi') typemaps += reindent(""" %%typemap(in) (int %(n)s,int* %(ptv)s,%(dtype)s* %(array)s){ if (!PyArray_Check($input)) { PyErr_SetString(PyExc_TypeError, "Not a NumPy array"); return NULL; ; } PyArrayObject* pyarray; pyarray = (PyArrayObject*)$input; $1 = int(pyarray->nd); int* dims = new int[$1]; for (int d=0; d<$1; d++) { dims[d] = int(pyarray->dimensions[d]); } $2 = dims; $3 = (%(dtype)s*)pyarray->data; } %%typemap(freearg) (int %(n)s,int* %(ptv)s,%(dtype)s* %(array)s){ // deleting dims delete $2; } """ % { 'n' : a[0] , 'ptv' : a[1], 'array' : a[2], 'dtype' : DATA_TYPE }) elif len(a) == 2: # 1-dimensional arrays, i.e. vectors typemaps += reindent(""" %%apply (int DIM1, %(dtype)s* INPLACE_ARRAY1) {(int %(n1)s, %(dtype)s* %(array)s)}; """ % { 'n1' : a[0], 'array' : a[1], 'dtype' : DATA_TYPE }) elif len(a) == 3: # 2-dimensional arrays, i.e. matrices typemaps += reindent(""" %%apply (int DIM1, int DIM2, %(dtype)s* INPLACE_ARRAY2) {(int %(n1)s, int %(n2)s, %(dtype)s* %(array)s)}; """ % { 'n1' : a[0], 'n2' : a[1], 'array' : a[2], 'dtype' : DATA_TYPE }) else: # 3-dimensional arrays, i.e. tensors typemaps += reindent(""" %%apply (int DIM1, int DIM2, int DIM3, %(dtype)s* INPLACE_ARRAY3) {(int %(n1)s, int %(n2)s, int %(n3)s, %(dtype)s* %(array)s)}; """ % { 'n1' : a[0], 'n2' : a[1], 'n3' : a[2], 'array' : a[3], 'dtype' : DATA_TYPE}) # end # end if # end for system_headers_code = mapstrings('#include <%s>', system_headers) local_headers_code = mapstrings('#include "%s"', local_headers) wrap_headers_code1 = mapstrings('#include "%s"', wrap_headers) wrap_headers_code2 = mapstrings('%%include "%s"', wrap_headers) numpy_i_include = '' if arrays: numpy_i_include = r'%include "numpy.i"' # Do not reindent as SWIG interface code can also include Python code. interface_string = """%%module %(modulename)s //%%module (directors="1") %(modulename)s //%%feature("director"); %%{ #include <iostream> %(additional_definitions)s %(system_headers_code)s %(local_headers_code)s %(wrap_headers_code1)s %(code)s %%} //%%feature("autodoc", "1"); %(numpy_i_include)s %%init%%{ %(init_code)s %%} %(additional_definitions)s %(additional_declarations)s %(wrap_headers_code2)s //%(typemaps)s %(code)s; """ % locals() write_file(filename, interface_string) instant_debug("Done generating interface file.")
[docs]def write_setup(filename, modulename, csrcs, cppsrcs, local_headers, include_dirs, library_dirs, libraries, swig_include_dirs, swigargs, cppargs, lddargs): """Generate a setup.py file. Intended for internal library use.""" instant_debug("Generating %s." % filename) swig_include_dirs.append(os.path.join(os.path.dirname(__file__), 'swig')) # Handle arguments swigfilename = "%s.i" % modulename wrapperfilename = "%s_wrap.cxx" % modulename # Treat C and C++ files in the same way for now cppsrcs = cppsrcs + csrcs + [wrapperfilename] swig_args = "" if swigargs: swig_args = " ".join(swigargs) compile_args = "" if cppargs: compile_args = ", extra_compile_args=%r" % cppargs link_args = "" if lddargs: link_args = ", extra_link_args=%r" % lddargs swig_include_dirs = " ".join("-I%s"%d for d in swig_include_dirs) if len(local_headers) > 0: swig_include_dirs += " -I.." py3 = "" if sys.version_info[0] < 3 else "-py3" # Generate code code = reindent(""" import os from distutils.core import setup, Extension name = '%s' swig_cmd =r'%s -python %s %s %s %s' os.system(swig_cmd) sources = %s setup(name = '%s', ext_modules = [Extension('_' + '%s', sources, include_dirs=%s, library_dirs=%s, libraries=%s %s %s)]) """ % (modulename, get_swig_binary(), py3, swig_include_dirs, swig_args, \ swigfilename, cppsrcs, modulename, modulename, include_dirs, \ library_dirs, libraries, compile_args, link_args)) write_file(filename, code) instant_debug("Done writing setup.py file.")
def _test_write_interfacefile(): modulename = "testmodule" code = "void foo() {}" init_code = "/* custom init code */" additional_definitions = "/* custom definitions */" additional_declarations = "/* custom declarations */" system_headers = ["system_header1.h", "system_header2.h"] local_headers = ["local_header1.h", "local_header2.h"] wrap_headers = ["wrap_header1.h", "wrap_header2.h"] arrays = [["length1", "array1"], ["dims", "lengths", "array2"]] write_interfacefile("%s.i" % modulename, modulename, code, init_code, additional_definitions, additional_declarations, system_headers, local_headers, wrap_headers, arrays) print("".join(io.open("%s.i" % modulename, encoding="utf8").readlines())) def _test_write_setup(): modulename = "testmodule" csrcs = ["csrc1.c", "csrc2.c"] cppsrcs = ["cppsrc1.cpp", "cppsrc2.cpp"] local_headers = ["local_header1.h", "local_header2.h"] include_dirs = ["includedir1", "includedir2"] library_dirs = ["librarydir1", "librarydir2"] libraries = ["lib1", "lib2"] swig_include_dirs = ["swigdir1", "swigdir2"], swigargs = ["-Swigarg1", "-Swigarg2"] cppargs = ["-cpparg1", "-cpparg2"] lddargs = ["-Lddarg1", "-Lddarg2"] write_setup("setup.py", modulename, csrcs, cppsrcs, local_headers, \ include_dirs, library_dirs, libraries, swig_include_dirs, \ swigargs, cppargs, lddargs) print("".join(io.open("setup.py", encoding="utf8").readlines()))
[docs]def unique(sequence): return list(set(sequence))
[docs]def find_vtk_classes(str): pattern = "vtk\w*" l = unique(re.findall(pattern, str)) return l
[docs]def create_typemaps(classes): s = "" typemap_template = """ %%typemap(in) %(class_name)s * { vtkObjectBase* obj = vtkPythonGetPointerFromObject($input, "%(class_name)s"); %(class_name)s * oobj = NULL; if (obj->IsA("%(class_name)s")) { oobj = %(class_name)s::SafeDownCast(obj); $1 = oobj; } } %%typemap(out) %(class_name)s * { $result = vtkPythonGetObjectFromPointer($1); } """ for cl in classes: s += typemap_template % { "class_name" : cl } return s
[docs]def generate_vtk_includes(classes): s = """ #include "vtkPythonUtil.h" """ for cl in classes: s += """ #include \"%s.h\" """ % cl return s
[docs]def generate_interface_file_vtk(signature, code): interface_template = """ %%module test %%{ %(includes)s %(code)s %%} %(typemaps)s %(code)s """ class_list = find_vtk_classes(code) includes = generate_vtk_includes(class_list) typemaps = create_typemaps(class_list) s = interface_template % { "typemaps" : typemaps, "code" : code, "includes" : includes } return s
[docs]def write_cmakefile(module_name, cmake_packages, csrcs, cppsrcs, local_headers, include_dirs, library_dirs, libraries, swig_include_dirs, swigargs, cppargs, lddargs): find_package_template = """ # Configuration for package %(package)s FIND_PACKAGE(%(package)s REQUIRED) IF(%(package)s_FOUND) INCLUDE(${%(PACKAGE)s_USE_FILE}) ENDIF(%(package)s_FOUND) """ cmake_form = dict(module_name=module_name) # Major hack to adress # https://bitbucket.org/fenics-project/dolfin/pull-requests/358/add-petsc-and-slepc-include-directories-in/ p = "" for package in cmake_packages: p += package.lower() + " " cmake_form["cmake_dep_targets"] = p cmake_form["python_executable"] = sys.executable cmake_form["extra_libraries"] = ";".join(libraries) cmake_form["extra_include_dirs"] = ";".join(include_dirs) cmake_form["extra_library_dirs"] = ";".join(library_dirs) cmake_form["extra_swig_include_dirs"] = " -I".join([" "] + swig_include_dirs) cmake_form["extra_swigargs"] = " ".join(swigargs) cmake_form["swig_executable"] = "\n".join(\ """if (DEFINED %(package)s_SWIG_EXECUTABLE) set(SWIG_EXECUTABLE ${%(package)s_SWIG_EXECUTABLE}) endif() """ % dict(package=package.upper()) for package in cmake_packages) cmake_form["find_packages"] = "\n\n".join(find_package_template % \ dict(package=package, PACKAGE=package.upper())\ for package in cmake_packages) cmake_form["packages_definitions"] = "\n".join( "${%s_CXX_DEFINITIONS}" % package.upper() for package in cmake_packages) cmake_form["packages_definitions"] += "\n"+"\n".join( "${%s_PYTHON_DEFINITIONS}" % package.upper() for package in cmake_packages) cmake_form["package_include_dirs"] = "\n".join(\ "include_directories(${%(package)s_PYTHON_INCLUDE_DIRS} ${%(package)s_3RD_PARTY_INCLUDE_DIRS} ${${NAME}_SOURCE_DIR})" % dict(package=package.upper()) for package in cmake_packages) cmake_form["package_flags"] = "\n".join(\ """set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} ${%(package)s_LINK_FLAGS}\") set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} ${%(package)s_LINK_FLAGS}\") """ % dict(package=package.upper()) for package in cmake_packages) cmake_form["package_swig_link_libraries"] = "\n".join(\ """if (DEFINED %(package)s_LIBRARIES OR DEFINED %(package)s_3RD_PARTY_LIBRARIES OR DEFINED %(package)s_PYTHON_LIBRARIES) swig_link_libraries(${SWIG_MODULE_NAME} ${%(package)s_LIBRARIES} ${%(package)s_3RD_PARTY_LIBRARIES} ${%(package)s_PYTHON_LIBRARIES} ${EXTRA_SOURCE_LIB}) endif()""" % dict(package=package.upper()) for package in cmake_packages) cmake_form["package_python_definitions"] = "\n".join(\ """if (DEFINED %(package)s_PYTHON_DEFINITIONS) add_definitions(${%(package)s_PYTHON_DEFINITIONS}) endif()""" % dict(package=package.upper()) for package in cmake_packages) cppsrcs.extend(csrcs) if len(cppsrcs) > 0: cmake_form["extra_sources_files"] = "set(SOURCE_FILES %s) " % " ".join(cppsrcs) else: cmake_form["extra_sources_files"] = "set(SOURCE_FILES)" if cppargs: cmake_form["cppargs"] = "set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} %s\")" % \ (" ".join(cppargs)) else: cmake_form["cppargs"] = "" if lddargs: cmake_form["lddargs"] = "set(CMAKE_EXE_LINKER_FLAGS \""\ "${CMAKE_EXE_LINKER_FLAGS} %s\")" % (" ".join(lddargs)) else: cmake_form["lddargs"] = "" cmake_template = """ cmake_minimum_required(VERSION 3.5.0) set (NAME %(module_name)s) PROJECT(${NAME}) set(PYTHON_EXECUTABLE %(python_executable)s) %(find_packages)s %(cppargs)s %(lddargs)s %(swig_executable)s find_package(SWIG REQUIRED) include(${SWIG_USE_FILE}) set(SWIG_MODULE_NAME ${NAME}) set(CMAKE_SWIG_FLAGS -module ${SWIG_MODULE_NAME} -shadow -modern -modernargs -fastdispatch -fvirtual -nosafecstrings -noproxydel -fastproxy -fastinit -fastunpack -fastquery -nobuildnone %(packages_definitions)s %(extra_swigargs)s %(extra_swig_include_dirs)s ) set(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}) set(SWIG_SOURCES ${NAME}.i) set_source_files_properties(${SWIG_SOURCES} PROPERTIES CPLUSPLUS ON) set(EXTRA_INCLUDE_DIRS \"%(extra_include_dirs)s\") if(EXTRA_INCLUDE_DIRS) include_directories(${EXTRA_INCLUDE_DIRS}) endif() %(package_include_dirs)s %(package_flags)s set(EXTRA_LIBRARY_DIRS \"%(extra_library_dirs)s\") if(EXTRA_LIBRARY_DIRS) link_directories(${EXTRA_LIBRARY_DIRS}) endif() %(extra_sources_files)s %(package_python_definitions)s swig_add_module(${SWIG_MODULE_NAME} python ${SWIG_SOURCES}) set(EXTRA_LIBRARIES %(extra_libraries)s) if(SOURCE_FILES) set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fpic\") add_library(source_file_lib STATIC ${SOURCE_FILES}) set(EXTRA_LIBRARIES \"source_file_lib;${EXTRA_LIBRARIES}\") target_link_libraries(source_file_lib %(cmake_dep_targets)s) endif() if(EXTRA_LIBRARIES) string(STRIP \"${EXTRA_LIBRARIES}\" EXTRA_LIBRARIES) swig_link_libraries(${SWIG_MODULE_NAME} ${EXTRA_LIBRARIES}) endif() %(package_swig_link_libraries)s """ % cmake_form filename = "CMakeLists.txt" write_file(filename, cmake_template)
[docs]def write_itk_cmakefile(name): file_template = """ cmake_minimum_required(VERSION 2.6.0) # This project is designed to be built outside the Insight source tree. PROJECT(%(name)%s) # Find ITK. FIND_PACKAGE(ITK REQUIRED) IF(ITK_FOUND) INCLUDE(${ITK_USE_FILE}) ENDIF(ITK_FOUND) # Find VTK. FIND_PACKAGE(VTK REQUIRED) IF(VTK_FOUND) INCLUDE(${VTK_USE_FILE}) ENDIF(VTK_FOUND) find_package(SWIG REQUIRED) include(${SWIG_USE_FILE}) set(SWIG_MODULE_NAME %(name)s) set(CMAKE_SWIG_FLAGS -module ${SWIG_MODULE_NAME} -shadow -modern -modernargs -fastdispatch -fvirtual -nosafecstrings -noproxydel -fastproxy -fastinit -fastunpack -fastquery -nobuildnone -Iinclude/swig ) set(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}) set(SWIG_SOURCES %(name)s.i) set_source_files_properties(${SWIG_SOURCES} PROPERTIES CPLUSPLUS ON) include_directories(${PYTHON_INCLUDE_PATH} ${%(name)s_SOURCE_DIR}) set(VTK_LIBS ITKCommon vtkCommon vtkImaging vtkIO vtkFiltering vtkRendering vtkGraphics vtkCommonPythonD vtkFilteringPythonD) # Work-around for bug in CMake 3.0.0 (see # http://www.cmake.org/Bug/view.php?id=14990) set(SWIG_MODULE_NAME_ORIG "${SWIG_MODULE_NAME}") if (${CMAKE_VERSION} MATCHES "3.0.0") set(SWIG_MODULE_NAME "_${SWIG_MODULE_NAME}") endif() swig_add_module(${SWIG_MODULE_NAME} python ${SWIG_SOURCES}) swig_link_libraries(${SWIG_MODULE_NAME} ${PYTHON_LIBRARIES} ${VTK_LIBS}) """ % { "name" : name } with io.open("CMakeLists.txt", 'w', encoding="utf8") as f: f.write(file_template)
[docs]def write_vmtk_cmakefile(name): file_template = """ cmake_minimum_required(VERSION 2.6.0) # This project is designed to be built outside the Insight source tree. PROJECT(%(name)%s) # Find ITK. FIND_PACKAGE(ITK REQUIRED) IF(ITK_FOUND) INCLUDE(${ITK_USE_FILE}) ENDIF(ITK_FOUND) # Find VTK. FIND_PACKAGE(VTK REQUIRED) IF(VTK_FOUND) INCLUDE(${VTK_USE_FILE}) ENDIF(VTK_FOUND) # Find VMTK. #FIND_PACKAGE(VMTK REQUIRED) #IF(VMTK_FOUND) # INCLUDE(${VMTK_USE_FILE}) #ENDIF(ITK_FOUND) find_package(SWIG REQUIRED) include(${SWIG_USE_FILE}) set(SWIG_MODULE_NAME %(name)s) set(CMAKE_SWIG_FLAGS -module ${SWIG_MODULE_NAME} -shadow -modern -modernargs -fastdispatch -fvirtual -nosafecstrings -noproxydel -fastproxy -fastinit -fastunpack -fastquery -nobuildnone -Iinclude/swig ) set(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}) set(SWIG_SOURCES %(name)s.i) set_source_files_properties(${SWIG_SOURCES} PROPERTIES CPLUSPLUS ON) include_directories(${PYTHON_INCLUDE_PATH} ${%(name)s_SOURCE_DIR} /usr/local/include/vmtk) link_directories(/usr/local/lib/vmtk .) set(VTK_LIBS ITKCommon vtkCommon vtkImaging vtkIO vtkFiltering vtkRendering vtkGraphics vtkCommonPythonD vtkFilteringPythonD) set(VMTK_LIBS vtkvmtkCommonPythonD vtkvmtkITKPythonD vtkvmtkCommon vtkvmtkITK vtkvmtkComputationalGeometryPythonD vtkvmtkMiscPythonD vtkvmtkComputationalGeometry vtkvmtkMisc vtkvmtkDifferentialGeometryPythonD vtkvmtkSegmentationPythonD vtkvmtkDifferentialGeometry vtkvmtkSegmentation vtkvmtkIOPythonD) # Work-around for bug in CMake 3.0.0 (see # http://www.cmake.org/Bug/view.php?id=14990) set(SWIG_MODULE_NAME_ORIG "${SWIG_MODULE_NAME}") if (${CMAKE_VERSION} MATCHES "3.0.0") set(SWIG_MODULE_NAME "_${SWIG_MODULE_NAME}") endif() swig_add_module(${SWIG_MODULE_NAME} python ${SWIG_SOURCES}) swig_link_libraries(${SWIG_MODULE_NAME} ${PYTHON_LIBRARIES} ${VTK_LIBS} ${VMTK_LIBS}) """ % { "name" : name } with io.open("CMakeLists.txt", 'w', encoding="utf8") as f: f.write(file_template)
[docs]def write_vtk_interface_file(signature, code): filename = signature ifile = filename + ".i" ifile_code = generate_interface_file_vtk(signature, code) with io.open(ifile, 'w', encoding="utf8") as iff: iff.write(ifile_code)
if __name__ == "__main__": _test_write_interfacefile() print("\n"*3) _test_write_setup()