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()`. 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();