ExternalProject: Fix lists and empty commands with environment modification

Fix two bugs from commit e301cbffcc (ExternalProject: Set environment
variables, 2025-04-09):

* Do not flatten lists in command arguments when adding env mods.

* Remove empty `COMMAND`s without injecting corresponding env mods.

Fixes: #27125
Fixes: #27126
Co-authored-by: Brad King <brad.king@kitware.com>
This commit is contained in:
Evan Wilde
2025-09-11 15:26:04 -07:00
committed by Brad King
parent 2e0a8455b3
commit 9cb590b843
6 changed files with 103 additions and 52 deletions
+35 -45
View File
@@ -2273,60 +2273,50 @@ function(ExternalProject_Add_Step name step)
PROPERTY _EP_${step}_WORKING_DIRECTORY
)
# Replace list separators.
# Replace list separators and inject environment modifications.
get_property(sep
TARGET ${name}
PROPERTY _EP_LIST_SEPARATOR
)
if(sep AND command)
string(REPLACE "${sep}" "\\;" command "${command}")
endif()
# Add environment here!
get_property(environment
TARGET ${name}
PROPERTY _EP_${step}_ENVIRONMENT_MODIFICATION)
if(environment AND command)
if("${sep}" STREQUAL ":")
# The environment modification operation and value is separated by a
# colon. We should not replace that colon with a semicolon, allowing
# colons to act as a valid list separator.
# <name>=<op>:<value>
# Note: Environment variable names can contain `:` on Windows
set(result "")
foreach(env_modification IN LISTS environment)
if("${env_modification}" MATCHES "(.*)=([a-z]*):(.*)?")
if(CMAKE_MATCH_COUNT EQUAL 3)
string(REPLACE "${sep}" "\\\\\\\\\\;" _escapedMod "${CMAKE_MATCH_3}")
endif()
list(APPEND result "${CMAKE_MATCH_1}=${CMAKE_MATCH_2}:${_escapedMod}")
else()
message(SEND_ERROR "Malformed environment modification specifier:"
" '${env_modification}'\n"
"Expected MYVAR=OP:VALUE")
PROPERTY _EP_${step}_ENVIRONMENT_MODIFICATION
)
if(environment)
set(env_args "")
foreach(env_mod IN LISTS environment)
if(env_mod MATCHES [[^([^=:]+)=([a-z]+):(.*)$]])
set(_value "${CMAKE_MATCH_3}")
# Replace the separator only in the value in case it is `:`.
if(sep)
string(REPLACE "${sep}" [[\;]] _value "${_value}")
endif()
endforeach()
set(environment ${result})
elseif(sep)
# if the separator is not a colon, we don't have to worry about
# accidentally replacing the separator between the modification and value
string(REPLACE "${sep}" "\\\\\\\\;" environment "${environment}")
endif()
list(JOIN environment ";--modify;" environment)
list(PREPEND environment "--modify")
string(REPLACE ";" "\;" command "${command}")
string(REPLACE "COMMAND" ";" command "${command}")
set(__cmd)
foreach(__item IN LISTS command)
list(APPEND __cmd "COMMAND")
if(__item AND NOT "x${__item}" STREQUAL "x;;")
list(APPEND __cmd "${CMAKE_COMMAND}" -E env ${environment} --)
list(APPEND env_args --modify "${CMAKE_MATCH_1}=${CMAKE_MATCH_2}:${_value}")
else()
message(SEND_ERROR "Malformed environment modification specifier:"
" '${env_mod}'\n"
"Expected MYVAR=OP:VALUE")
endif()
list(APPEND __cmd "${__item}")
endforeach()
set(command ${__cmd})
set(env_command "${CMAKE_COMMAND};-E;env;${env_args};--")
else()
set(env_command "")
endif()
if(command)
if(env_command)
# Strip empty commands so we do not add env for them.
string(REGEX REPLACE [[^COMMAND;+(COMMAND;+)*]] "" command "${command}")
string(REGEX REPLACE [[;COMMAND;+(COMMAND;+)*]] ";COMMAND;" command "${command}")
# Replace the separator with an extra escape to survive list(TRANSFORM).
if(sep)
string(REPLACE "${sep}" [[\\;]] command "${command}")
endif()
# Prepend every command with our environment modification launcher.
list(TRANSFORM command APPEND ";${env_command}" REGEX "^COMMAND$")
set(command "${env_command};${command}")
elseif(sep)
string(REPLACE "${sep}" [[\;]] command "${command}")
endif()
endif()
# Replace location tags.
@@ -7,7 +7,11 @@
.*(Performing configure step for 'CustomCommand'|CustomCommand-configure).*
*-- Stage: config
*-- Separator: ;
*-- List: 4;5;6.*
*-- List: 4;5;6
*-- MYLIST:
*-- -- a
*-- -- b
*-- -- c.*
*-- Variable - Stage: config.*
*-- Variable - ListVar: 4;5;6
.*(Performing build step for 'CustomCommand'|CustomCommand-build).*
@@ -30,3 +34,12 @@
*-- Stage: build
*-- Separator: ,
*-- List: 7,8,9,10
.*(Performing configure step for 'DefaultCommandListSep'|DefaultCommandListSep-configure).*
*-- ConfigVar: config
*-- Separator: ;
*-- List: 9;8;7
*-- MYLIST:
*-- -- d
*-- -- e
*-- -- f
*-- -- g
@@ -7,7 +7,11 @@
.*(Performing configure step for 'CustomCommand'|CustomCommand-configure).*
*-- Stage: config
*-- Separator: ;
*-- List: 4;5;6.*
*-- List: 4;5;6
*-- MYLIST:
*-- -- a
*-- -- b
*-- -- c.*
*-- Variable - Stage: config.*
*-- Variable - ListVar: 4;5;6
.*(Performing build step for 'CustomCommand'|CustomCommand-build).*
@@ -31,14 +35,23 @@
*-- Separator: ,
*-- List: 7,8,9,10
.*(Performing configure step for 'DefaultCommandListSep'|DefaultCommandListSep-configure).*
*-- ConfigVar: config
*-- Separator: ;
*-- List: 9;8;7
*-- MYLIST:
*-- -- d
*-- -- e
*-- -- f
*-- -- g
.*(Performing configure step for 'DefaultCommandListColon'|DefaultCommandListColon-configure).*
*-- ConfigVar: config
*-- Separator: ;
*-- List: 10;11;12
.*(Performing build step for 'DefaultCommandListSep'|DefaultCommandListSep-build).*
.*(Performing build step for 'DefaultCommandListColon'|DefaultCommandListColon-build).*
*-- Stage: build
*-- Separator: ;
*-- List: 10;11;12
.*(Performing install step for 'DefaultCommandListSep'|DefaultCommandListSep-install).*
.*(Performing install step for 'DefaultCommandListColon'|DefaultCommandListColon-install).*
*-- Stage: install
*-- Separator: ;
*-- List: 10;11;12;13
+24 -3
View File
@@ -12,9 +12,13 @@ ExternalProject_Add(CustomCommand
PATCH_COMMAND ""
LIST_SEPARATOR ,
CONFIGURE_COMMAND ""
COMMAND "${CMAKE_COMMAND}" -P ${ScriptPath}
COMMAND
COMMAND "${CMAKE_COMMAND}" -DMYLIST='a,b,c' -P ${ScriptPath}
COMMAND "${CMAKE_COMMAND}" -DVARNAME=Stage -P ${ScriptPath}
COMMAND ""
COMMAND COMMAND COMMAND
COMMAND "${CMAKE_COMMAND}" -E echo "" ""
COMMAND
COMMAND "${CMAKE_COMMAND}" -DVARNAME=ListVar -P ${ScriptPath}
CONFIGURE_ENVIRONMENT_MODIFICATION
Stage=set:config
@@ -75,17 +79,34 @@ ExternalProject_Add(DefaultCommand
Stage=set:install
Separator=set:,)
ExternalProject_Add(DefaultCommandListSep
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/EnvVars"
DOWNLOAD_COMMAND ""
UPDATE_COMMAND ""
PATCH_COMMAND ""
DEPENDS DefaultCommand
LIST_SEPARATOR `
CMAKE_ARGS
-DVARIABLE=ConfigVar
-DMYLIST=d`e`f`g
CONFIGURE_ENVIRONMENT_MODIFICATION
ConfigVar=set:config
ListVar=set:9`8`7
ListSeparator=set:`
BUILD_COMMAND ""
INSTALL_COMMAND "")
# Using `:` as a list separator on Windows does not work as it replaces the `:`
# between the drive letter and the filepath with `;`.
if(NOT WIN32)
# Ensure that using `:` as a list-separator does not break setting environment
# variables
ExternalProject_Add(DefaultCommandListSep
ExternalProject_Add(DefaultCommandListColon
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/EnvVars"
DOWNLOAD_COMMAND ""
UPDATE_COMMAND ""
PATCH_COMMAND ""
DEPENDS DefaultCommand
DEPENDS DefaultCommandListSep
LIST_SEPARATOR :
CMAKE_ARGS
-DVARIABLE=ConfigVar
@@ -6,6 +6,13 @@ message(STATUS "${VARIABLE}: $ENV{${VARIABLE}}")
message(STATUS "Separator: $ENV{ListSeparator}")
message(STATUS "List: $ENV{ListVar}")
if(MYLIST)
message(STATUS "MYLIST:")
foreach(__item IN LISTS MYLIST)
message(STATUS "-- ${__item}")
endforeach()
endif()
add_custom_target(EchoEnvVars ALL COMMENT "Build environment..."
COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_SOURCE_DIR}/EchoVar.cmake")
@@ -5,3 +5,10 @@ else()
message(STATUS "Separator: $ENV{ListSeparator}")
message(STATUS "List: $ENV{ListVar}")
endif()
if(MYLIST)
message(STATUS "MYLIST:")
foreach(__item IN LISTS MYLIST)
message(STATUS "-- ${__item}")
endforeach()
endif()