execute_process: Fix invocation of .cmd/.bat with spaces

The `CreateProcessW` documentation states "to run a batch file, you must
start the command interpreter".  Use `cmd /c the.bat` to run batch files.
Also, use a "short path" to the `.bat` file if needed to avoid spaces.

Previously this worked in some cases only due to undocumented behavior
of `CreateProcessW` when given a `.bat` file.

Fixes: #26655
This commit is contained in:
Brad King 2025-01-31 11:51:01 -05:00
parent 104ca10208
commit 74c9d40876
8 changed files with 49 additions and 4 deletions

1
.gitattributes vendored
View File

@ -25,6 +25,7 @@ configure eol=lf
*.bat eol=crlf
*.bat.in eol=crlf
*.cmd eol=crlf
*.sln eol=crlf
*.vcproj eol=crlf

View File

@ -48,8 +48,14 @@ Options:
child process in an ``argv[]`` style array.
* On Windows platforms, the command line is encoded as a string such
that child processes using ``CommandLineToArgvW`` will decode the
original arguments.
that child processes using `CommandLineToArgvW`_ will decode the
original arguments. If the command runs a ``.bat`` or ``.cmd``
script, it may receive arguments with extra quoting.
* .. versionchanged:: 4.0
On Windows platforms, if the command runs a ``.bat`` or ``.cmd`` script,
it is automatically executed through the command interpreter, ``cmd /c``.
However, paths with spaces may fail if a "short path" is not available.
No intermediate shell is used, so shell operators such as ``>``
are treated as normal arguments.
@ -197,3 +203,5 @@ Options:
is checked. If the variable is not set, the default is ``NONE``.
If ``RESULT_VARIABLE`` or ``RESULTS_VARIABLE`` is supplied,
:variable:`CMAKE_EXECUTE_PROCESS_COMMAND_ERROR_IS_FATAL` is ignored.
.. _`CommandLineToArgvW`: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw

View File

@ -120,7 +120,7 @@ bool cmExecuteProcessCommand(std::vector<std::string> const& args,
.Bind("COMMAND_ERROR_IS_FATAL"_s, &Arguments::CommandErrorIsFatal);
std::vector<std::string> unparsedArguments;
Arguments const arguments = parser.Parse(args, &unparsedArguments);
Arguments arguments = parser.Parse(args, &unparsedArguments);
if (arguments.MaybeReportError(status.GetMakefile())) {
return true;
@ -161,11 +161,21 @@ bool cmExecuteProcessCommand(std::vector<std::string> const& args,
status.SetError(" called with no COMMAND argument.");
return false;
}
for (std::vector<std::string> const& cmd : arguments.Commands) {
for (std::vector<std::string>& cmd : arguments.Commands) {
if (cmd.empty()) {
status.SetError(" given COMMAND argument with no value.");
return false;
}
#ifdef _WIN32
cmsys::Status shortPathRes = cmSystemTools::MaybePrependCmdExe(cmd);
if (!shortPathRes) {
status.GetMakefile().IssueMessage(
MessageType::WARNING,
cmStrCat("Conversion of COMMAND:\n ", cmd[2], '\n',
"to a short path without spaces failed:\n ",
shortPathRes.GetString()));
}
#endif
}
// Parse the timeout string.

View File

@ -63,6 +63,14 @@ if(WIN32 OR CYGWIN)
run_cmake_command(WindowsNoExtension-build ${CMAKE_COMMAND} --build . --config Debug --target RunScript)
endif()
if(CMAKE_HOST_WIN32
# By default, only C: has short paths enabled.
# Since querying with `fsutil 8dot3name query C:`
# requires admin, just test the drive letter.
AND RunCMake_SOURCE_DIR MATCHES "^[Cc]:")
run_cmake_command(WindowsBatch ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/WindowsBatch.cmake)
endif()
if(TEST_STARTUPINFO_EXE)
run_cmake_script(StartupInfo -DTEST_STARTUPINFO_EXE=${TEST_STARTUPINFO_EXE})
endif()

View File

@ -0,0 +1,8 @@
^bat arg1 - arg2 -
bat "arg1 space" - arg2 -
bat arg1 - "arg2 space" -
bat "arg1 space" - "arg2 space" -
cmd arg1 - arg2 -
cmd "arg1 space" - arg2 -
cmd arg1 - "arg2 space" -
cmd "arg1 space" - "arg2 space" -$

View File

@ -0,0 +1,8 @@
execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/with space.bat" arg1 arg2)
execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/with space.Bat" "arg1 space" arg2)
execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/with space.bAT" arg1 "arg2 space")
execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/with space.BAT" "arg1 space" "arg2 space")
execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/with space.cmd" arg1 arg2)
execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/with space.Cmd" "arg1 space" arg2)
execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/with space.cMD" arg1 "arg2 space")
execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/with space.CMD" "arg1 space" "arg2 space")

View File

@ -0,0 +1 @@
@echo bat %1 - %2 -

View File

@ -0,0 +1 @@
@echo cmd %1 - %2 -