diff --git a/CHANGELOG.md b/CHANGELOG.md index e1b98fd..bdbd0d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ [Babashka process](https://github.com/babashka/process) Clojure library for shelling out / spawning sub-processes +## 0.6.24 (2025-12-07) + +- [#181](https://github.com/babashka/process/issues/181): support `:discard` or `ProcessBuilder$Redirect` as `:out` and `:err` options + ## 0.6.23 (2025-03-31) - [#163](https://github.com/babashka/process/issues/163), [#164](https://github.com/babashka/process/issues/164): Program resolution strategy for `exec` and Windows now matches macOS/Linux/PowerShell ([@lread](https://github.com/lread)) diff --git a/src/babashka/process.cljc b/src/babashka/process.cljc index 24ead72..1e9bab0 100644 --- a/src/babashka/process.cljc +++ b/src/babashka/process.cljc @@ -219,7 +219,12 @@ :escape default-escape :program-resolver default-program-resolver}) -(defn- normalize-opts [{:keys [:out :err :in :inherit] :as opts}] +(def ^java.io.File null-file + (delay (io/file (if windows? + "NUL" + "/dev/null")))) + +(defn- normalize-opts [{:keys [out err in inherit] :as opts}] (cond-> opts (and inherit (not out)) (-> (assoc :out :inherit)) @@ -265,13 +270,21 @@ :inherit (.redirectOutput pb ProcessBuilder$Redirect/INHERIT) :write (.redirectOutput pb (ProcessBuilder$Redirect/to (io/file (str out-file)))) :append (.redirectOutput pb (ProcessBuilder$Redirect/appendTo (io/file (str out-file)))) - nil) + :discard (.redirectOutput pb (if-before-jdk8 + (ProcessBuilder$Redirect/to @null-file) + ProcessBuilder$Redirect/DISCARD)) + (when (instance? java.lang.ProcessBuilder$Redirect out) + (.redirectOutput pb out))) (case err :out (.redirectErrorStream pb true) :inherit (.redirectError pb ProcessBuilder$Redirect/INHERIT) :write (.redirectError pb (ProcessBuilder$Redirect/to (io/file (str err-file)))) :append (.redirectError pb (ProcessBuilder$Redirect/appendTo (io/file (str err-file)))) - nil) + :discard (.redirectError pb (if-before-jdk8 + (ProcessBuilder$Redirect/to @null-file) + ProcessBuilder$Redirect/DISCARD)) + (when (instance? java.lang.ProcessBuilder$Redirect err) + (.redirectError pb err))) (case in :inherit (.redirectInput pb ProcessBuilder$Redirect/INHERIT) (when (or (instance? java.io.File in) @@ -385,12 +398,14 @@ stderr (.getErrorStream proc) out (if (and out (or (identical? :string out) (identical? :bytes out) - (not (keyword? out)))) + (and (not (keyword? out)) + (not (instance? java.lang.ProcessBuilder$Redirect out))))) (future (copy stdout out out-enc)) stdout) err (if (and err (or (identical? :string err) (identical? :bytes err) - (not (keyword? err)))) + (and (not (keyword? err)) + (not (instance? java.lang.ProcessBuilder$Redirect err))))) (future (copy stderr err err-enc)) stderr)] ;; wrap in futures, see https://github.com/clojure/clojure/commit/7def88afe28221ad78f8d045ddbd87b5230cb03e @@ -470,6 +485,7 @@ For writing output to a file, you can set `:out` and `:err` to a `java.io.File` object, or a keyword: - `:write` + an additional `:out-file`/`:err-file` + file to write to the file. - `:append` + an additional `:out-file`/`:err-file` + file to append to the file. + To discard `:out` or `:err`, use `:discard` - `:prev`: output from `:prev` will be piped to the input of this process. Overrides `:in`. - `:inherit`: if true, sets `:in`, `:out` and `:err` to `:inherit`. - `:dir`: working directory. diff --git a/test/babashka/process_test.cljc b/test/babashka/process_test.cljc index 08e98cf..a36016e 100644 --- a/test/babashka/process_test.cljc +++ b/test/babashka/process_test.cljc @@ -627,3 +627,17 @@ :out)] (is (bytes? result)) (is (= (seq ba) (seq result)))))) + +(defmacro if-pre-jdk9+ [then else] + (if (identical? ::pre-jdk9 + (try (import 'java.lang.ProcessHandle) + (catch Exception _ ::pre-jdk9))) + then else)) + +(deftest discard-test + (when-let [bb (u/find-bb)] + (doseq [out [:discard (if-pre-jdk9+ :discard java.lang.ProcessBuilder$Redirect/DISCARD)]] + (is (= "" (slurp (:out @(p/process {:out out} bb "-e" "(println :dude) (binding [*out* *err*] (println :bye))"))))) + (is (= (with-out-str (println :bye)) (slurp (:err @(p/process {:out out} bb "-e" "(println :dude) (binding [*out* *err*] (println :bye))"))))) + (is (= "" (slurp (:err @(p/process {:err out} bb "-e" "(println :dude) (binding [*out* *err*] (println :bye))"))))) + (is (= (with-out-str (println :dude)) (slurp (:out @(p/process {:err out} bb "-e" "(println :dude) (binding [*out* *err*] (println :bye))"))))))))