diff --git a/CMakeLists.txt b/CMakeLists.txt index 45d490355d..fcaab7b9a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,10 +43,17 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) OPTION(BUILD_HELICS_EXAMPLES "Enable the example Executables to be built" ON) OPTION(BUILD_PYTHON_INTERFACE "Build Python extension" OFF) +OPTION(BUILD_PYTHON2_INTERFACE "Build Python2.7 extension(Requires swig and will not build if python interface is active)" OFF) OPTION(BUILD_MATLAB_INTERFACE "Build Matlab Extension" OFF) OPTION(BUILD_OCTAVE_INTERFACE "Build Octave extension (very experimental)" OFF) OPTION(BUILD_JAVA_INTERFACE "Build Java extension" OFF) +if (BUILD_PYTHON_INTERFACE OR BUILD_PYTHON2_INTERFACE OR BUILD_MATLAB_INTERFACE OR BUILD_JAVA_INTERFACE OR BUILD_OCTAVE_INTERFACE OR BUILD_C_SHARED_LIB OR BUILD_CXX_SHARED_LIB) +set(INTERFACE_BUILD ON) +else() +set(INTERFACE_BUILD OFF) +endif() + OPTION(DISABLE_SWIG "Disable the use of swig to generate interface code and use repo code" OFF) OPTION(BUILD_C_SHARED_LIB "Build the Shared Libraries with a C interface" ON) @@ -54,14 +61,14 @@ IF(NOT MSVC) OPTION(BUILD_CXX_SHARED_LIB "Build a Shared Libraries of the CXX interface gcc/clang only" OFF) ENDIF() -if (BUILD_PYTHON_INTERFACE OR BUILD_MATLAB_INTERFACE OR BUILD_JAVA_INTERFACE OR BUILD_OCTAVE_INTERFACE OR BUILD_C_SHARED_LIB OR BUILD_CXX_SHARED_LIB) +if (INTERFACE_BUILD OR BUILD_C_SHARED_LIB OR BUILD_CXX_SHARED_LIB) set(BUILD_SHARED_LIBS ON) else() OPTION(USE_POSITION_INDEPENDENT_CODE "Build the libraries with Position independent code Useful if only building the static library and it will be used later in a shared library" OFF) endif() -if (BUILD_PYTHON_INTERFACE OR BUILD_MATLAB_INTERFACE OR BUILD_JAVA_INTERFACE OR BUILD_OCTAVE_INTERFACE OR BUILD_C_SHARED_LIB) +if (INTERFACE_BUILD OR BUILD_C_SHARED_LIB) set(BUILT_C_SHARED_LIB ON) endif() @@ -328,7 +335,7 @@ IF(BUILD_SHARED_LIBS AND NOT BUILD_CXX_SHARED_LIB) ENDIF() IF(BUILD_CXX_SHARED_LIB) - if (BUILD_C_SHARED_LIB OR BUILD_PYTHON_INTERFACE OR BUILD_JAVA_INTERFACE OR BUILD_MATLAB_INTERFACE OR BUILD_OCTAVE_INTERFACE) + if (BUILD_C_SHARED_LIB OR INTERFACE_BUILD) message(WARNING "Building the CXX shared library and C shared library in the same build is not advisable due to conflicting symbol visibility guidelines and is unlikely to work very well") endif() @@ -400,7 +407,7 @@ if (ENABLE_CLANG_TOOLS) include(clang-cxx-dev-tools) endif(ENABLE_CLANG_TOOLS) -if (BUILD_PYTHON_INTERFACE OR BUILD_MATLAB_INTERFACE OR BUILD_JAVA_INTERFACE OR BUILD_OCTAVE_INTERFACE) +if (INTERFACE_BUILD) add_subdirectory(swig) endif() diff --git a/swig/CMakeLists.txt b/swig/CMakeLists.txt index b721a745de..10960dfb09 100644 --- a/swig/CMakeLists.txt +++ b/swig/CMakeLists.txt @@ -10,6 +10,11 @@ INCLUDE_DIRECTORIES("${PROJECT_SOURCE_DIR}/src/helics/shared_api_library") if (BUILD_PYTHON_INTERFACE) ADD_SUBDIRECTORY(python) + if( BUILD_PYTHON2_INTERFACE) + message(WARNING "PYTHON2 interface will not be built since python 3 build is active") + endif() +elseif (BUILD_PYTHON2_INTERFACE) + ADD_SUBDIRECTORY(python2) endif() if (BUILD_JAVA_INTERFACE) diff --git a/swig/python2/CMakeLists.txt b/swig/python2/CMakeLists.txt new file mode 100644 index 0000000000..e5c96d4573 --- /dev/null +++ b/swig/python2/CMakeLists.txt @@ -0,0 +1,65 @@ + +IF (DISABLE_SWIG OR NOT SWIG_EXECUTABLE) + +MESSAGE(ERROR "Python 2 build requires swig") + +ENDIF() + +# https://stackoverflow.com/a/3818084/5451769 +if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + # Update if necessary + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-long-long -Wno-overlength-strings -Wno-ignored-attributes") +endif() + +find_package(PythonLibs 2 REQUIRED ) + +INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH}) +INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIRS}) + +SET(CMAKE_SWIG_FLAGS "") + +set(CMAKE_MACOSX_RPATH 1) +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") + +SET_PROPERTY(SOURCE ../helics.i PROPERTY SWIG_MODULE_NAME helics) + + +IF (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER 3.7) + SWIG_ADD_LIBRARY(helics TYPE MODULE LANGUAGE python SOURCES helicsPython2.i) +ELSE () + SWIG_ADD_MODULE(helics python helicsPython2.i) +ENDIF () + +SWIG_LINK_LIBRARIES(helics helicsSharedLib) + + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # https://groups.google.com/a/continuum.io/d/msg/anaconda/057P4uNWyCU/Iem6OtjBCQAJ + set_target_properties(_helics PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") +else () + SWIG_LINK_LIBRARIES(helics ${PYTHON_LIBRARIES}) +endif() + +set_target_properties (_helics PROPERTIES FOLDER interfaces) + +INSTALL(TARGETS _helics DESTINATION python COMPONENT python) + +foreach(keyfile IN LISTS KEY_LIBRARY_FILES) +add_custom_command(TARGET _helics POST_BUILD # Adds a post-build event to api tests + COMMAND ${CMAKE_COMMAND} -E copy_if_different # which executes "cmake - E copy_if_different..." + "${keyfile}" # <--this is in-file + "$/") # <--this is out-file path +endforeach(keyfile) + +add_custom_command(TARGET _helics2 POST_BUILD # Adds a post-build event to api tests + COMMAND ${CMAKE_COMMAND} -E copy_if_different # which executes "cmake - E copy_if_different..." + "$" # <--this is in-file + "$/") # <--this is out-file path + + + + +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/helics.py DESTINATION python COMPONENT python) +INSTALL(FILES $ DESTINATION python COMPONENT python) +INSTALL(FILES ${KEY_LIBRARY_FILES} DESTINATION python COMPONENT python) + diff --git a/swig/python2/README.md b/swig/python2/README.md new file mode 100644 index 0000000000..d2f6374984 --- /dev/null +++ b/swig/python2/README.md @@ -0,0 +1,3 @@ +# HELICS python 2.7 extension + + diff --git a/swig/python2/helicsPython2.i b/swig/python2/helicsPython2.i new file mode 100644 index 0000000000..1251ba16fd --- /dev/null +++ b/swig/python2/helicsPython2.i @@ -0,0 +1,5 @@ +%include "cstring.i" +%include "python_maps2.i" + +%include "../helics.i" + diff --git a/swig/python2/python_maps2.i b/swig/python2/python_maps2.i new file mode 100644 index 0000000000..ebef4449f7 --- /dev/null +++ b/swig/python2/python_maps2.i @@ -0,0 +1,132 @@ + +//typemap for short maxlen strings +%typemap(in, numinputs=0) (char *outputString, int maxlen) { + $2=256; + $1=(char *)malloc(256); +} + +%typemap(argout) (char *outputString, int maxlen) { + PyObject *str=PyString_FromString($1); + $result = SWIG_Python_AppendOutput($result, str); +} + +%typemap(freearg) (char *outputString, int maxlen) { + if ($1) free($1); +} + + +//typemap for large string output with a length return in C +%typemap(in, numinputs=0) (char *outputString, int maxStringlen, int *actualLength) { + $3=&($2); +} + +%typemap(freearg) (char *outputString, int maxStringlen, int *actualLength) { + if ($1) free($1); +} + +%typemap(check)(char *outputString, int maxStringlen, int *actualLength) { + $2=helicsSubscriptionGetValueSize(arg1)+2; + $1 = (char *) malloc($2); +} + +%typemap(argout) (char *outputString, int maxStringlen, int *actualLength) { + PyObject *o2=PyString_FromString($1); + $result = SWIG_Python_AppendOutput($result, o2); +} + + +//typemap for the input arguments +%typemap(in) (int argc, const char *const *argv) { + /* Check if is a list */ + if (PyList_Check($input)) { + int i; + $1 = PyList_Size($input); + $2 = (char **) malloc(($1+1)*sizeof(char *)); + for (i = 0; i < $1; i++) { + PyObject *o = PyList_GetItem($input,i); + if (PyString_Check(o)) + $2[i] = PyString_AsString(PyList_GetItem($input,i)); + else { + PyErr_SetString(PyExc_TypeError,"list must contain strings"); + free($2); + return NULL; + } + } + $2[i] = 0; + } else { + PyErr_SetString(PyExc_TypeError,"not a list"); + return NULL; + } +} + +%typemap(freearg) (int argc, const char *const *argv) { + free((char *) $2); +} + +// typemap for vector input functions +%typemap(in) (const double *vectorInput, int vectorlength) { + int i; + if (!PyList_Check($input)) { + PyErr_SetString(PyExc_ValueError,"Expected a list"); + return NULL; + } + $2=PyList_Size($input); + $1 = (double *) malloc($2*sizeof(double)); + + for (i = 0; i < $2; i++) { + PyObject *o = PyList_GetItem($input,i); + if (PyFloat_Check(o)) { + $1[i] = PyFloat_AsDouble(o); + }else if (PyInt_Check(o)) + { + $1[i] = (double)(PyInt_AsLong(o)); + } else { + PyErr_SetString(PyExc_ValueError,"List elements must be numbers"); + free($1); + return NULL; + } + } +} + +%typemap(argout) (const double *vectorInput, int vectorlength) +{ +} + +%typemap(freearg) (const double *vectorInput, int vectorlength) { + if ($1) free($1); +} + +// typemap for vector output functions + +%typemap(arginit) (double data[], int maxlen, int *actualSize) { + $1=(double *)(NULL); +} + +%typemap(in, numinputs=0) (double data[], int maxlen, int *actualSize) { + $3=&($2); +} + +%typemap(freearg) (double data[], int maxlen, int *actualSize) { + if ($1) free($1); +} + +// Set argument to NULL before any conversion occurs +%typemap(check)(double data[], int maxlen, int *actualSize) { + $2=helicsSubscriptionGetVectorSize(arg1); + $1 = (double *) malloc($2*sizeof(double)); +} + +%typemap(argout) (double data[], int maxlen, int *actualSize) { + int i; + PyObject *o2=PyList_New(*$3); + for (i = 0; i < *$3; i++) { + PyObject *o_item=PyFloat_FromDouble($1[i]); + PyList_SetItem(o2, i, o_item); + } + + $result = SWIG_Python_AppendOutput($result, o2); +} + +%apply (char *STRING, size_t LENGTH) { (const void *data, int inputDataLength) }; + +%apply (char *outputString, int maxStringlen, int *actualLength) {(void *data, int maxDatalen, int *actualSize)};