From 3d301f788d74326b2ff6a49055f2426f45e03a9d Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Fri, 23 Oct 2026 14:32:20 +0000 Subject: [PATCH 1/2] ENH: Store ITK_AUTOLOAD_PATH in ObjectFactoryBase globals Initialize the autoload path from the environment once, when the ObjectFactoryBase globals are created, and expose ObjectFactoryBase::SetAutoloadPath()/GetAutoloadPath() so a host can set it at runtime before the factories are first loaded. ReHash() uses the stored path instead of re-reading the environment. Add standalone tests for setting the path by environment and by API, and use the setter in itkObjectFactoryTest2. The tests use only CMake and ITK facilities available on release-5.4 so the change can be backported; the CMake guard needs adapting there. Relates to #6982 and #6975. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Common/include/itkObjectFactoryBase.h | 18 +++- .../Core/Common/src/itkObjectFactoryBase.cxx | 35 ++++++-- Modules/Core/Common/test/CMakeLists.txt | 34 +++++++ .../test/itkObjectFactoryAutoloadPathTest.cxx | 88 +++++++++++++++++++ .../Common/test/itkObjectFactoryTest2.cxx | 13 +-- 5 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 Modules/Core/Common/test/itkObjectFactoryAutoloadPathTest.cxx diff --git a/Modules/Core/Common/include/itkObjectFactoryBase.h b/Modules/Core/Common/include/itkObjectFactoryBase.h index 3f6070ad3a3..fabf3d73ff2 100644 --- a/Modules/Core/Common/include/itkObjectFactoryBase.h +++ b/Modules/Core/Common/include/itkObjectFactoryBase.h @@ -89,8 +89,22 @@ class ITKCommon_EXPORT ObjectFactoryBase : public Object static std::list CreateAllInstance(const char * itkclassname); - /** Re-check the ITK_AUTOLOAD_PATH for new factory libraries. - * This calls UnRegisterAll before re-loading. */ + /** Set/Get the path list searched for dynamically loadable factories. + * The value is initialized from the ITK_AUTOLOAD_PATH environment variable + * when the global state is created. Set it before the factories are first + * used (or call ReHash() afterwards) for the new value to take effect. + * Paths are separated by a colon (semi-colon on win32). + * The path is only stored, and has no effect, when dynamic factory loading + * is disabled (ITK_USE_AUTOLOAD_PATH is OFF, e.g. in Python-wrapped builds). */ + static void + SetAutoloadPath(const std::string & path); + static std::string + GetAutoloadPath(); + + /** Re-check the autoload path (see SetAutoloadPath()) for new factory + * libraries. This calls UnRegisterAll before re-loading. + * The environment is not re-read: changing ITK_AUTOLOAD_PATH after startup + * requires SetAutoloadPath(). */ static void ReHash(); diff --git a/Modules/Core/Common/src/itkObjectFactoryBase.cxx b/Modules/Core/Common/src/itkObjectFactoryBase.cxx index 6d52197fef7..5f4da9ca6ea 100644 --- a/Modules/Core/Common/src/itkObjectFactoryBase.cxx +++ b/Modules/Core/Common/src/itkObjectFactoryBase.cxx @@ -89,6 +89,17 @@ SynchronizeList(FactoryListType & output, FactoryListType & input, bool internal } } } + +std::string +GetAutoloadPathFromEnvironment() +{ + std::string path; + if (!itksys::SystemTools::GetEnv("ITK_AUTOLOAD_PATH", path)) + { + path.clear(); + } + return path; +} } // end of anonymous namespace namespace itk @@ -125,6 +136,7 @@ class ObjectFactoryBase::ObjectFactoryBasePrivate : public LightObject FactoryListType m_InternalFactories{}; std::atomic m_Initialized{ false }; bool m_StrictVersionChecking{ false }; + std::string m_AutoloadPath{ GetAutoloadPathFromEnvironment() }; }; auto @@ -184,6 +196,19 @@ ObjectFactoryBase::GetStrictVersionChecking() return m_PimplGlobals->m_StrictVersionChecking; } +void +ObjectFactoryBase::SetAutoloadPath(const std::string & path) +{ + itkInitGlobalsMacro(PimplGlobals); + m_PimplGlobals->m_AutoloadPath = path; +} + +std::string +ObjectFactoryBase::GetAutoloadPath() +{ + itkInitGlobalsMacro(PimplGlobals); + return m_PimplGlobals->m_AutoloadPath; +} /** * Create an instance of a named ITK object using the loaded @@ -259,12 +284,8 @@ ObjectFactoryBase::LoadDynamicFactories() const char PathSeparator = ':'; # endif - const std::string itk_autoload_env{ "ITK_AUTOLOAD_PATH" }; - std::string LoadPath; - if (!itksys::SystemTools::GetEnv(itk_autoload_env, LoadPath)) - { - return; - } + itkInitGlobalsMacro(PimplGlobals); + const std::string LoadPath = m_PimplGlobals->m_AutoloadPath; if (LoadPath.empty()) { return; @@ -452,7 +473,7 @@ ObjectFactoryBase::LoadLibrariesInPath(const char * path) } /** - * Recheck the ITK_AUTOLOAD_PATH for new libraries + * Recheck the autoload path for new libraries */ void ObjectFactoryBase::ReHash() diff --git a/Modules/Core/Common/test/CMakeLists.txt b/Modules/Core/Common/test/CMakeLists.txt index 6a587ceca93..ff24bed1f28 100644 --- a/Modules/Core/Common/test/CMakeLists.txt +++ b/Modules/Core/Common/test/CMakeLists.txt @@ -158,6 +158,40 @@ if(ITK_BUILD_SHARED_LIBS AND ITK_USE_AUTOLOAD_PATH) ${ITK_TEST_OUTPUT_DIR} ) add_dependencies(ITKCommon2TestDriver FactoryTestLib) + + # Standalone tests: the autoload path must be configurable before the + # factories are dynamically loaded, either by environment or by API. + add_executable( + itkObjectFactoryAutoloadPathTest + itkObjectFactoryAutoloadPathTest.cxx + ) + itk_module_target_label(itkObjectFactoryAutoloadPathTest) + target_link_libraries( + itkObjectFactoryAutoloadPathTest + PUBLIC + ${ITKCommon_LIBRARIES} + ) + add_dependencies(itkObjectFactoryAutoloadPathTest FactoryTestLib) + itk_add_test( + NAME itkObjectFactoryAutoloadPathEnvTest + COMMAND + itkObjectFactoryAutoloadPathTest + env + $ + ) + set_tests_properties( + itkObjectFactoryAutoloadPathEnvTest + PROPERTIES + ENVIRONMENT + "ITK_AUTOLOAD_PATH=$" + ) + itk_add_test( + NAME itkObjectFactoryAutoloadPathApiTest + COMMAND + itkObjectFactoryAutoloadPathTest + api + $ + ) endif() itk_add_test( diff --git a/Modules/Core/Common/test/itkObjectFactoryAutoloadPathTest.cxx b/Modules/Core/Common/test/itkObjectFactoryAutoloadPathTest.cxx new file mode 100644 index 00000000000..9bd60e48101 --- /dev/null +++ b/Modules/Core/Common/test/itkObjectFactoryAutoloadPathTest.cxx @@ -0,0 +1,88 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkImportImageContainer.h" +#include "itkObjectFactoryBase.h" +#include "itksys/SystemTools.hxx" + +#include +#include +#include +#include + +// Usage: itkObjectFactoryAutoloadPathTest +// +// env: ITK_AUTOLOAD_PATH is set by the test harness before the process starts; +// nothing is configured in main(). +// api: ITK_AUTOLOAD_PATH is removed from the environment, then the autoload +// path is set with ObjectFactoryBase::SetAutoloadPath() before any +// factory is used. +// +// In both cases the factory library must be loaded lazily on first use. +int +main(int argc, char * argv[]) +{ + if (argc != 3) + { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return EXIT_FAILURE; + } + const std::string mode{ argv[1] }; + const std::string factoryDirectory{ argv[2] }; + + if (mode == "api") + { + // Ensure the setter, not an inherited environment, provides the path. + itksys::SystemTools::UnPutEnv("ITK_AUTOLOAD_PATH"); + itk::ObjectFactoryBase::SetAutoloadPath(factoryDirectory); + } + else if (mode != "env") + { + std::cerr << "Unknown mode: " << mode << std::endl; + return EXIT_FAILURE; + } + + if (itk::ObjectFactoryBase::GetAutoloadPath() != factoryDirectory) + { + std::cerr << "Autoload path is \"" << itk::ObjectFactoryBase::GetAutoloadPath() << "\", expected \"" + << factoryDirectory << '"' << std::endl; + return EXIT_FAILURE; + } + + // Objects created by the dynamic factory must be released before ReHash() unloads its library. + { + const auto container = itk::ImportImageContainer::New(); + if (std::strcmp(container->GetNameOfClass(), "TestImportImageContainer") != 0) + { + std::cerr << "Dynamic factory was not loaded; created class: " << container->GetNameOfClass() << std::endl; + return EXIT_FAILURE; + } + } + + // An empty path must not load the factory after a rehash. + itk::ObjectFactoryBase::SetAutoloadPath(""); + itk::ObjectFactoryBase::ReHash(); + if (std::strcmp(itk::ImportImageContainer::New()->GetNameOfClass(), + "TestImportImageContainer") == 0) + { + std::cerr << "Dynamic factory was loaded with an empty autoload path." << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/Modules/Core/Common/test/itkObjectFactoryTest2.cxx b/Modules/Core/Common/test/itkObjectFactoryTest2.cxx index c07ea3d8d64..eea90eca523 100644 --- a/Modules/Core/Common/test/itkObjectFactoryTest2.cxx +++ b/Modules/Core/Common/test/itkObjectFactoryTest2.cxx @@ -114,17 +114,8 @@ itkObjectFactoryTest2(int argc, char * argv[]) #ifdef CMAKE_INTDIR path += std::string("/") + std::string(CMAKE_INTDIR); #endif - const std::string itk_autoload_env{ "ITK_AUTOLOAD_PATH" }; - const std::string myenv{ itk_autoload_env + "=" + path }; - itksys::SystemTools::PutEnv(myenv); - std::cout << "SetValue => " << myenv << std::endl; - std::string getmyenv; - if (!itksys::SystemTools::GetEnv(itk_autoload_env, getmyenv)) - { - std::cerr << "ERROR: Environmental variable not set as requested : " << itk_autoload_env << "!=" << path - << std::endl; - } - std::cout << "GetValue => " << itk_autoload_env + "=" + getmyenv << std::endl; + itk::ObjectFactoryBase::SetAutoloadPath(path); + std::cout << "Autoload path => " << itk::ObjectFactoryBase::GetAutoloadPath() << std::endl; itk::ObjectFactoryBase::ReHash(); From 32419f36356531db1a9b0f50dc83553dae568d6d Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Fri, 23 Oct 2026 14:32:22 +0000 Subject: [PATCH 2/2] DOC: Add ITK 6 migration note for SetAutoloadPath ReHash() no longer re-reads ITK_AUTOLOAD_PATH; callers changing the path after startup must use ObjectFactoryBase::SetAutoloadPath(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../migration_guides/itk_6_migration_guide.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/docs/migration_guides/itk_6_migration_guide.md b/Documentation/docs/migration_guides/itk_6_migration_guide.md index d14721ccdb6..0df0aef5cac 100644 --- a/Documentation/docs/migration_guides/itk_6_migration_guide.md +++ b/Documentation/docs/migration_guides/itk_6_migration_guide.md @@ -1218,3 +1218,22 @@ afterwards raises `RuntimeError` rather than crashing. Calls to `OverrideBoundaryCondition` on any *other* class are unaffected — those never took ownership and keep their current behavior. + +## `ITK_AUTOLOAD_PATH` is read once; use `ObjectFactoryBase::SetAutoloadPath` + +The `ITK_AUTOLOAD_PATH` environment variable lists the directories ITK scans for +shared libraries that register dynamically loaded object factories (IO plugins, +for example). Entries are separated by `:` (`;` on Windows). The scan happens +lazily, the first time a factory is used, and only when ITK is built with +`ITK_USE_AUTOLOAD_PATH` (dynamic loading enabled, not a Python-wrapped build). + +The autoload path is now stored in `ObjectFactoryBase` global state, initialized +from the `ITK_AUTOLOAD_PATH` environment variable when that state is created. +`ObjectFactoryBase::SetAutoloadPath()` and `GetAutoloadPath()` set and query it +at runtime. `ReHash()` no longer re-reads the environment. + +### What you need to do + +Code that calls `putenv("ITK_AUTOLOAD_PATH=...")` after startup and then +`ObjectFactoryBase::ReHash()` must call `ObjectFactoryBase::SetAutoloadPath(...)` +instead. When factories were already initialized, follow it with `ReHash()`.