diff --git a/cmake/modules/RootTestDriver.cmake b/cmake/modules/RootTestDriver.cmake index 0a549e688ff0c..4579acca8b1e5 100644 --- a/cmake/modules/RootTestDriver.cmake +++ b/cmake/modules/RootTestDriver.cmake @@ -72,12 +72,33 @@ endif() #---Set environment -------------------------------------------------------------------------------- if(ENV) - string(REPLACE "#" ";" _env ${ENV}) + # CMake treats ';' as a list separator. Since ENV values may legitimately + # contain semicolons (e.g. PATH-like variables), we must escape them here + # so they survive the upcoming list expansion in `foreach()`. + string(REPLACE ";" "\\;" _env "${ENV}") + + # Deserialize the ENV string into a CMake list. + # This is the inverse operation of ROOT_ADD_TEST, which serializes the list + # by joining elements with '#'. + string(REPLACE "#" ";" _env "${_env}") + foreach(pair ${_env}) - string(REGEX REPLACE "^([^=]+)=(.*)$" "\\1;\\2" pair ${pair}) + + # During list expansion, CMake has already consumed any previous '\;' + # escaping. Re-escape semicolons so the key=value split below treats the + # value as a single string. + string(REPLACE ";" "\\;" pair "${pair}") + + # Split KEY=VALUE into a 2-element CMake list: [KEY;VALUE] + string(REGEX REPLACE "^([^=]+)=(.*)$" "\\1;\\2" pair "${pair}") + list(GET pair 0 var) list(GET pair 1 val) - set(ENV{${var}} ${val}) + + # As before, quoting is important here so the semicolons are not + # interpreted as list separators. + set(ENV{${var}} "${val}") + if(DBG) message(STATUS "testdriver[ENV]:${var}==>${val}") endif() diff --git a/core/clingutils/src/TClingUtils.cxx b/core/clingutils/src/TClingUtils.cxx index 3c49f56bdf5d0..c534f82b28245 100644 --- a/core/clingutils/src/TClingUtils.cxx +++ b/core/clingutils/src/TClingUtils.cxx @@ -5219,9 +5219,16 @@ void ROOT::TMetaUtils::SetPathsForRelocatability(std::vector& cling if (!envInclPath) return; + +#ifdef _WIN32 + constexpr char kPathSep = ';'; +#else + constexpr char kPathSep = ':'; +#endif + std::istringstream envInclPathsStream(envInclPath); std::string inclPath; - while (std::getline(envInclPathsStream, inclPath, ':')) { + while (std::getline(envInclPathsStream, inclPath, kPathSep)) { // Can't use TSystem in here; re-implement TSystem::ExpandPathName(). replaceEnvVars("ROOT_INCLUDE_PATH", inclPath); if (!inclPath.empty()) { diff --git a/roottest/root/io/transient/base/CMakeLists.txt b/roottest/root/io/transient/base/CMakeLists.txt index c15e2ea07a457..c59bacde1dfa2 100644 --- a/roottest/root/io/transient/base/CMakeLists.txt +++ b/roottest/root/io/transient/base/CMakeLists.txt @@ -30,8 +30,18 @@ else() set(outref_suffix "ZLIB") endif() +set(root_includepaths + ${CMAKE_CURRENT_SOURCE_DIR} + ${DEFAULT_ROOT_INCLUDE_PATH} +) +cmake_path(CONVERT "${root_includepaths}" TO_NATIVE_PATH_LIST root_includepaths_native) +# Like in tutorials/CMakeLists.txt, we use this generator expression to make +# sure the semicolon in Windows path separators doesn't get used up for list +# parsing at configuration time in ROOTTEST_ADD_TEST. +string(REPLACE ";" "$" root_includepaths_escaped "${root_includepaths_native}") + ROOTTEST_ADD_TEST(hadd_autoload COMMAND hadd -f data_merge.root data1.root data2.root OUTREF hadd_autoload${outref_suffix}.ref FIXTURES_REQUIRED root-io-transient-base-WriteFile-fixture - ENVIRONMENT ROOT_INCLUDE_PATH=${CMAKE_CURRENT_SOURCE_DIR}:${DEFAULT_ROOT_INCLUDE_PATH}) + ENVIRONMENT ROOT_INCLUDE_PATH=${root_includepaths_escaped}) diff --git a/tutorials/CMakeLists.txt b/tutorials/CMakeLists.txt index 53ceffd1e4d9a..4035dfdafc322 100644 --- a/tutorials/CMakeLists.txt +++ b/tutorials/CMakeLists.txt @@ -18,15 +18,15 @@ set(root_includepaths ) cmake_path(CONVERT "${root_includepaths}" TO_NATIVE_PATH_LIST root_includepaths_native) -# The environment argument gets serialized in ROOT_ADD_TEST for the -# RootTestDriver, and then deserialized in the latter with multiple steps of -# string parsing. We have to escape the semicolon excessively, because in every -# parsing step we lose one semicolon. Also we use the SEMICOLON generator -# expression, so at least we don't have to worry about escaping for the initial -# configuration stage. -# TODO: consider improving ROOT_ADD_TEST and RootTestDriver.cmake so this is not necessary. -string(REPLACE ";" "\\\\\\\\$" pythonpaths_escaped "${pythonpaths_native}") -string(REPLACE ";" "\\\\\\\\$" root_includepaths_escaped "${root_includepaths_native}") +# Use the $ generator expression to defer semicolon expansion until +# generator-expression evaluation. This avoids CMake interpreting literal ';' +# as list separators during argument handling. +# +# ROOT_ADD_TEST performs multiple string-to-list conversions when serializing +# the test command, which makes it unsafe to pass literal semicolons in +# arguments until this behavior is fixed. +string(REPLACE ";" "$" pythonpaths_escaped "${pythonpaths_native}") +string(REPLACE ";" "$" root_includepaths_escaped "${root_includepaths_native}") # - Set the environment for the tutorials, which also contains some environment # variables related to limiting the number of threads used by NumPy and