Process.hx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. package asys;
  2. import haxe.Error;
  3. import haxe.NoData;
  4. import haxe.async.*;
  5. import haxe.io.*;
  6. import asys.net.Socket;
  7. import asys.io.*;
  8. import asys.uv.UVProcessSpawnFlags;
  9. private typedef Native =
  10. #if doc_gen
  11. Void;
  12. #elseif eval
  13. eval.uv.Process;
  14. #elseif hl
  15. hl.uv.Process;
  16. #elseif neko
  17. neko.uv.Process;
  18. #else
  19. #error "process not supported on this platform"
  20. #end
  21. private typedef NativeProcessIO =
  22. #if doc_gen
  23. Void;
  24. #elseif eval
  25. eval.uv.Process.ProcessIO;
  26. #elseif hl
  27. hl.uv.Process.ProcessIO;
  28. #elseif neko
  29. neko.uv.Process.ProcessIO;
  30. #else
  31. #error "process not supported on this platform"
  32. #end
  33. /**
  34. Options for spawning a process. See `Process.spawn`.
  35. **/
  36. typedef ProcessSpawnOptions = {
  37. ?cwd:FilePath,
  38. ?env:Map<String, String>,
  39. ?argv0:String,
  40. ?stdio:Array<ProcessIO>,
  41. ?detached:Bool,
  42. ?uid:Int,
  43. ?gid:Int,
  44. // ?shell:?,
  45. ?windowsVerbatimArguments:Bool,
  46. ?windowsHide:Bool
  47. };
  48. /**
  49. Class representing a spawned process.
  50. **/
  51. class Process {
  52. /**
  53. Execute the given `command` with `args` (none by default). `options` can be
  54. specified to change the way the process is spawned.
  55. `options.stdio` is an optional array of `ProcessIO` specifications which
  56. can be used to define the file descriptors for the new process:
  57. - `Ignore` - skip the current position. No stream or pipe will be open for
  58. this index.
  59. - `Inherit` - inherit the corresponding file descriptor from the current
  60. process. Shares standard input, standard output, and standard error in
  61. index 0, 1, and 2, respectively. In index 3 or higher, `Inherit` has the
  62. same effect as `Ignore`.
  63. - `Pipe(readable, writable, ?pipe)` - create or use a pipe. `readable` and
  64. `writable` specify whether the pipe will be readable and writable from
  65. the point of view of the spawned process. If `pipe` is given, it is used
  66. directly, otherwise a new pipe is created.
  67. - `Ipc` - create an IPC (inter-process comunication) pipe. Only one may be
  68. specified in `options.stdio`. This special pipe will not have an entry in
  69. the `stdio` array of the resulting process; instead, messages can be sent
  70. using the `send` method, and received over `messageSignal`. IPC pipes
  71. allow sending and receiving structured Haxe data, as well as connected
  72. sockets and pipes.
  73. Pipes are made available in the `stdio` array afther the process is
  74. spawned. Standard file descriptors have their own variables:
  75. - `stdin` - set to point to a pipe in index 0, if it exists and is
  76. read-only for the spawned process.
  77. - `stdout` - set to point to a pipe in index 1, if it exists and is
  78. write-only for the spawned process.
  79. - `stderr` - set to point to a pipe in index 2, if it exists and is
  80. write-only for the spawned process.
  81. If `options.stdio` is not given,
  82. `[Pipe(true, false), Pipe(false, true), Pipe(false, true)]` is used as a
  83. default.
  84. @param options.cwd Path to the working directory. Defaults to the current
  85. working directory if not given.
  86. @param options.env Environment variables. Defaults to the environment
  87. variables of the current process if not given.
  88. @param options.argv0 First entry in the `argv` array for the spawned
  89. process. Defaults to `command` if not given.
  90. @param options.stdio Array of `ProcessIO` specifications, see above.
  91. @param options.detached When `true`, creates a detached process which can
  92. continue running after the current process exits. Note that `unref` must
  93. be called on the spawned process otherwise the event loop of the current
  94. process is kept allive.
  95. @param options.uid User identifier.
  96. @param options.gid Group identifier.
  97. @param options.windowsVerbatimArguments (Windows only.) Do not perform
  98. automatic quoting or escaping of arguments.
  99. @param options.windowsHide (Windows only.) Automatically hide the window of
  100. the spawned process.
  101. **/
  102. public static function spawn(command:String, ?args:Array<String>, ?options:ProcessSpawnOptions):Process {
  103. var proc = new Process();
  104. var flags:UVProcessSpawnFlags = None;
  105. if (options == null)
  106. options = {};
  107. if (options.detached)
  108. flags |= UVProcessSpawnFlags.Detached;
  109. if (options.uid != null)
  110. flags |= UVProcessSpawnFlags.SetUid;
  111. if (options.gid != null)
  112. flags |= UVProcessSpawnFlags.SetGid;
  113. if (options.windowsVerbatimArguments)
  114. flags |= UVProcessSpawnFlags.WindowsVerbatimArguments;
  115. if (options.windowsHide)
  116. flags |= UVProcessSpawnFlags.WindowsHide;
  117. if (options.stdio == null)
  118. options.stdio = [Pipe(true, false), Pipe(false, true), Pipe(false, true)];
  119. var stdin:IWritable = null;
  120. var stdout:IReadable = null;
  121. var stderr:IReadable = null;
  122. var stdioPipes = [];
  123. var ipc:Socket = null;
  124. var nativeStdio:Array<NativeProcessIO> = [
  125. for (i in 0...options.stdio.length)
  126. switch (options.stdio[i]) {
  127. case Ignore:
  128. Ignore;
  129. case Inherit:
  130. Inherit;
  131. case Pipe(r, w, pipe):
  132. if (pipe == null) {
  133. pipe = Socket.create();
  134. @:privateAccess pipe.initPipe(false);
  135. } else {
  136. if (@:privateAccess pipe.native == null)
  137. throw "invalid pipe";
  138. }
  139. switch (i) {
  140. case 0 if (r && !w):
  141. stdin = pipe;
  142. case 1 if (!r && w):
  143. stdout = pipe;
  144. case 2 if (!r && w):
  145. stderr = pipe;
  146. case _:
  147. }
  148. stdioPipes[i] = pipe;
  149. Pipe(r, w, @:privateAccess pipe.native);
  150. case Ipc:
  151. if (ipc != null)
  152. throw "only one IPC pipe can be specified for a process";
  153. ipc = Socket.create();
  154. @:privateAccess ipc.initPipe(true);
  155. Ipc(@:privateAccess ipc.native);
  156. }
  157. ];
  158. var args = args != null ? args : [];
  159. if (options.argv0 != null)
  160. args.unshift(options.argv0);
  161. else
  162. args.unshift(command);
  163. var native = new Native(
  164. (err, data) -> proc.exitSignal.emit(data),
  165. command,
  166. args,
  167. options.env != null ? [ for (k => v in options.env) '$k=$v' ] : [],
  168. options.cwd != null ? @:privateAccess options.cwd.get_raw() : Sys.getCwd(),
  169. flags,
  170. nativeStdio,
  171. options.uid != null ? options.uid : 0,
  172. options.gid != null ? options.gid : 0
  173. );
  174. proc.native = native;
  175. if (ipc != null) {
  176. proc.connected = true;
  177. proc.ipc = ipc;
  178. proc.ipcOut = @:privateAccess new asys.io.IpcSerializer(ipc);
  179. proc.ipcIn = @:privateAccess new asys.io.IpcUnserializer(ipc);
  180. proc.messageSignal = new ArraySignal(); //proc.ipcIn.messageSignal;
  181. proc.ipcIn.messageSignal.on(message -> proc.messageSignal.emit(message));
  182. }
  183. proc.stdin = stdin;
  184. proc.stdout = stdout;
  185. proc.stderr = stderr;
  186. proc.stdio = stdioPipes;
  187. return proc;
  188. }
  189. /**
  190. Emitted when `this` process and all of its pipes are closed.
  191. **/
  192. public final closeSignal:Signal<NoData> = new ArraySignal();
  193. // public final disconnectSignal:Signal<NoData> = new ArraySignal(); // IPC
  194. /**
  195. Emitted when an error occurs during communication with `this` process.
  196. **/
  197. public final errorSignal:Signal<Error> = new ArraySignal();
  198. /**
  199. Emitted when `this` process exits, potentially due to a signal.
  200. **/
  201. public final exitSignal:Signal<ProcessExit> = new ArraySignal();
  202. /**
  203. Emitted when a message is received over IPC. The process must be created
  204. with an `Ipc` entry in `options.stdio`; see `Process.spawn`.
  205. **/
  206. public var messageSignal(default, null):Signal<IpcMessage>;
  207. public var connected(default, null):Bool = false;
  208. public var killed:Bool;
  209. private function get_pid():Int {
  210. return native.getPid();
  211. }
  212. /**
  213. Process identifier of `this` process. A PID uniquely identifies a process
  214. on its host machine for the duration of its lifetime.
  215. **/
  216. public var pid(get, never):Int;
  217. /**
  218. Standard input. May be `null` - see `options.stdio` in `spawn`.
  219. **/
  220. public var stdin:IWritable;
  221. /**
  222. Standard output. May be `null` - see `options.stdio` in `spawn`.
  223. **/
  224. public var stdout:IReadable;
  225. /**
  226. Standard error. May be `null` - see `options.stdio` in `spawn`.
  227. **/
  228. public var stderr:IReadable;
  229. /**
  230. Pipes created between the current (host) process and `this` (spawned)
  231. process. The order corresponds to the `ProcessIO` specifiers in
  232. `options.stdio` in `spawn`. This array can be used to access non-standard
  233. pipes, i.e. file descriptors 3 and higher, as well as file descriptors 0-2
  234. with non-standard read/write access.
  235. **/
  236. public var stdio:Array<Socket>;
  237. var native:Native;
  238. var ipc:Socket;
  239. var ipcOut:asys.io.IpcSerializer;
  240. var ipcIn:asys.io.IpcUnserializer;
  241. // public function disconnect():Void; // IPC
  242. /**
  243. Send a signal to `this` process.
  244. **/
  245. public function kill(?signal:Int = 7):Void {
  246. native.kill(signal);
  247. }
  248. /**
  249. Close `this` process handle and all pipes in `stdio`.
  250. **/
  251. public function close(?cb:Callback<NoData>):Void {
  252. var needed = 1;
  253. var closed = 0;
  254. function close(err:Error, _:NoData):Void {
  255. closed++;
  256. if (closed == needed && cb != null)
  257. cb(null, new NoData());
  258. }
  259. for (pipe in stdio) {
  260. if (pipe != null) {
  261. needed++;
  262. pipe.destroy(close);
  263. }
  264. }
  265. if (connected) {
  266. needed++;
  267. ipc.destroy(close);
  268. }
  269. native.close(close);
  270. }
  271. /**
  272. Send `data` to the process over the IPC channel. The process must be
  273. created with an `Ipc` entry in `options.stdio`; see `Process.spawn`.
  274. **/
  275. public function send(message:IpcMessage):Void {
  276. if (!connected)
  277. throw "IPC not connected";
  278. ipcOut.write(message);
  279. }
  280. public function ref():Void {
  281. native.ref();
  282. }
  283. public function unref():Void {
  284. native.unref();
  285. }
  286. private function new() {}
  287. }