Add RunProcess, a safer alternative to Exec - #6536
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6536 +/- ##
==========================================
+ Coverage 78.97% 79.01% +0.04%
==========================================
Files 683 683
Lines 294944 295015 +71
Branches 8638 8638
==========================================
+ Hits 232919 233098 +179
+ Misses 60214 60098 -116
- Partials 1811 1819 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
6d1efcd to
3eb8de7
Compare
lgoettgens
left a comment
There was a problem hiding this comment.
Looks good overall. Some maybe stupid questions
|
|
||
| # resolve the command name via the system PATH, unless it already is a path | ||
| if not '/' in cmd and not (ARCH_IS_WINDOWS() and '\\' in cmd) then | ||
| a := PathSystemProgram( cmd ); |
There was a problem hiding this comment.
The docstring states that DirectoriesSystemPrograms is used for lookup. Is this implementation here semantically equivalent?
There was a problem hiding this comment.
Not quite equivalent, and the docstring was the inaccurate part. PathSystemProgram walks the same DirectoriesSystemPrograms() directories but additionally requires IsExecutableFile, whereas plain Filename would also accept a non-executable file of that name. The stricter behaviour is what we want, so Claude reworded the documentation to name PathSystemProgram directly.
Following this up turned out to be worthwhile, as it exposed two further problems, now fixed:
RunProcess("")silently returnedstatus := fail.PathSystemProgram("")returns a directory, and directories passIsExecutableFile, so the lookup "succeeded" and the exec failed in the kernel. Empty command names are now rejected, with a test.statusis not always an exit code.FuncExecuteProcessmaps 255 tofail, and a failedexecvein the forked child exits with 255 — sofailmeans "could not execute", and a program genuinely exiting with 255 is indistinguishable from it. Both facts are now documented. (I'll ponder whether perhaps we should/could change this inExecuteProcess).
Claude also documented that a cmd containing a path separator is resolved relative to GAP's current directory, not the directory option. This is inherited Process behaviour, but surprising enough to state.
There was a problem hiding this comment.
The exit code 255 vs. fail issue is being tackled in PR #6565 but that's work in progress.
3eb8de7 to
9a8b66d
Compare
`Exec` hands its concatenated arguments to a shell. That makes it hard to pass arguments containing spaces or quotes, ties the behaviour to whichever shell is installed, discards the exit code, and wires the child process to the user's terminal. `RunProcess` takes the program and its arguments as separate strings and runs it directly, so nothing needs quoting and no shell is involved. It returns a record holding the exit code and, by default, the captured output. Anything beyond the command line is passed in an optional trailing options record with the keys `directory`, `input` and `output`; unknown keys are rejected, so that `error` can be added once GAP is able to capture the standard error stream of a child process (see #4657). The price is that shell features such as redirections and wildcard expansion are no longer available, so `Exec` stays. Convert the callers in `helpview.gi` and `streams.gi`. The terminal browsers keep the user's terminal explicitly, as they are interactive. Co-authored-by: Claude <noreply@anthropic.com>
9a8b66d to
e4d4b60
Compare
Grew out of #5103, and hence closes #5103.
Exechands its concatenated arguments to a shell. That makes it hard to pass arguments containing spaces or quotes, ties the behaviour to whichever shell happens to be installed, discards the exit code, and wires the child process to the user's terminal.RunProcesstakes the program and its arguments as separate strings and runs it directly:For example:
The price is that shell features such as redirections and wildcard expansion are gone, so
Execstays; its documentation now points here for new code.What changed since #5103
The draft let the working directory and the streams appear as positional arguments in any order. Following the discussion there (@ChrisJefferson, @wilfwilson), everything beyond the command line now goes into an optional trailing options record with the keys
directory,inputandoutput:Unknown keys are rejected rather than ignored. That is the point of the record: GAP currently cannot capture a child process's standard error at all (#4657), and this leaves room to add an
errorkey later without breaking any caller — and without older GAP versions silently dropping it.The name is the other thing #5103 stalled on.
RunProcesssits next to the existingProcessoperation, reads as a verb, and matches Mathematica'sRunProcessand Python'ssubprocess.run.Arguments must be strings or integers; anything else is an error. Deliberately not
String-ing arbitrary objects, as that would close off future extensions.Co-authored-by: Claude Opus 5 noreply@anthropic.com