On Windows, G_spawn_ex() and the G_popen_* functions always run the child through cmd.exe /c, so finding the program is left to cmd.exe and inherits its limits, notably the ~8191 character handling of PATH. spawn.c already contains find_program(), which resolves a program against PATH/PATHEXT directly, but it is only used when shell is 0, which no caller triggers, so it is effectively dead code. Resolving the program with find_program() and passing it to CreateProcess() directly would remove the cmd.exe layer.
Details (analysis generated with Claude Code)
The Windows do_spawn() in lib/gis/spawn.c hardcodes the shell:
status = win_spawn(command, sp->args, env, sp->directory, handles,
sp->background, 1);
With shell=1, win_spawn() builds %COMSPEC% /c "program args..." and passes lpApplicationName=NULL, so cmd.exe performs the lookup of the program. With shell=0, win_spawn() resolves the program itself with find_program() and passes it as lpApplicationName -- this path is never taken.
Going through cmd.exe cost a CI failure: r.coin runs r.stats via G_popen_read(), and when the process environment's PATH grew past what cmd.exe handles, cmd.exe reported 'r.stats' is not recognized as an internal or external command even though the directory with r.stats.exe was at the front of PATH. Every process spawned from Python with the same PATH (via CreateProcessW) kept working; only the cmd.exe-mediated spawn failed. Analysis in #7732, resulting r.coin behavior in #7734.
An extra cmd.exe process per spawn and a second layer of quoting rules also go away.
Batch files (e.g., the .bat wrappers for scripts in GISBASE\bin) still need cmd.exe as the interpreter, so the decision has to be per resolved program: if find_program() resolves to .bat/.cmd, run cmd.exe on the resolved absolute path (which still avoids the PATH search by cmd.exe); otherwise call CreateProcess() on the program directly. Whether any caller relies on other cmd.exe behavior needs checking.
On Windows,
G_spawn_ex()and theG_popen_*functions always run the child throughcmd.exe /c, so finding the program is left to cmd.exe and inherits its limits, notably the ~8191 character handling ofPATH.spawn.calready containsfind_program(), which resolves a program againstPATH/PATHEXTdirectly, but it is only used whenshellis 0, which no caller triggers, so it is effectively dead code. Resolving the program withfind_program()and passing it toCreateProcess()directly would remove the cmd.exe layer.Details (analysis generated with Claude Code)
The Windows
do_spawn()inlib/gis/spawn.chardcodes the shell:With
shell=1,win_spawn()builds%COMSPEC% /c "program args..."and passeslpApplicationName=NULL, so cmd.exe performs the lookup of the program. Withshell=0,win_spawn()resolves the program itself withfind_program()and passes it aslpApplicationName-- this path is never taken.Going through cmd.exe cost a CI failure: r.coin runs r.stats via
G_popen_read(), and when the process environment'sPATHgrew past what cmd.exe handles, cmd.exe reported'r.stats' is not recognized as an internal or external commandeven though the directory withr.stats.exewas at the front ofPATH. Every process spawned from Python with the samePATH(viaCreateProcessW) kept working; only the cmd.exe-mediated spawn failed. Analysis in #7732, resulting r.coin behavior in #7734.An extra
cmd.exeprocess per spawn and a second layer of quoting rules also go away.Batch files (e.g., the
.batwrappers for scripts inGISBASE\bin) still need cmd.exe as the interpreter, so the decision has to be per resolved program: iffind_program()resolves to.bat/.cmd, run cmd.exe on the resolved absolute path (which still avoids thePATHsearch by cmd.exe); otherwise callCreateProcess()on the program directly. Whether any caller relies on other cmd.exe behavior needs checking.