Add support to start .cmd shims via a cmd.exe sub-shell on Windows
If I understand the current code right, in ProcessWin32.c
the function kwsysProcessCreate()
invokes CreateProcessW()
directly.
This is fine for executables, but fails for .cmd
shims (and probably several other Windows specific scripts), which require starting via a cmd.exe
sub-shell.
In the npm/xpm ecosystem, all applications are started via symbolic links, but on Windows, where there are no symbolic links, .cmd
shims are used, and all npm/xpm applications are practically .cmd
shims; this makes CMake unusable with npm/xpm applications.
My current solution is a patch that checks if the command is an .cmd
, and starts it via a cmd.exe
sub-shell:
#if 1
/* This patch adds support for running .cmd shims
via a cmd.exe sub-shell. */
wchar_t* wFrom = cp->Commands[index];
size_t fromLength = wcslen(wFrom);
wchar_t* wp = wFrom;
/* Skip initial spaces. */
while (*wp == L' ' || *wp == L'\t') {
++wp;
}
if (*wp == L'"') {
while (*++wp != L'"')
;
/* Stop on the ending quote. */
} else {
while (*wp != L' ' && *wp != L'\t') {
++wp;
}
/* Stop on the first space. */
}
wchar_t* wCmd;
wchar_t wDotCmd[] = L".cmd";
if (wcsncmp(wp - wcslen(wDotCmd), wDotCmd, wcslen(wDotCmd)) != 0) {
/* If not an explicit .cmd, execute it directly. */
wCmd = malloc((fromLength + 1) * sizeof(wchar_t));
wcscpy(wCmd, wFrom);
} else {
/* Run it via a cmd.exe subshell. */
wchar_t wCmdExe[] = L"cmd.exe /s /c \" ";
wchar_t wCmdEnd[] = L" \"";
wchar_t escapedChars[] = L"&\\<>^|";
bool inQuotedString = false;
/* Count the number of characters that need to be escaped. */
size_t escapedCount = 0;
for (size_t i = 0; i < fromLength; ++i) {
wchar_t wChr = wFrom[i];
if (wChr == L'"') {
inQuotedString = !inQuotedString;
}
if (!inQuotedString && wcsrchr(escapedChars, wChr) != NULL) {
++escapedCount;
}
}
wCmd = malloc(
(wcslen(wCmdExe) + fromLength + escapedCount + wcslen(wCmdEnd) + 1)
* sizeof(wchar_t)
);
wcscpy(wCmd, wCmdExe);
if (escapedCount > 0) {
inQuotedString = false;
wchar_t* wTo = wCmd + wcslen(wCmdExe);
for (size_t i = 0; i < fromLength; ++i) {
wchar_t wChr = wFrom[i];
if (wChr == L'"') {
inQuotedString = !inQuotedString;
}
if (!inQuotedString && wcsrchr(escapedChars, wChr) != NULL) {
*wTo++ = L'^';
}
*wTo++ = wChr;
}
*wTo++ = L'\0';
} else {
wcscat(wCmd, wFrom);
}
wcscat(wCmd, wCmdEnd);
}
/* Create inherited copies of the handles. */
(error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput,
si->hStdInput, 1)) ||
(error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdOutput,
si->hStdOutput, 0)) ||
(error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdError,
si->hStdError, 0)) ||
/* Create the process. */
(!CreateProcessW(0, wCmd, 0, 0, TRUE, creationFlags, 0, 0,
&si->StartupInfo, &cp->ProcessInformation[index]) &&
(error = GetLastError()));
free(wCmd);
#else
/* Create inherited copies of the handles. */
(error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput,
si->hStdInput, 1)) ||
(error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdOutput,
si->hStdOutput, 0)) ||
(error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdError,
si->hStdError, 0)) ||
/* Create the process. */
(!CreateProcessW(0, cp->Commands[index], 0, 0, TRUE, creationFlags, 0, 0,
&si->StartupInfo, &cp->ProcessInformation[index]) &&
(error = GetLastError()));
#endif
A more elaborate solution should probably check for other extensions too (like .bat
), but I don't know the full list.
Any thoughts on this issue? Should I submit a merge request with this feature?