99(ns clojure.clr.process
1010 " A process invocation API wrapping the Java System.Diagnostic.Process API.
1111
12- The primary function here is 'start' which starts a process and handles the
13- streams as directed. It returns a map that contains keys to access the streams
14- (if available) and the Java Process object. It is also deref-able to wait for
15- process exit.
16-
17- Use ‘slurp' to capture the output of a process stream, and 'ok?’ to wait for a
18- non-error exit. The 'exec' function handles the common case of `start'ing a
19- process, waiting for process exit, slurp, and return stdout."
12+ The primary function is 'start' which starts a process and handles the
13+ streams as directed. It returns the Process object. Use 'exit-ref' to wait
14+ for completion and receive the exit value, and ‘stdout', 'stderr', 'stdin'
15+ to access the process streams. The 'exec' function handles the common case
16+ to 'start' a process, wait for process exit, and return stdout."
2017 (:require
2118 [clojure.clr.io :as cio]
2219 [clojure.string :as str])
2320 (:import
24- [System.IO StringWriter FileInfo]
21+ [System.IO FileInfo StreamReader StreamWriter ]
2522 [System.Diagnostics Process ProcessStartInfo]
2623 [clojure.lang IDeref IBlockingDeref]))
2724
2825(set! *warn-on-reflection* true )
2926
30-
3127(def ^:private ^FileInfo null-file
3228 (delay
3329 (cio/file-info
4844 :clear-env - if true, remove all inherited parent env vars
4945 :env - {env-var value} of environment variables to set (all strings)
5046
51- Returns an ILookup containing the System.Diagnostics.Process in :process and the
52- streams :in :out :err. The map is also an IDeref that waits for process exit
53- and returns the exit code."
47+ Returns the java.lang.Process."
5448 {:added " 1.12" }
55- [& opts+args]
49+ ^Process [& opts+args]
5650 (let [[opts command] (if (map? (first opts+args))
5751 [(first opts+args) (rest opts+args)]
5852 [{} opts+args])
7165 (when env
7266 (run! (fn [[k v]] (.Add si-env k v)) env))
7367
74- (let [process ( Process/Start si)
75- m { :process process
76- :in ( when redirect-in ( .get_StandardInput process))
77- :out ( when redirect-out ( .get_StandardOutput process))
78- :err ( when redirect-err ( .get_StandardError process))} ]
79- ( reify
80- clojure.lang.ILookup
81- ( valAt [_ key] ( get m key))
82- ( valAt [_ key not-found] ( get m key not-found))
83-
84- IDeref
85- ( deref [_]
86- ( .WaitForExit process)
87- ( .ExitCode process))
88-
89- IBlockingDeref
90- ( deref [_ timeout timeout-value]
91- ( if ( .WaitForExit process ( int timeout))
92- ( .ExitCode process)
93- timeout- value))))))
94-
95- ( defn ok?
96- " Given the map returned from 'start', wait for the process to exit
97- and then return true on success "
98- { :added " 1.12 " }
99- [ process-map]
100- ( let [p ^Process ( :process process-map)]
101- ( .WaitForExit p)
102- ( zero? (.ExitCode p)) ))
103-
104-
68+ (Process/Start si)) )
69+
70+ ( defn stdin
71+ " Given a process, return the stdin of the external process (an OutputStream) "
72+ ^StreamWriter [^Process process]
73+ ( .get_StandardInput process))
74+
75+ ( defn stdout
76+ " Given a process, return the stdout of the external process (an InputStream) "
77+ ^StreamReader [^Process process]
78+ ( .get_StandardOutput process))
79+
80+ ( defn stderr
81+ " Given a process, return the stderr of the external process (an InputStream) "
82+ ^StreamReader [^Process process]
83+ ( .get_StandardError process))
84+
85+ ( defn exit-ref
86+ " Given a Process (the output of 'start'), return a reference that can be
87+ used to wait for process completion then returns the exit value. "
88+ [^Process process]
89+ ( reify
90+ IDeref
91+ ( deref [_]
92+ ( .WaitForExit process)
93+ ( .ExitCode process))
94+ IBlockingDeref
95+ ( deref [_ timeout timeout-value]
96+ ( if (.WaitForExit process ( int timeout ))
97+ ( .ExitCode process)
98+ timeout-value))))
10599
106100#_(defn io-task
107101 {:skip-wiki true }
108- [f]
102+ [^Runnable f]
109103 (let [f (bound-fn* f)
110- fut (clojure.lang.Future f)]
104+ fut (.submit ^ExecutorService io-executor ^Callable f)]
111105 (reify
112106 clojure.lang.IDeref
113107 (deref [_] (#'clojure.core/deref-future fut))
117111 (#'clojure.core/deref-future fut timeout-ms timeout-val))
118112 clojure.lang.IPending
119113 (isRealized [_] (.isDone fut))
120- clojure.lang .Future
114+ java.util.concurrent .Future
121115 (get [_] (.get fut))
122116 (get [_ timeout unit] (.get fut timeout unit))
123117 (isCancelled [_] (.isCancelled fut))
138132 (.get fut timeout-ms timeout-val))
139133 clojure.lang.IPending
140134 (isRealized [_] (.isDone fut)))))
141-
142-
135+
143136
144137(defn exec
145138 " Execute a command and on successful exit, return the captured output,
150143 (let [[opts command] (if (map? (first opts+args))
151144 [(first opts+args) (rest opts+args)]
152145 [{} opts+args])
153- opts (merge {:redirect-err true :redirect-out true } opts)]
154- (let [state (apply start opts command)
155- captured (io-task #(slurp (:out state)))]
156- (if (ok? state)
146+ opts (merge {:err :inherit } opts)]
147+ (let [proc (apply start opts command)
148+ captured (io-task #(slurp (stdout proc)))
149+ exit (deref (exit-ref proc))]
150+ (if (zero? exit)
157151 @captured
158- (throw (Exception. (str " Process failed with exit=" ( .ExitCode ^Process ( :process state)) )))))))
152+ (throw (Exception. (str " Process failed with exit=" exit )))))))
159153
160154(comment
161155 ; ; shell out and inherit the i/o
162156 (start {:out :inherit , :err :stdout } " ls" " -l" )
163157
164158 ; ; write out and err to files, wait for process to exit, return exit code
165- @(start {:out (to-file " out" ) :err (to-file " err" )} " ls" " -l" )
159+ @(exit-ref ( start {:out (to-file " out" ) :err (to-file " err" )} " ls" " -l" ) )
166160
167161 ; ; capture output to string
168- (-> (start " ls" " -l" ) :out slurp)
162+ (-> (start " ls" " -l" ) stdout slurp)
169163
170164 ; ; with exec
171165 (exec " ls" " -l" )
172166
173167 ; ; read input from file
174- (exec {:in (from-file " deps.edn" )} " wc" " -l" )
168+ (-> ( exec {:in (from-file " deps.edn" )} " wc" " -l" ) clojure.string/trim parse-long )
175169 )
0 commit comments