Java process API

Description

For a long time, we’ve had the clojure.java.shell api in the core library, but it does not take advantage of the newer I/O support available in the Java process API in more recent JVMs. This proposal adds a new namespace clojure.java.process with support for wrapping the Java process API and providing some conveniences for common use cases.

The primary api is start:

------------------------- clojure.java.process/start ([& opts+args]) Starts an external command as args and optional leading opts map: :in - a ProcessBuilder.Redirect (default = :pipe) or :inherit :out - a ProcessBuilder.Redirect (default = :pipe) or :inherit :discard :err - a ProcessBuilder.Redirect (default = :pipe) or :inherit :discard :stdout :dir - directory to run the command from, default="." :env - {env-var value} of environment variables (all strings) Returns an ILookup containing the java.lang.Process in :process and the streams :in :out :err. The map is also an IDeref that waits for process exit and returns the exit code.

A helper is provided to make ProcessBuilder.Redirect instances that read from (for :in) or write to (for :out and :err) a stream:

------------------------- clojure.java.process/from-file ([file]) Coerce f to a file per clojure.java.io/file and return a ProcessBuilder.Redirect reading from the file. This can be passed to 'start' in :in. ------------------------- clojure.java.process/to-file ([file & {:keys [append], :as opts}]) Coerce f to a file per clojure.java.io/file and return a ProcessBuilder.Redirect writing to the file. Set ':append' in opts to append. This can be passed to 'start' in :out or :err.

The return value of start is a map with keys :process :in :out :err that also can be deref’ed to wait for the process to exit. For the common case of waiting for a process to exit successfully, use ok?:

------------------------- clojure.java.process/ok? ([process-map]) Given the map returned from 'start', wait for the process to exit and then return true on success

To capture a stream to a string, use capture:

------------------------- clojure.java.process/capture ([input-stream & opts]) Read from input-stream until EOF and return a String (or nil if 0 length). Takes same opts as clojure.java.io/copy - :buffer and :encoding

A useful helper covers the common case of executing a process, waiting for exit, and capturing the stdout to a string:

------------------------- clojure.java.process/exec ([& opts+args]) Execute a command and on successful exit, return the captured output, else throw RuntimeException. Args are the same as 'start' and options if supplied override the default 'exec' settings.

Some examples:

(use 'clojure.java.process) ;; shell out and inherit the i/o (start {:out :inherit, :err :stdout} "ls" "-l") ;; write out and err to files, wait for process to exit, return exit code @(start {:out (to-file "out") :err (to-file "err")} "ls" "-l") ;; capture output to string (-> (start "ls" "-l") :out capture) ;; with exec (exec "ls" "-l") ;; read input from file (exec {:in (from-file "deps.edn")} "wc" "-l")

Patch: clj-2759-4.patch

Pulled build compilation changes for this namespace out of the patch, will include in https://clojure.atlassian.net/browse/CLJ-2761 so the patches can apply.

Screened by: fogus

Environment

None

Attachments

4
  • 06 Apr 2023, 05:16 PM
  • 31 Mar 2023, 02:52 PM
  • 28 Mar 2023, 02:53 AM
  • 14 Mar 2023, 09:21 PM

Activity

Show:

Alex MillerApril 14, 2023 at 2:03 PM

Released in 1.12.0-alpha2

Michael FogusMarch 28, 2023 at 4:03 PM

Fair point regarding :cmd. Screened with newest ILookup doc change.

Alex MillerMarch 28, 2023 at 2:57 AM

Added a -2 patch, only change is s/map/ILookup/ in that docstring. I looked at the :cmd thing - in other libs there are front-end fns or pluggable steps that can modify the command, but those don’t exist here - the :cmd would just be the args already being passed in. Also, despite a fair amount of looking, I was not able to find anyone actually using this field in the output, so I’m going to leave it out for now.

Michael FogusMarch 23, 2023 at 1:51 PM

Quick note that this does not cleanly apply after applying the patch for https://clojure.atlassian.net/browse/CLJ-2757. There is a conflict in build.xml. This is not a big deal but wanted to make it known.

Alex MillerMarch 22, 2023 at 2:11 PM

A common feature of process libraries is to capture as part of the process details the command that was run. This is saved in the start/launch/ps/etc return map under a key such as :cmd. Is that intentionally excluded in this library?

No, should probably add that. Some things like this are available in Java 9+ via the Process .info method - since we are straddling an API expansion, I tried to go light in this area.

Fixed

Details

Assignee

Reporter

Labels

Approval

Ok

Patch

Code

Priority

Affects versions

Fix versions

Created March 14, 2023 at 9:17 PM
Updated April 14, 2023 at 2:03 PM
Resolved April 14, 2023 at 2:03 PM

Flag notifications