|
| 1 | +:- module(process, [ |
| 2 | + process_create/3, |
| 3 | + process_id/2, |
| 4 | + process_release/1, |
| 5 | + process_wait/2, |
| 6 | + process_wait/3, |
| 7 | + process_kill/1 |
| 8 | +]). |
| 9 | + |
| 10 | +:- use_module(library(error)). |
| 11 | +:- use_module(library(iso_ext)). |
| 12 | +:- use_module(library(lists), [member/2, maplist/2, maplist/3, append/2]). |
| 13 | +:- use_module(library(reif), [tfilter/3, memberd_t/3]). |
| 14 | + |
| 15 | + |
| 16 | +%% process_create(+Exe, +Args:list, +Options). |
| 17 | +% |
| 18 | +% Create a new process by executing the executable Exe and passing it the Arguments Args. |
| 19 | +% |
| 20 | +% Note: On windows please take note of [windows argument splitting](https://doc.rust-lang.org/std/process/index.html#windows-argument-splitting). |
| 21 | +% |
| 22 | +% Options is a list consisting of the following options: |
| 23 | +% |
| 24 | +% * `cwd(+Path)` Set the processes working directory to `Path` |
| 25 | +% * `process(-Process)` `Process` will be assigned a process handle for the spawned process |
| 26 | +% * `env(+List)` Don't inherit environment variables and set the variables defined in `List` |
| 27 | +% * `environment(+List)` Inherit environment variables and set/override the variables defined in `List` |
| 28 | +% * `stdin(Spec)`, `stdout(Spec)` or `stderr(Spec)` defines how to redirect the spawned processes io streams |
| 29 | +% |
| 30 | +% The elements of `List` in `env(List)`/`environment(List)` List must be string pairs using `=/2`. |
| 31 | +% `env/1` and `environment/1` may not be both specified. |
| 32 | +% |
| 33 | +% The following stdio `Spec` are available: |
| 34 | +% |
| 35 | +% * `std` inherit the current processes original stdio streams (does currently not account for stdio being changed by `set_input` or `set_output`) |
| 36 | +% * `file(+Path)` attach the strea to the file at `Path` |
| 37 | +% * `null` discards writes and behaves as eof for read. Equivalent to using `file(/dev/null)` |
| 38 | +% * `pipe(-Steam)` create a new pipe and assigne one end to the created process and the other end to `Stream` |
| 39 | +% |
| 40 | +% Specifying an option multiple times is an error, when an option is not specified the following defaults apply: |
| 41 | +% |
| 42 | +% - `cwd(".")` |
| 43 | +% - `environment([])` |
| 44 | +% - `stdin(std)`, `stdout(std)`, `stderr(std)` |
| 45 | +% |
| 46 | +process_create(Exe, Args, Options) :- call_with_error_context(process_create_(Exe, Args, Options), predicate-process_create/3). |
| 47 | + |
| 48 | +process_create_(Exe, Args, Options) :- |
| 49 | + must_be(chars, Exe), |
| 50 | + must_be(list, Args), |
| 51 | + maplist(must_be(chars), Args), |
| 52 | + must_be(list, Options), |
| 53 | + check_options( |
| 54 | + [ |
| 55 | + option([stdin], valid_stdio, stdin(std), stdin(Stdin)), |
| 56 | + option([stdout], valid_stdio, stdout(std), stdout(Stdout)), |
| 57 | + option([stderr], valid_stdio, stderr(std), stderr(Stderr)), |
| 58 | + option([env, environment], valid_env, environment([]), Env), |
| 59 | + option([process], valid_uninit_process, process(_), process(Process)), |
| 60 | + option([cwd], valid_cwd, cwd("."), cwd(Cwd)) |
| 61 | + ], |
| 62 | + Options, |
| 63 | + process_create_option |
| 64 | + ), |
| 65 | + Stdin =.. Stdin1, |
| 66 | + Stdout =.. Stdout1, |
| 67 | + Stderr =.. Stderr1, |
| 68 | + simplify_env(Env, Env1), |
| 69 | + '$process_create'(Exe, Args, Stdin1, Stdout1, Stderr1, Env1, Cwd, Process). |
| 70 | + |
| 71 | +%% process_id(+Process, -Pid). |
| 72 | +% |
| 73 | +process_id(Process, Pid) :- call_with_error_context(process_id_(Process, Pid), predicate-process_id/2). |
| 74 | + |
| 75 | +process_id_(Process, Pid) :- |
| 76 | + valid_process(Process), |
| 77 | + must_be(var, Pid), |
| 78 | + '$process_id'(Process, Pid). |
| 79 | + |
| 80 | +%% process_wait(+Process, Status). |
| 81 | +% |
| 82 | +% See `process_create/3` with `Options = []` |
| 83 | +% |
| 84 | +process_wait(Process, Status) :- call_with_error_context(process_wait(Process, Status, []), predicate-process_wait/2). |
| 85 | + |
| 86 | + |
| 87 | +%% process_wait(+Process, Status, Options). |
| 88 | +% |
| 89 | +% Wait for the process behind the process handle `Process` to exit. |
| 90 | +% |
| 91 | +% When the process exits regulary `Status` will be unified with `exit(Exit)` where `Exit` is the processes exit code. |
| 92 | +% When the process exits was killed `Status` will be unified with `killed(Signal)` where `Signal` is the signal number that killed the process. |
| 93 | +% When the process doesn't exit before the timeout `Status` will be unified with `timeout`. |
| 94 | +% |
| 95 | +% `Options` is a a list of the following options |
| 96 | +% |
| 97 | +% * timeout(Timeout) supported values for `Timeout` are 0 or `infinite` |
| 98 | +% |
| 99 | +% Each options may be specified at most once, when an option is not specified the following defaults apply: |
| 100 | +% |
| 101 | +% - timeout(infinite) |
| 102 | +% |
| 103 | +process_wait(Process, Status, Options) :- call_with_error_context(process_wait_(Process, Status, Options), predicate-process_wait/3). |
| 104 | + |
| 105 | +process_wait_(Process, Status, Options) :- |
| 106 | + valid_process(Process), |
| 107 | + check_options( |
| 108 | + [ |
| 109 | + option([timeout], valid_timeout, timeout(infinite), timeout(Timeout)) |
| 110 | + ], |
| 111 | + Options, |
| 112 | + process_wait_option |
| 113 | + ), |
| 114 | + '$process_wait'(Process, Exit, Timeout), |
| 115 | + Exit = Status. |
| 116 | + |
| 117 | +valid_timeout(timeout(infinite)). |
| 118 | +valid_timeout(timeout(0)). |
| 119 | + |
| 120 | + |
| 121 | +%% process_kill(+Process). |
| 122 | +% |
| 123 | +% Kill the process using the process handle `Process`. |
| 124 | +% On Unix this sends SIGKILL. |
| 125 | +% |
| 126 | +% Only works for processes spawned with `process_create/3` that have not yet been release with `process_release/1` |
| 127 | +% |
| 128 | +process_kill(Process) :- call_with_error_context(process_kill_(Process), predicate-process_kill/1). |
| 129 | + |
| 130 | +process_kill_(Process) :- |
| 131 | + valid_process(Process), |
| 132 | + '$process_kill'(Process). |
| 133 | + |
| 134 | +%% process_release(+Process) |
| 135 | +% |
| 136 | +% wait for the process to exit (if not already) and release process handle `Process` |
| 137 | +% |
| 138 | +% It's an error if `Process` is not a valid process handle |
| 139 | +% |
| 140 | +process_release(Process) :- call_with_error_context(process_release_(Process), predicate-process_release/1). |
| 141 | + |
| 142 | +process_release_(Process) :- |
| 143 | + valid_process(Process), |
| 144 | + process_wait(Process, _), |
| 145 | + '$process_release'(Process). |
| 146 | + |
| 147 | + |
| 148 | +must_be_known_options(Valid, Options, Domain) :- call_with_error_context(must_be_known_options_(Valid, [], Options, Domain),predicate-must_be_known_options/3). |
| 149 | + |
| 150 | +must_be_known_options_(_, _, [], _). |
| 151 | +must_be_known_options_(Valid, Found, [X|XS], Domain) :- |
| 152 | + ( functor(X, Option, 1) -> true |
| 153 | + ; domain_error(Domain, X, []) |
| 154 | + ) , |
| 155 | + ( member(Option, Found) -> domain_error(non_duplicate_options, Option , []) |
| 156 | + ; member(Option, Valid) -> true |
| 157 | + ; domain_error(Domain, Option, []) |
| 158 | + ), |
| 159 | + must_be_known_options_(Valid, [Option | Found], XS, Domain). |
| 160 | + |
| 161 | +check_options(KnownOptions, Options, Domain) :- call_with_error_context(check_options_(KnownOptions, Options, Domain), predicate-check_options/3). |
| 162 | + |
| 163 | +check_options_(KnownOptions, Options, Domain) :- |
| 164 | + maplist(option_names, KnownOptions, Namess), |
| 165 | + append(Namess, Names), |
| 166 | + must_be_known_options(Names, Options, Domain), |
| 167 | + extract_options(KnownOptions, Options). |
| 168 | + |
| 169 | +option_names(option(Names,_,_,_), Names). |
| 170 | + |
| 171 | +extract_options(KnownOptions, Options) :- call_with_error_context(extract_options_(KnownOptions, Options), predicate-extract_options/2). |
| 172 | + |
| 173 | +extract_options_([], _). |
| 174 | +extract_options_([X | XS], Options) :- |
| 175 | + option(Kinds, Pred, Default, Choice) = X, |
| 176 | + tfilter(find_option(Kinds), Options, Solutions), |
| 177 | + ( Solutions = [] -> Choice = Default |
| 178 | + ; Solutions = [Provided] -> functor(Pred, Name, Arity), ArityP1 is Arity+1, call_with_error_context(call(Pred, Provided),predicate-Name/ArityP1), Choice = Provided |
| 179 | + ; domain_error(non_conflicting_options, Solutions, []) |
| 180 | + ), |
| 181 | + extract_options_(XS, Options). |
| 182 | + |
| 183 | +find_option(Names, Found, T) :- |
| 184 | + functor(Found, Name, 1), |
| 185 | + memberd_t(Name, Names, T). |
| 186 | + |
| 187 | +valid_stdio(IO) :- arg(1, IO, Arg), |
| 188 | + ( var(Arg) -> instantiation_error([]) |
| 189 | + ; valid_stdio_(Arg) -> true |
| 190 | + ; domain_error(stdio_spec, Arg, []) |
| 191 | + ). |
| 192 | + |
| 193 | +valid_stdio_(std). |
| 194 | +valid_stdio_(null). |
| 195 | +valid_stdio_(pipe(Stream)) :- must_be(var, Stream). |
| 196 | +valid_stdio_(file(Path)) :- must_be(chars, Path). |
| 197 | + |
| 198 | +valid_env(env(E)) :- |
| 199 | + must_be(list, E), |
| 200 | + ( valid_env_(E) -> true |
| 201 | + ; domain_error(process_create_option, env(E), []) |
| 202 | + ). |
| 203 | +valid_env(environment(E)) :- |
| 204 | + must_be(list, E), |
| 205 | + ( valid_env_(E) -> true |
| 206 | + ; domain_error(process_create_option, environment(E), []) |
| 207 | + ). |
| 208 | + |
| 209 | +valid_env_([]). |
| 210 | +valid_env_([N=V|ES]) :- |
| 211 | + must_be(chars, N), |
| 212 | + must_be(chars, V), |
| 213 | + valid_env_(ES). |
| 214 | + |
| 215 | +valid_uninit_process(process(Process)) :- must_be(var, Process). |
| 216 | + |
| 217 | +valid_process(Process) :- var(Process) -> instantiation_error([]) ; true. |
| 218 | + |
| 219 | +valid_cwd(cwd(Cwd)) :- must_be(chars, Cwd). |
| 220 | + |
| 221 | +simplify_env(E, [Kind, Envs1]) :- E =.. [Kind, Envs], simplify_env_(Envs, Envs1). |
| 222 | + |
| 223 | +simplify_env_([],[]). |
| 224 | +simplify_env_([N=V|E],[[N, V]|E1]) :- simplify_env_(E, E1). |
0 commit comments