diff --git a/CMake/ITKModuleDoxygen.cmake b/CMake/ITKModuleDoxygen.cmake index 7a1ca952840..3af92802f76 100644 --- a/CMake/ITKModuleDoxygen.cmake +++ b/CMake/ITKModuleDoxygen.cmake @@ -3,6 +3,13 @@ # ${_name}.dot file which defines the local dependency as graph # which will be then processed by dot +# /CMake in a build tree, the installed package directory otherwise. +if(EXISTS "${ITK_CMAKE_DIR}/../Utilities/Doxygen/mcdoc.py") + set(ITK_MCDOC_SCRIPT "${ITK_CMAKE_DIR}/../Utilities/Doxygen/mcdoc.py") +else() + set(ITK_MCDOC_SCRIPT "${ITK_CMAKE_DIR}/mcdoc.py") +endif() + macro(itk_module_doxygen _name) # _content defines the content of the ${_name}.dox file set(_content "/**\n") @@ -49,7 +56,7 @@ macro(itk_module_doxygen _name) NAME ${_name}InDoxygenGroup COMMAND ${Python3_EXECUTABLE} - "${ITK_CMAKE_DIR}/../Utilities/Doxygen/mcdoc.py" + "${ITK_MCDOC_SCRIPT}" check ${_name} ${${_name}_SOURCE_DIR}/include diff --git a/CMake/ITKModuleExternal.cmake b/CMake/ITKModuleExternal.cmake index 2aa1f99c93c..6347832be2b 100644 --- a/CMake/ITKModuleExternal.cmake +++ b/CMake/ITKModuleExternal.cmake @@ -255,23 +255,24 @@ if(ITK_WRAPPING) "${CMAKE_CURRENT_SOURCE_DIR}/wrapping/CMakeLists.txt" ) set(EXTERNAL_WRAP_ITK_PROJECT ON) - set(WRAP_ITK_CMAKE_DIR "${ITK_CMAKE_DIR}/../Wrapping") - include("${WRAP_ITK_CMAKE_DIR}/TypedefMacros.cmake") - # Build tree + # /CMake in a build tree, the installed package directory otherwise. if(EXISTS "${ITK_CMAKE_DIR}/../Wrapping/CMakeLists.txt") - add_subdirectory( - "${ITK_CMAKE_DIR}/../Wrapping" - ${CMAKE_CURRENT_BINARY_DIR}/Wrapping - ) - # Install tree - elseif(EXISTS"${ITK_CMAKE_DIR}/Wrapping/CMakeLists.txt") - add_subdirectory( - "${ITK_CMAKE_DIR}/Wrapping" - ${CMAKE_CURRENT_BINARY_DIR}/Wrapping - ) + set(WRAP_ITK_CMAKE_DIR "${ITK_CMAKE_DIR}/../Wrapping") + elseif(EXISTS "${ITK_CMAKE_DIR}/Wrapping/CMakeLists.txt") + set(WRAP_ITK_CMAKE_DIR "${ITK_CMAKE_DIR}/Wrapping") else() - message(FATAL_ERROR "Could not find wrapping infrastructure.") + message( + FATAL_ERROR + "Could not find wrapping infrastructure at ${ITK_CMAKE_DIR}. To wrap a module against an installed ITK, that ITK must be reconfigured with ITK_INSTALL_WRAPPING_DEVELOPMENT_FILES=ON, then rebuilt and reinstalled." + ) endif() + include("${WRAP_ITK_CMAKE_DIR}/TypedefMacros.cmake") + # Python registers the factories at import; the wrapping library must not. + set(ITK_WRAPPING_NO_FACTORY_REGISTER_MANAGER ON) + add_subdirectory( + "${WRAP_ITK_CMAKE_DIR}" + ${CMAKE_CURRENT_BINARY_DIR}/Wrapping + ) endif() endif() # Create target to download data from the ITKData group. This must come after diff --git a/CMake/ITKModuleHeaderTest.cmake b/CMake/ITKModuleHeaderTest.cmake index 22745f5c78b..d9570f7cd90 100644 --- a/CMake/ITKModuleHeaderTest.cmake +++ b/CMake/ITKModuleHeaderTest.cmake @@ -4,6 +4,16 @@ # primary purpose of this test is to make sure there are not missing module # dependencies. +# /CMake in a build tree, the installed package directory otherwise. +if(EXISTS "${ITK_CMAKE_DIR}/../Utilities/Maintenance/BuildHeaderTest.py") + set( + ITK_BUILD_HEADER_TEST_SCRIPT + "${ITK_CMAKE_DIR}/../Utilities/Maintenance/BuildHeaderTest.py" + ) +else() + set(ITK_BUILD_HEADER_TEST_SCRIPT "${ITK_CMAKE_DIR}/BuildHeaderTest.py") +endif() + # Improve performance of MSVC GUI, by reducing number of header tests. set(MAXIMUM_NUMBER_OF_HEADERS_default 35) if(MSVC) @@ -114,8 +124,7 @@ macro(itk_module_headertest _name) OUTPUT ${_header_test_src} COMMAND - ${Python3_EXECUTABLE} - ${ITK_CMAKE_DIR}/../Utilities/Maintenance/BuildHeaderTest.py ${_name} + ${Python3_EXECUTABLE} ${ITK_BUILD_HEADER_TEST_SCRIPT} ${_name} ${${_name}_SOURCE_DIR} ${${_name}_BINARY_DIR} ${MAXIMUM_NUMBER_OF_HEADERS} ${_test_num} ) @@ -126,6 +135,10 @@ macro(itk_module_headertest _name) ${ITK_MODULE_${_name}_TARGETS_NAMESPACE}${_name}Module ${ITK_MODULE_${_name}_TARGETS_NAMESPACE}ITKKWSysModule ) + # UseITK's register manager references every IO factory the ITK enables. + if(NOT ITK_SOURCE_DIR) + target_link_libraries(${_test_name} PRIVATE ${ITK_LIBRARIES}) + endif() target_link_options( ${_test_name} diff --git a/CMake/ITKModuleKWStyleTest.cmake b/CMake/ITKModuleKWStyleTest.cmake index 69a5060303a..bc5abbca804 100644 --- a/CMake/ITKModuleKWStyleTest.cmake +++ b/CMake/ITKModuleKWStyleTest.cmake @@ -32,7 +32,11 @@ if( AND NOT CMAKE_CROSSCOMPILING + AND + EXISTS + "${ITK_CMAKE_DIR}/../Utilities/KWStyle/BuildKWStyle.cmake" ) + # Only an ITK source tree carries BuildKWStyle.cmake; an install tree does not. include(${ITK_CMAKE_DIR}/../Utilities/KWStyle/BuildKWStyle.cmake) elseif(NOT KWSTYLE_FOUND) set(ITK_USE_KWSTYLE OFF) diff --git a/CMake/ITKModuleTest.cmake b/CMake/ITKModuleTest.cmake index 86aefd6c49b..720f4a4409a 100644 --- a/CMake/ITKModuleTest.cmake +++ b/CMake/ITKModuleTest.cmake @@ -87,6 +87,10 @@ EM_ASM( ${KIT_LIBS} ${ITKTestKernel_LIBRARIES} ) + # UseITK's register manager references every IO factory the ITK enables. + if(NOT ITK_SOURCE_DIR) + target_link_libraries(${KIT}TestDriver PRIVATE ${ITK_LIBRARIES}) + endif() target_link_options( ${KIT}TestDriver PRIVATE diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a17ab6f239..508dce5fc8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -878,6 +878,27 @@ install( CMake/itkTransformIOFactoryRegisterManager.h.in CMake/itkMeshIOFactoryRegisterManager.h.in CMake/ITKInitializeCXXStandard.cmake + # The transitive closure of include(ITKModuleExternal). + CMake/ITKModuleExternal.cmake + CMake/ITKModuleMacros.cmake + CMake/ITKModuleTest.cmake + CMake/ITKModuleInfo.cmake.in + CMake/ITKSetPython3Vars.cmake + CMake/ITKInitializeBuildType.cmake + CMake/ITKWindowsUtf8.cmake + CMake/ITKExternalData.cmake + CMake/ITKDownloadSetup.cmake + CMake/ExternalData.cmake + CMake/ExternalData_config.cmake.in + CMake/WrappingConfigCommon.cmake + CMake/itkRemoveTestFiles.cmake + CMake/ITKModuleDoxygen.cmake + CMake/ITKModuleHeaderTest.cmake + CMake/ITKModuleKWStyleTest.cmake + CMake/ITKModuleCPPCheckTest.cmake + CMake/CppcheckTargets.cmake + Utilities/Maintenance/BuildHeaderTest.py + Utilities/Doxygen/mcdoc.py DESTINATION ${ITK_INSTALL_PACKAGE_DIR} COMPONENT Development ) diff --git a/Documentation/docs/contributing/module_workflows.md b/Documentation/docs/contributing/module_workflows.md index e4bdf98c7cc..9e7ee5d20c8 100644 --- a/Documentation/docs/contributing/module_workflows.md +++ b/Documentation/docs/contributing/module_workflows.md @@ -36,6 +36,28 @@ jobs: pypi_password: ${{ secrets.pypi_password }} ``` +## Building an external module against an installed ITK + +An external module can be configured, built, and Python-wrapped against an +installed ITK prefix rather than an ITK build tree. Point `ITK_DIR` at the +installed package directory, for example +`/lib/cmake/ITK-6.0`, and configure the module as usual. + +Wrapping additionally requires the SWIG type indices and the wrapping CMake +infrastructure, which ITK installs only on request because the payload is +tens of megabytes. To make an installed prefix usable as a wrapping SDK, the +ITK being installed must be configured with: + +```bash +cmake -DITK_WRAP_PYTHON:BOOL=ON \ + -DITK_INSTALL_WRAPPING_DEVELOPMENT_FILES:BOOL=ON +``` + +then rebuilt and reinstalled. Configuring a module with `ITK_WRAP_PYTHON=ON` +against a prefix that lacks these files fails with "Could not find wrapping +infrastructure"; the remedy is to reconfigure, rebuild, and reinstall the ITK +the module points at, not to change anything in the module. + ## Further Reading For more information visit README documentation at the [ITKRemoteModuleBuildTestPackageAction](https://github.com/InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction/blob/main/README.md#itkremotemodulebuildtestpackageaction) project. diff --git a/Wrapping/CMakeLists.txt b/Wrapping/CMakeLists.txt index 1f4d8c95cd3..c09bcd38aae 100644 --- a/Wrapping/CMakeLists.txt +++ b/Wrapping/CMakeLists.txt @@ -267,3 +267,57 @@ if(ITK_WRAP_PYTHON) unset(ITK_STUB_DIR CACHE) unset(ITK_PKL_DIR CACHE) endif() + +# Lands under the package directory, where an installed consumer's ITK_DIR already points. +if(ITK_INSTALL_WRAPPING_DEVELOPMENT_FILES) + set(_itk_wrapping_install_dir "${ITK_INSTALL_PACKAGE_DIR}/Wrapping") + + macro(_itk_install_wrapping_development _source_dir _destination) + install( + DIRECTORY + "${_source_dir}/" + DESTINATION "${_destination}" + COMPONENT ${WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER}WrappingDevelopment + FILES_MATCHING + ${ARGN} + ) + endmacro() + + _itk_install_wrapping_development( + "${WrapITK_SOURCE_DIR}" + "${_itk_wrapping_install_dir}" + PATTERN + "*.cmake" + PATTERN + "CMakeLists.txt" + PATTERN + "*.i" + PATTERN + "*.in" + PATTERN + "*.py" + PATTERN + "*.h" + PATTERN + "images" + EXCLUDE + PATTERN + "Tests" + EXCLUDE + ) + + _itk_install_wrapping_development( + "${WRAP_ITK_TYPEDEFS_DIRECTORY}" + "${_itk_wrapping_install_dir}/Typedefs" + PATTERN + "*.i" + PATTERN + "*.idx" + PATTERN + "*.mdx" + PATTERN + "*.h" + ) + + unset(_itk_wrapping_install_dir) +endif() diff --git a/Wrapping/TypedefMacros.cmake b/Wrapping/TypedefMacros.cmake index 4d034eab42d..71a74ba01e0 100644 --- a/Wrapping/TypedefMacros.cmake +++ b/Wrapping/TypedefMacros.cmake @@ -133,6 +133,15 @@ macro(itk_wrap_module library_name) set(WRAPPER_LIBRARY_NAME "${library_name}") message(STATUS "${WRAPPER_LIBRARY_NAME}: Creating module.") + # Drop the register managers the consumer tree's UseITK put on this directory. + if(ITK_WRAPPING_NO_FACTORY_REGISTER_MANAGER) + foreach(_factory_name ${ITK_FACTORY_LIST}) + string(TOUPPER ${_factory_name} _factory_uc) + remove_definitions(-DITK_${_factory_uc}_FACTORY_REGISTER_MANAGER) + endforeach() + unset(_factory_uc) + endif() + # Mark the current source dir for inclusion because it may contain header files. include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}") include_directories(BEFORE ${WRAPPER_LIBRARY_INCLUDE_DIRECTORIES}) diff --git a/Wrapping/WrappingOptions.cmake b/Wrapping/WrappingOptions.cmake index 8b1fd7d71a4..cd4ff6817fe 100644 --- a/Wrapping/WrappingOptions.cmake +++ b/Wrapping/WrappingOptions.cmake @@ -13,6 +13,16 @@ else() set(ITK_WRAPPING OFF CACHE INTERNAL "Build external languages support" FORCE) endif() +# Opt-in: the SWIG type indices are tens of MB. +cmake_dependent_option( + ITK_INSTALL_WRAPPING_DEVELOPMENT_FILES + "Install the wrapping infrastructure and SWIG type indices so that external modules can be wrapped against an installed ITK" + OFF + "ITK_WRAPPING" + OFF +) +mark_as_advanced(ITK_INSTALL_WRAPPING_DEVELOPMENT_FILES) + cmake_dependent_option( ITK_PYTHON_RELEASE_GIL "Release Python Global Interpreter Lock (GIL) during ITK operations" diff --git a/Wrapping/macro_files/itk_auto_load_submodules.cmake b/Wrapping/macro_files/itk_auto_load_submodules.cmake index 4ca246ebc1e..32cd4af007d 100644 --- a/Wrapping/macro_files/itk_auto_load_submodules.cmake +++ b/Wrapping/macro_files/itk_auto_load_submodules.cmake @@ -105,6 +105,10 @@ function(generate_castxml_commandline_flags) # create the files used to pass the file to include to castxml set(include_dir_list ${WRAPPER_LIBRARY_INCLUDE_DIRECTORIES}) + # From an install tree the dependency targets carry no ITK include dirs. + if(ITK_INCLUDE_DIRS) + list(APPEND include_dir_list ${ITK_INCLUDE_DIRS}) + endif() list(REMOVE_DUPLICATES include_dir_list) # CONFIG_CASTXML_INC_CONTENTS - variable used for building contents to write with file(GENERATE)