diff --git a/packaging/apple/mac_packager.sh.in b/packaging/apple/mac_packager.sh.in index dd3519b7f0..2e693c6dc1 100755 --- a/packaging/apple/mac_packager.sh.in +++ b/packaging/apple/mac_packager.sh.in @@ -33,6 +33,10 @@ done @dtk_DIR@/bin/dtkDeploy medInria.app $injectDirs &>/dev/null +@CMAKE_COMMAND@ --install @pyncpp_ROOT@ --prefix . --component Runtime +mkdir medInria.app/Contents/Resources/lib +mv lib/python@PYTHON_VERSION_MAJOR@.@PYTHON_VERSION_MINOR@ medInria.app/Contents/Resources/lib + #Run fancy packaging apple script \cp -f @medInria_SOURCE_DIR@/utils/osx_packaging/BaseMedinriaPackage.sparseimage.gz @PROJECT_BINARY_DIR@/MedinriaPackage.sparseimage.gz diff --git a/packaging/linux/LinuxPackaging.cmake b/packaging/linux/LinuxPackaging.cmake index 7d70129e92..89866a0533 100644 --- a/packaging/linux/LinuxPackaging.cmake +++ b/packaging/linux/LinuxPackaging.cmake @@ -84,11 +84,16 @@ set(backup_CPACK_INSTALL_CMAKE_PROJECTS ${CPACK_INSTALL_CMAKE_PROJECTS} ${CMAKE_ #clear it set(CPACK_INSTALL_CMAKE_PROJECTS "") -foreach(external_project ${external_projects}) - if(NOT USE_SYSTEM_${external_project} AND BUILD_SHARED_LIBS_${external_project}) - ExternalProject_Get_Property(${external_project} binary_dir) - set(CPACK_INSTALL_CMAKE_PROJECTS ${CPACK_INSTALL_CMAKE_PROJECTS} ${binary_dir} ${external_project} ALL "/") - endif() +foreach(external_project ${external_projects}) + if(NOT USE_SYSTEM_${external_project} + AND BUILD_SHARED_LIBS_${external_project} + AND DEFINED ${external_project}_ROOT) + install(CODE " + execute_process( + COMMAND ${CMAKE_COMMAND} --install ${${external_project}_ROOT} --prefix \"\${CMAKE_INSTALL_PREFIX}\" + ) + ") + endif() endforeach() foreach(dir ${PRIVATE_PLUGINS_DIRS}) diff --git a/packaging/windows/WindowsPackaging.cmake b/packaging/windows/WindowsPackaging.cmake index 9e36e36b86..2adca34c39 100644 --- a/packaging/windows/WindowsPackaging.cmake +++ b/packaging/windows/WindowsPackaging.cmake @@ -117,6 +117,9 @@ list(APPEND ${DCMTK_ROOT}/bin/${CONFIG_MODE} ) + +install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} --install ${pyncpp_ROOT} --prefix \"\${CMAKE_INSTALL_PREFIX}\" --component Runtime)" COMPONENT Runtime) + install(CODE " file(GLOB_RECURSE itk_files LIST_DIRECTORIES true \"${ITK_ROOT}/bin/*.dll\") @@ -139,6 +142,7 @@ endforeach() file(INSTALL ${MEDINRIA_FILES}/ DESTINATION \${CMAKE_INSTALL_PREFIX}/bin/ FILES_MATCHING PATTERN \"*${CMAKE_EXECUTABLE_SUFFIX}\") file(INSTALL ${MEDINRIA_FILES}/ DESTINATION \${CMAKE_INSTALL_PREFIX}/bin/ FILES_MATCHING PATTERN \"*${CMAKE_SHARED_LIBRARY_SUFFIX}\") +file(INSTALL ${MEDINRIA_FILES}/ DESTINATION \${CMAKE_INSTALL_PREFIX}/bin/ FILES_MATCHING PATTERN \"*.pyd\") file(INSTALL ${QT_PLUGINS_DIR}/imageformats/qgif.dll DESTINATION \${CMAKE_INSTALL_PREFIX}/bin/imageformats/ FILES_MATCHING PATTERN \"*${CMAKE_SHARED_LIBRARY_SUFFIX}\") file(INSTALL ${QT_PLUGINS_DIR}/imageformats/qicns.dll DESTINATION \${CMAKE_INSTALL_PREFIX}/bin/imageformats/ FILES_MATCHING PATTERN \"*${CMAKE_SHARED_LIBRARY_SUFFIX}\") file(INSTALL ${QT_PLUGINS_DIR}/imageformats/qico.dll DESTINATION \${CMAKE_INSTALL_PREFIX}/bin/imageformats/ FILES_MATCHING PATTERN \"*${CMAKE_SHARED_LIBRARY_SUFFIX}\") @@ -206,4 +210,4 @@ if(${SDK_PACKAGING} ) else() message("No folder ${SDK_DIR} exists. SDK will not be installed.") endif() -endif() \ No newline at end of file +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1ce34e993c..1e8975d001 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -83,6 +83,11 @@ option(USE_FFmpeg ) endif() +option(USE_Python + "Build with Python support" + ON + ) + ## ############################################################################# ## Find package ## ############################################################################# @@ -103,6 +108,10 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS find_package(dtk REQUIRED) +if(USE_Python) + find_package(pyncpp REQUIRED COMPONENTS CPP_API Qt5) +endif() + ## ############################################################################# ## enable c++ 17 ## ############################################################################# diff --git a/src/app/medInria/CMakeLists.txt b/src/app/medInria/CMakeLists.txt index 45586eec24..5ab8006572 100644 --- a/src/app/medInria/CMakeLists.txt +++ b/src/app/medInria/CMakeLists.txt @@ -141,6 +141,41 @@ target_link_libraries(${TARGET_NAME} PRIVATE med::CoreGui ) +## ############################################################################# +## Python +## ############################################################################# + +if(USE_Python) + target_link_libraries(${TARGET_NAME} PUBLIC ${pyncpp_CPP_API_LIBRARY}) + + if(APPLE) + set(python_home "../Resources") + else() + set(python_home "..") + endif() + + set(python_home "${python_home}/${pyncpp_PYTHON_INSTALL_DESTINATION}") + + if(APPLE) + set(python_plugin_path "../Plugins/python") + else() + set(python_plugin_path "python_plugins") + endif() + + target_compile_definitions(${TARGET_NAME} PUBLIC + USE_PYTHON + PYTHON_HOME="${python_home}" + PYTHON_PLUGIN_PATH="${python_plugin_path}" + BUILD_PYTHON_HOME="${pyncpp_PYTHON_DIR}" + BUILD_PYTHON_PLUGIN_PATH="${CMAKE_BINARY_DIR}/bin/python_plugins" + ) + + if(WIN32) + target_compile_definitions(${TARGET_NAME} PUBLIC + PLUGINS_LEGACY_PATH="plugins_legacy" + ) + endif() +endif() ## ############################################################################# ## install diff --git a/src/app/medInria/main.cpp b/src/app/medInria/main.cpp index f7f432fa81..b5619e761b 100644 --- a/src/app/medInria/main.cpp +++ b/src/app/medInria/main.cpp @@ -43,6 +43,10 @@ #include +#ifdef USE_PYTHON +#include +#endif + void forceShow(medMainWindow &mainwindow) { // Idea and code taken from the OpenCOR project, Thanks Allan for the code! @@ -181,6 +185,89 @@ int main(int argc, char *argv[]) if (runningMedInria) return 0; +#ifdef USE_PYTHON + pyncpp::Manager pythonManager; + QString pythonHomePath = PYTHON_HOME; + QDir applicationPath = qApp->applicationDirPath(); + QDir pythonHome = applicationPath; + QDir pythonPluginPath = pythonHome; + bool pythonHomeFound = false; + bool pythonPluginsFound = false; + QString pythonErrorMessage; + + if (pythonHome.cd(PYTHON_HOME)) + { + pythonHomeFound = true; + pythonPluginsFound = pythonPluginPath.cd(PYTHON_PLUGIN_PATH); + } + else + { + if (pythonHome.cd(BUILD_PYTHON_HOME)) + { + pythonHomeFound = true; + pythonPluginsFound = pythonPluginPath.cd(BUILD_PYTHON_PLUGIN_PATH); + } + } + + if (!pythonHomeFound) + { + pythonErrorMessage = "The embedded Python could not be found "; + } + else + { + if(!pythonManager.initialize(qUtf8Printable(pythonHome.absolutePath()))) + { + pythonErrorMessage = "Initialization of the embedded Python failed."; + } + else + { + if (pythonPluginsFound) + { + try + { + pyncpp::Module sysModule = pyncpp::Module::import("sys"); + pyncpp::Object sysPath = sysModule.attribute("path"); + sysPath.append(pyncpp::Object(pythonPluginPath.absolutePath())); + qInfo() << "Added Python plugin path: " << pythonPluginPath.path(); + +#ifdef Q_OS_WIN + QDir sitePackages = pythonHome; + + if (sitePackages.cd("lib/site-packages")) + { + sysPath.append(pyncpp::Object(sitePackages.absolutePath())); + } + else + { + pythonErrorMessage = "Cannot find site directory."; + } + + pyncpp::Module osModule = pyncpp::Module::import("os"); + osModule.callMethod("add_dll_directory", applicationPath.absolutePath()); + qInfo() << "Added Python DLL path: " << applicationPath.path(); + + QDir pluginsLegacyPath = applicationPath; + + if (pluginsLegacyPath.cd(PLUGINS_LEGACY_PATH)) + { + osModule.callMethod("add_dll_directory", pluginsLegacyPath.absolutePath()); + qInfo() << "Added Python DLL path: " << pluginsLegacyPath.path(); + } + else + { + pythonErrorMessage = "Could not find legacy plugins path."; + } +#endif // Q_OS_WIN + } + catch (pyncpp::Exception& e) + { + pythonErrorMessage = e.what(); + } + } + } + } +#endif // USE_PYTHON + QNetworkAccessManager *qnam = new QNetworkAccessManager(&application); medFirstStart firstStart(qnam); firstStart.pushPathToCheck(medSourcesLoader::path(), ":/configs/DataSourcesDefault.json", "dataSourceLoader", "", medSourcesLoader::initSourceLoaderCfg); @@ -291,6 +378,14 @@ int main(int argc, char *argv[]) QGLFormat::setDefaultFormat(format); } +#ifdef USE_PYTHON + if(!pythonErrorMessage.isEmpty()) + { + qWarning() << "(Python error) " << pythonErrorMessage; + QMessageBox::warning(mainwindow, "Python error", pythonErrorMessage); + } +#endif + if (medPluginManager::instance()->plugins().isEmpty()) { QMessageBox::warning( diff --git a/superbuild/CMakeLists.txt b/superbuild/CMakeLists.txt index d2be24f338..2e69628d86 100644 --- a/superbuild/CMakeLists.txt +++ b/superbuild/CMakeLists.txt @@ -11,6 +11,8 @@ # ################################################################################ +option(USE_Python "Build with Python support" ON) + ## ############################################################################# ## Add packages ## ############################################################################# @@ -28,6 +30,16 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick ) +if (USE_Python AND UNIX) + if(APPLE) + set(default_root_dir "/usr/local/opt/openssl") + else() + set(default_root_dir) + endif() + set(OPENSSL_ROOT_DIR ${default_root_dir} CACHE PATH "Root directory of OpenSSL.") + find_package(OpenSSL COMPONENTS SSL) +endif() + ## ############################################################################# ## Add exteral projects ## ############################################################################# @@ -71,6 +83,12 @@ if (USE_DTKIMAGING) ) endif() +if (USE_Python) + list(APPEND external_projects + pyncpp + ) +endif() + list(APPEND external_projects medInria ) diff --git a/superbuild/projects_modules/medInria.cmake b/superbuild/projects_modules/medInria.cmake index 301bba9728..ab96978a62 100644 --- a/superbuild/projects_modules/medInria.cmake +++ b/superbuild/projects_modules/medInria.cmake @@ -33,6 +33,12 @@ if (USE_DTKIMAGING) dtkImaging ) endif() + +if (USE_Python) + list(APPEND ${ep}_dependencies + pyncpp + ) +endif() ## ############################################################################# ## Prepare the project @@ -107,6 +113,12 @@ if (USE_DTKIMAGING) -DdtkImaging_DIR:PATH=${dtkImaging_DIR} ) endif() + +if (USE_Python) + list(APPEND cmake_cache_args + -Dpyncpp_ROOT:PATH=${pyncpp_ROOT} + ) +endif() ## ############################################################################# ## Add external-project diff --git a/superbuild/projects_modules/pyncpp.cmake b/superbuild/projects_modules/pyncpp.cmake new file mode 100644 index 0000000000..32a5f0be41 --- /dev/null +++ b/superbuild/projects_modules/pyncpp.cmake @@ -0,0 +1,78 @@ +################################################################################ +# +# medInria +# +# Copyright (c) INRIA 2024. 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. +# +################################################################################ + +set(PYTHON_VERSION_MAJOR 3 CACHE STRING "Python major version") +set(PYTHON_VERSION_MINOR 10 CACHE STRING "Python minor version") +set(PYTHON_VERSION_PATCH 10 CACHE STRING "Python patch version") + +function(pyncpp_project) + + set(ep pyncpp) + + EP_Initialisation(${ep} + USE_SYSTEM OFF + BUILD_SHARED_LIBS ON + REQUIRED_FOR_PLUGINS ON + ) + + if(NOT USE_SYSTEM_${ep}) + + epComputPath(${ep}) + + set(project_args + GIT_REPOSITORY ${GITHUB_PREFIX}LIRYC-IHU/pyncpp.git + GIT_TAG 0.1.x + GIT_SHALLOW True + GIT_PROGRESS True + ) + + set(cmake_args + ${ep_common_cache_args} + -D "PYNCPP_PYTHON_VERSION_MAJOR:STRING=${PYTHON_VERSION_MAJOR}" + -D "PYNCPP_PYTHON_VERSION_MINOR:STRING=${PYTHON_VERSION_MINOR}" + -D "PYNCPP_PYTHON_VERSION_PATCH:STRING=${PYTHON_VERSION_PATCH}" + -D "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE_ExtProjs}" + ) + + if(UNIX) + list(APPEND cmake_args + -D Qt5_DIR:PATH=${Qt5_DIR} + -D OPENSSL_ROOT_DIR:PATH=${OPENSSL_ROOT_DIR} + ) + endif() + + ## ##################################################################### + ## Add external project + ## ##################################################################### + + ExternalProject_Add(${ep} + PREFIX ${EP_PATH_SOURCE} + SOURCE_DIR ${EP_PATH_SOURCE}/${ep} + BINARY_DIR ${build_path} + TMP_DIR ${tmp_path} + STAMP_DIR ${stamp_path} + DEPENDS ${${ep}_dependencies} + CMAKE_ARGS ${cmake_args} + INSTALL_COMMAND "" + "${project_args}" + ) + + ## ##################################################################### + ## Export variables + ## ##################################################################### + + set(${ep}_ROOT "${build_path}" PARENT_SCOPE) + + endif() + +endfunction()