babashka / process Goto Github PK
View Code? Open in Web Editor NEWClojure library for shelling out / spawning sub-processes
License: Eclipse Public License 1.0
Clojure library for shelling out / spawning sub-processes
License: Eclipse Public License 1.0
This is less opinionated and same as the Java API.
See tools.build:
:write (.redirectError pb (ProcessBuilder$Redirect/to (jio/file (api/resolve-path err-file))))
:append (.redirectError pb (ProcessBuilder$Redirect/appendTo (jio/file (api/resolve-path err-file))))
It would be handy to get a jar for each build, could we use a SNAPSHOT version during dev?
Also see #77
We also need to document this in the babashka book.
Currently arg0 isn't set so the first argument disappears:
$ bb -e “(babashka.process/exec [\“echo\” \“ok\“])”
prints nothing
But most of the type arg0 should be equal to the program name anyway, so we can do that by default.
(proc/exec ["foo" ".."] {:arg0 "bar"})
The use case for tee is to redirect output to stdout but also to capture it in a file. We could make a built-in construct for this so you can use it to print to stdout but also capture the out stream.
(require '[babashka.process :refer [pipeline pb]]
'[clojure.java.io :as io])
(let [[catp teep] (pipeline (pb ["cat"]) (pb ["tee" "log.txt"] {:out :inherit}))]
;; write to cat every 100 milliseconds 10 times
(future
(with-open [writer (io/writer (:in catp))]
(loop [i 0]
(when (< i 10)
(binding [*out* writer]
(println i)
(flush))
(Thread/sleep 100)
(recur (inc i))))))
@teep ;; wait for the tee process to finish
;; finally we print the file's content
(println (slurp "log.txt")))
(shutdown-agents)
This code is close but doesn't work yet:
(ns tee
(:require [babashka.process :refer [process]]
[clojure.java.io :as io])
(:import [java.io InputStream]))
(defn tee [proc out-1 out-2]
(let [^InputStream in (:out proc)
^java.lang.Process proc (:proc proc)]
(loop [alive? (.isAlive proc) idx 0]
(if-not alive?
(recur (.isAlive proc) idx)
(let [j (.available in)]
(when (pos? j)
(prn :read idx j)
(let [buf (byte-array j)]
(.read in buf idx j)
(io/copy buf out-1)
(io/copy buf out-2)))
(when (.isAlive proc)
(recur alive? (+ idx j))))))))
(def catp (process ["cat"]))
(future
(with-open [writer (io/writer (:in catp))]
(loop [i 0]
(when (< i 10)
(binding [*out* writer]
(println i))
(Thread/sleep 100)
(recur (inc i))))))
(tee catp *out* *out*)
@catp
(shutdown-agents)
See
(process "rm -rf")
Tokenize function already implemented here:
With babashka.process/sh
, :inherit true
doesn't work as intended.
Hey everyone, I wonder how I can use this awesome lib with my clojure project and lein, I tried voom but didn't get anywhere.
Thanks
$ bb -cp src:test -e "(require '[clojure.test :refer :all] 'babashka.process :reload 'babashka.process-test) (clojure.test/run-tests 'babashka.process-test)"
(future
(loop []
(spit "/tmp/log.txt" (str (rand-int 10) "\n") :append true)
(Thread/sleep 100)
(recur)))
(-> (process ["tail" "-f" "/tmp/log.txt"]
{:out :stream
:wait false
:throw false
:err :inherit})
(process ["cat"] #_["grep" "5"] {:out :inherit
:err :inherit
:wait false}))
For comparison, this does work:
(->
(process ["yes"]
{:out :stream
:wait false
:throw false
:err :inherit})
(process ["cat"] {:out :inherit
:err :inherit}))
This also works:
(defn works3 []
(let [proc (process ["bash" "-c" "yes | head"]
{:out :stream
:wait false
:err :inherit})
out (:out proc)]
(binding [*in* (io/reader out)]
(loop []
(when-let [l (read-line)]
(println l)
(recur))))))
repro using clojure
clojure -Sdeps '{:deps {babashka/process {:mvn/version "0.1.0"}}}' -M -e '(require \'[babashka.process :as p]) (-> (p/process ["cd" "nonexistentdir"]) p/check)'
gives me the following output & stacktrace
Exception in thread "main" java.lang.IllegalArgumentException: Multiple methods in multimethod 'simple-dispatch' match dispatch value: class babashka.process.Process -> interface clojure.lang.IDeref and interface clojure.lang.IPersistentMap, and neither is preferred
at clojure.lang.MultiFn.findAndCacheBestMethod(MultiFn.java:179)
at clojure.lang.MultiFn.getMethod(MultiFn.java:150)
at clojure.lang.MultiFn.getFn(MultiFn.java:154)
at clojure.lang.MultiFn.invoke(MultiFn.java:229)
at clojure.pprint$write_out.invokeStatic(pprint_base.clj:194)
at clojure.pprint$pprint_map$fn__11054$fn__11056.invoke(dispatch.clj:113)
at clojure.pprint$pprint_map$fn__11054.invoke(dispatch.clj:113)
at clojure.pprint$pprint_map.invokeStatic(dispatch.clj:112)
at clojure.pprint$pprint_map.invoke(dispatch.clj:106)
at clojure.lang.MultiFn.invoke(MultiFn.java:229)
at clojure.pprint$write_out.invokeStatic(pprint_base.clj:194)
at clojure.pprint$pprint_vector$fn__11039.invoke(dispatch.clj:95)
at clojure.pprint$pprint_vector.invokeStatic(dispatch.clj:94)
at clojure.pprint$pprint_vector.invoke(dispatch.clj:92)
at clojure.lang.MultiFn.invoke(MultiFn.java:229)
at clojure.pprint$write_out.invokeStatic(pprint_base.clj:194)
at clojure.pprint$pprint_map$fn__11054$fn__11056.invoke(dispatch.clj:113)
at clojure.pprint$pprint_map$fn__11054.invoke(dispatch.clj:113)
at clojure.pprint$pprint_map.invokeStatic(dispatch.clj:112)
at clojure.pprint$pprint_map.invoke(dispatch.clj:106)
at clojure.lang.MultiFn.invoke(MultiFn.java:229)
at clojure.pprint$write_out.invokeStatic(pprint_base.clj:194)
at clojure.pprint$pprint_map$fn__11054$fn__11056.invoke(dispatch.clj:113)
at clojure.pprint$pprint_map$fn__11054.invoke(dispatch.clj:113)
at clojure.pprint$pprint_map.invokeStatic(dispatch.clj:112)
at clojure.pprint$pprint_map.invoke(dispatch.clj:106)
at clojure.lang.MultiFn.invoke(MultiFn.java:229)
at clojure.pprint$write_out.invokeStatic(pprint_base.clj:194)
at clojure.pprint$pprint$fn__10295.invoke(pprint_base.clj:249)
at clojure.pprint$pprint.invokeStatic(pprint_base.clj:248)
at clojure.pprint$pprint.invoke(pprint_base.clj:241)
at clojure.pprint$pprint.invokeStatic(pprint_base.clj:245)
at clojure.pprint$pprint.invoke(pprint_base.clj:241)
at clojure.lang.Var.invoke(Var.java:384)
at clojure.main$report_error$fn__9184$fn__9185.invoke(main.clj:603)
at clojure.main$report_error$fn__9184.invoke(main.clj:602)
at clojure.main$report_error.invokeStatic(main.clj:601)
at clojure.main$main.invokeStatic(main.clj:666)
at clojure.main$main.doInvoke(main.clj:616)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.lang.Var.applyTo(Var.java:705)
at clojure.main.main(main.java:40)
Let me know if there's more info I should provide.
So, the idea is that you could use some mechanism to have full control over streams. What I can think right now (that's already on Babashka) is core.async:
(let [out (async/chan)
in (async/chan)
p (process ["grep" "foo"] {:in in :out out})]
(async/>!! in "foobar")
(async/<!! out) ;=> "foobar")
(async/>!! in "quoz")
(async/>!! in "foo")
(async/<!! out) ;=> "foo"
(async/close! in)
(async/<!! out) ;=> nil
@(:exit p)) ;=> 0)
This can be even useful while piping commands, for example:
(let [out (async/chan)]
(process ["ls"] {:out out})
(process ["cat"] {:in out :out :inherit})
nil)
LICENSE
README.md
deps.edn
src
test
nil
:extra-env
for adding extra env
https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/lang/ProcessBuilder.Redirect.html#PIPE is useful as it allows you to close stdin/stdout. In my case I've started a REPL, and I can cause a "clean shutdown" by closing the process stdin. I can't do that with inherit though.
I'm still a bit unsure about some things: maybe :out :string
and :err :string
should be the default. And when it is, maybe :exit
should be the exit code directly, not a future / delay with an exit code. This is also how babashka.curl works. Also maybe throw with the contents of :err
if :exit
is non-zero, unless :throw
is false
.
https://gist.github.com/borkdude/63a86a04f5d97aae4825326c0309dad2
Would be cool if this also composed with ->
.
E.g.:
(process ["foo" "bar"] {:color :green :prepend "FOO"})
This would be useful in babashka tasks where multiple processes are running in parallel so you can distinguish them.
(require '[babashka.process :as p]
'[clojure.java.io :as io])
(with-open [w (io/writer "hello.txt")]
(-> (p/pipeline
(p/pb '[echo foo])
(p/pb '[cat "bb.edn"]))
last
(p/process '[cat] {:out w})
p/check))
(slurp "hello.txt")
(with-open [w (io/writer "hello.txt")]
(->> (p/pipeline
(p/pb '[echo foo])
(p/pb '[cat "bb.edn"] {:out w}))
(run! p/check)))
(slurp "hello.txt")
We should take a look at the options that clojure.java.shell supports and support something similar. Do we need both :out
and :out-enc
options or is just :out :bytes
enough?
The reason for this is that adding a shutdown hook by default creates a potential memory leak problem: the closure will be retained until shutdown. In case of lots of processes, this will create lots of uncollected garbage.
So this works:
(require '[babashka.process :refer :all])
(future
(loop []
(spit "log.txt" (str (rand-int 10) "\n") :append true)
(Thread/sleep 10)
(recur)))
(-> (pb '[tail -f "log.txt"])
(pb '[cat])
(pb '[grep "5"] {:out :inherit})
(start)
)
When there are certain GraalVM classes on the classpath babashka.process
crashes.
I'm guessing that this is because babashka.process
wrongly thinks that it is running in a native GraalVM environment. I noticed it when running nextjournal/clerk
which depends on org.graalvm.js/js
via io.github.nextjournal/markdown
.
$ clojure -Sdeps '{:deps {io.github.nextjournal/clerk {:mvn/version "0.8.451"} babashka/process {:mvn/version "0.1.4"}}}'
Clojure 1.11.1
user=> (require 'babashka.process)
Syntax error (IllegalArgumentException) compiling . at (babashka/process.cljc:528:10).
No matching method exec found taking 3 args for class org.graalvm.nativeimage.ProcessProperties
user=> (boolean (resolve 'org.graalvm.nativeimage.ProcessProperties))
true
user=> (System/getProperty "java.vm.version")
"17.0.3+7"
user=> (System/getProperty "java.vm.name")
"OpenJDK 64-Bit Server VM"
It would be nice if we could download binary files like so:
(def response (-> (process ["curl" "--progress-bar" "http://ipv4.download.thinkbroadband.com/200MB.zip"] {:out bytes :err :inherit}) deref :out))
and the response would be a byte array.
Note that we can do this currently with:
user=> (def response (-> (process ["curl" "--progress-bar" "http://ipv4.download.thinkbroadband.com/200MB.zip"] {:out byte-stream :err :inherit}) deref :out))
######################################################################## 100.0%
#'user/response
user=> (type response)
java.io.ByteArrayOutputStream
user=> (count (.toByteArray response))
209715200
user=> (/ 209715200 1024 1024)
200
:env (assoc (into {} (System/getenv)) "FOO" "BAR")
(-> (process ["ls" "foo"]) (check) (process ["cat"]))
would then succesfully crash because there is no foo
This may support better the case described at the bottom in notes:
bb version: 0.8.2 (Good), 0.8.156 (Bad)
Platform: Windows
A command string passed to shell that works fine in 0.8.2 stopped working in 0.8.156.
build-release-package
{:doc "Generate a RELEASE flavor Web Deploy package, compatible with AWS Elastic Beanstalk."
;; :depends [-clean-tree-test]
:task (let [cmd (str "\"" (:msbuild-path (combo!)) "\""
" /p:Configuration=Release"
" /t:Package"
" /p:DeployIisAppPath=\"Default Web Site\"")]
(shell cmd))}
Expected, and on 0.8.2: The MSBuild command line called successfully,
When same piece is run under 0.8.156,
Microsoft (R) Build Engine version 17.2.1+52cd2da31 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
MSBUILD : error MSB1009: Project file does not exist.
Switch: Default Web Site
Error while executing task: build-release-package
After some digging, I believe the one passed down is:
"D:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\MSBuild\\Current\\Bin\\MSBuild.exe" /p:Configuration=Release /t:Package /p:DeployIisAppPath= Default Web Site
The spaces around Default Web Site
is not kept.
I'm able to make it work by change the \"
to \\\"
or '
This is a deadlock:
(-> (process ["cat"] {#_#_:out (io/file "/tmp/foo.csv") :in (io/file "/Users/borkdude/Downloads/1mb-test_csv.csv")}) check)
since the stream can't write anywhere, it's waiting before it can exit.
This works:
user=> (def csv (with-out-str (-> (process ["cat"] {:out *out* :in (io/file "/Users/borkdude/Downloads/1mb-test_csv.csv")}) check)))
#'user/csv
user=> (count csv)
1000448
This also works:
(def sw (java.io.StringWriter.))
(-> (process ["cat"] {:in (slurp "https://datahub.io/datahq/1mb-test/r/1mb-test.csv") :out sw}) check)
(count (str sw)) ;; 1043005
We implemented :out :string
to make this easier:
(count (-> (process ["cat"] {:in (slurp "https://datahub.io/datahq/1mb-test/r/1mb-test.csv") :out :string}) check :out))
;; 1043005
$ bash -c "echo 'two words' | wc -w"
2
user=> (require '[babashka.process :refer [process check tokenize]])
nil
user=> (-> (process "bash -c \"echo 'two words' | wc -w\"" {:out :string}) deref :out)
"two words\n"
user=> (-> (process ["bash" "-c" "echo 'two words' | wc -w"] {:out :string}) deref :out)
"2\n"
user=> (tokenize "bash -c \"echo 'two words' | wc -w\"")
["bash" "-c" "echo two words" " | wc -w"]
tokenize
should return ["bash" "-c" "echo 'two words' | wc -w"]
Works the other way around; with doubles inside singles:
user=> (tokenize "bash -c 'echo \"two words\" | wc -w'")
["bash" "-c" "echo \"two words\" | wc -w"]
Now I am doing
(with-meta ($ "cmd.exe" "/C" "dir" "/S" "/OD" "/B" "/AD" ) {:dir root})
but the value of root
is not considered.
(process '["cmd.exe" "/C" "dir" "/S" "/OD" "/B" "/AD"] {:dir root})
works as expected.
Hi,
some of the descriptions in the API table of contents are truncated compared to their full docstring, is this a feature or a bug? :)
https://github.com/babashka/process/blob/master/API.md
Perhaps they should include the text up to the first full stop .
? e.g. sh
looks a bit strange with the description ending in the middle of the sentence.
babashka.process
- Shell out in Clojure with simplicity and ease.
$
- Convenience macro around process
*defaults*
- Dynamic var containing overridable default optionscheck
- Takes a process, waits until is finished and throws if exit code is non-zero.destroy
- Takes process or mapdestroy-tree
- Same as destroy
but also destroys all descendantsexec
- Replaces the current process image with the process image specifiedpb
- Returns a process builder (as record).pipeline
- Returns the processes for one pipe created with -> or createsprocess
- Takes a command (vector of strings or objects that will be turnedsh
- Convenience function similar to clojure.java.shell/sh
that setsshell
- Convenience function around process
that was originally in babashka.tasks
.start
- Takes a process builder, calls start and returns a process (as record).tokenize
- Tokenize string to list of individual space separated arguments.Thanks
Seems there is a problem with slashes (as used in most dos-tools for command line arguments/switches).
I.e.
(println (-> (process '[dir "/S" "/OD" "/B" "/AD"]) :out slurp))
prints nothing. Whereas
(println (-> (process '[dir /S /OD /B /AD]) :out slurp))
gives
Type: clojure.lang.ExceptionInfo
Message: [line 5, col 31] Invalid symbol: /S.
Location: c:\ProgramData\DATEV\LOG\Install\VAA1DAV0171\openMostCurrentDir.clj:5:
Phase: parse
Exception in thread "main" java.lang.NullPointerException
at clojure.lang.Numbers.ops(Numbers.java:1068)
at clojure.lang.Numbers.dec(Numbers.java:145)
at babashka.impl.error_handler$error_context.invokeStatic(error_handler.clj:51)
at babashka.impl.error_handler$error_handler.invokeStatic(error_handler.clj:106)
at babashka.main$main$fn__18864.invoke(main.clj:625)
at babashka.main$main.invokeStatic(main.clj:599)
at babashka.main$main.doInvoke(main.clj:462)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:665)
at babashka.main$_main.invokeStatic(main.clj:651)
at babashka.main$_main.doInvoke(main.clj:645)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at babashka.main.main(Unknown Source)
Any way to geht this working?
Waiting for the process to end happens by realizing this delay.
So I can wait for a process to die naturally or destroy it (or (deref process 5000 nil) (bb.process/destroy process))
.
Could be implemented using https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/lang/Process.html#waitFor(long,java.util.concurrent.TimeUnit) ?
When any of the babashka.process/process
:in
, :out
and :err
OPTS
is set to :inherit
, their stream is inherited from the parent process' corresponding stream, which, for clarification, is not the same as the parent process' Clojure *in*
, *out*
and *err*
"streams".
This at first sight might cause confusion to clojurians, because they are accustomed to think at the clojure context and thus their first expectation is for the clojure streams to be inherited. It can also cause some confusing at first puzzling effects, such as the error output of a failing babashka.process/shell
call during an nREPL session to be printed by the nREPL server than send over to the REPL client (since the parent java process in this case is the nREPL server), i.e. the error message is likely to be "hidden" from the user.
Was this redirection at the java level a design decision or rather happened for convenience, or maybe both? I'm trying to ascertain whether it would be a more convenient option to inherit from the clojure streams by default or add an additional option for this or just update the documentation to elaborate just a bit more on the inheritance so that clojurians are less likely to be confused by the side effects. What do you think? :)
Looking at the code, the redirection to the java process happens in the build
fn which works at the java ProcessBuilder level:
process/src/babashka/process.cljc
Line 221 in bd203b7
(defn- build
(^java.lang.ProcessBuilder [cmd] (build cmd nil))
(^java.lang.ProcessBuilder [^java.util.List cmd opts]
(let [
;; ...
pb (cond-> (java.lang.ProcessBuilder. ^java.util.List cmd)
dir (.directory (io/file dir))
env (set-env env)
extra-env (add-env extra-env))]
(case out
:inherit (.redirectOutput pb ProcessBuilder$Redirect/INHERIT)
:write (.redirectOutput pb (ProcessBuilder$Redirect/to (io/file out-file)))
:append (.redirectOutput pb (ProcessBuilder$Redirect/appendTo (io/file out-file)))
nil)
(case err
:inherit (.redirectError pb ProcessBuilder$Redirect/INHERIT)
:write (.redirectError pb (ProcessBuilder$Redirect/to (io/file err-file)))
:append (.redirectError pb (ProcessBuilder$Redirect/appendTo (io/file err-file)))
nil)
(case in
:inherit (.redirectInput pb ProcessBuilder$Redirect/INHERIT)
nil)
pb)))
To reproduce the Hudini effect with babashka.process/shell
and Emacs:
babashka
with Emacs Cider(babashka.process/shell "ls" "-----")
, an error is thrown but there is no indication what has gone wrong, just that the process exited with error code 2:clojure.lang.ExceptionInfo:
{:type :sci/error, :line 1, :column 1, :message "",
#...
}
at sci.impl.utils$rethrow_with_location_of_node.invokeStatic (utils.cljc:128)
#...
Caused by: clojure.lang.ExceptionInfo:
{:proc #object[java.lang.ProcessImpl 0x1d310663 "Process[pid=28332, exitValue=2]"], :exit 2
# ...
*nrepl-server ...*
buffer:Started nREPL server at 127.0.0.1:1667
For more info visit: https://book.babashka.org/#_nrepl
ls: unknown option -- ---
Try '/usr/bin/ls --help' for more information.
:err
OPTS we get some sense out of babashka.process/check
with (babashka.process/shell {:err nil } "ls" "-----")
:clojure.lang.ExceptionInfo: ls: unknown option -- ---
Try '/usr/bin/ls --help' for more information.
;; ...
{:type :sci/error, :line 1, :column 1,
Caused by: clojure.lang.ExceptionInfo: ls: unknown option -- ---
Try '/usr/bin/ls --help' for more information.
{:proc #object[java.lang.ProcessImpl 0x3b4ce6c0 "Process[pid=22280, exitValue=2]"],
;; ...
Thanks
On Windows 10, given a Path
environment variable:
And a fiddle.clj
(which I've plunked in a fresh clone of babashka/process):
(require '[babashka.process :as p])
(-> (p/process ["clojure" "-e" "(println (System/getenv \"PATH\"))"]
{:extra-env {"PATH" (str "some-path" (System/getProperty "path.separator") (System/getenv "PATH"))}})
:out
slurp
(subs 0 50)
println)
From your shell, run:
clojure -M fiddle.clj
As a Windows user, I would expect :extra-env
to respect Windows environment variable name case insensitivity when overriding existing environment variables.
This means any case of path
should override Path
(including PATH
).
If this works, I expect to see some-path
printed as the first path returned by PATH.
We do not see out some-path
prefixed:
>clojure -M fiddle.clj
C:\Program Files\Parallels\Parallels Tools\Applica
If I alter fiddle.clj
to change PATH
to Path
for :extra-env
:
(require '[babashka.process :as p])
(-> (p/process ["clojure" "-e" "(println (System/getenv \"PATH\"))"]
{:extra-env {"Path" (str "some-path" (System/getProperty "path.separator") (System/getenv "PATH"))}})
:out
slurp
(subs 0 50)
println)
I will get the expected result:
>clojure -M fiddle.clj
some-path;C:\Program Files\Parallels\Parallels Too
(Notice I did not alter PATH
for getenv
calls).
On Windows, environment variables are case insensitive.
The JDK behaves accordingly for System/getenv
on Windows, but ProcessBuilder.environment
does not.
Any case variant of path
passed to System/getenv
returns value for Path
.
user=> (= (System/getenv "Path") (System/getenv "PATH") (System/getenv "pAtH"))
true
I think ProcessBuilder.environment
holds both Path
and PATH
. I'm not sure how one gets chosen over the other when the env is set for a Windows process.
Side Note: To keep things confusing, Windows does show Path
for cmd.exe
set
, but PATH
for env
, but Path
is the underlying environment variable name.
If we take the stance that:
Then maybe we will do nothing.
Pros:
Cons:
Add a tip to docs about :extra-env
on Windows stating that although Windows environment variable names are case insensitive, an environment variable will not be overridden unless the provided name matches the original case of the variable name.
Questions: Is the original name always Path
on Windows? Can it be path
or PATH
?
Pros:
Cons:
If an :extra-env
variable name is a case insensitive match for an existing variable name it is a match and the variable's value's entry should be replaced.
But what if someone specifies an :extra-env
of {"PATH" "boo" "path" "foo"}
?
Well, that's probably unlikely enough to ignore as undefined behaviour.
Pros:
PATH
sets PATH
on Linux/macOS but appropriately any case of Path
on Windows.:extra-env
behaves like the Windows shells and therefore matches what Windows users would expect.Concerns:
Cons:
I like option 3 the for pros it lists.
But, I'm sure I've not thought of everything, please do chime in with your thoughts.
Happy to PR if you decide option 3 is the path (pun intended) as well.
Example: "c:\\Users\\borkdude\\bin\\graal.bat 1 2 3"
Add option :shutdown-hook
which defaults to (fn [proc] (.destroy (:proc proc)))
and can be overridden with any arity-1 function.
check
fails with the following error when passed the *err*
stream:
Execution error (IllegalArgumentException) at babashka.process/check (process.cljc:106).
Cannot open <#object[java.io.PrintWriter 0x784abd3e "java.io.PrintWriter@784abd3e"]> as an InputStream.
To reproduce on the REPL:
;; Clojure 1.11.1
user=> (require '[babashka.process :as p])
;; => nil
user=> (-> (p/process '[ls ----] {:err *err*}) p/check)
ls: unknown option -- --
Try '/usr/bin/ls --help' for more information.
;; => Execution error (IllegalArgumentException) at babashka.process/check (process.cljc:106).
;; => Cannot open <#object[java.io.PrintWriter 0x784abd3e "java.io.PrintWriter@784abd3e"]> as an InputStream.
PR to follow.
Adding a cljdoc config will allow babashka/process docs to build correctly on cljdoc.
I shall follow up with a PR.
Hi,
Please add Windows support too.
e.g. on Windows there's Bash (from Git) , "lein" https://chocolatey.org/packages/Lein ( and "boot" https://chocolatey.org/packages/boot-clj ) but there's no "clojure" (since the clojure/tools seem to be mostly a "hombrew" only solution now :( ) .
So:
export BABASHKA_CLASSPATH=$(clojure -Sdeps '{:deps {babashka/babashka.process {:sha "6c348b5213c0c77ebbdfcf2f5da71da04afee377" :git/url "https://github.com/babashka/babashka.process"}}}' -Spath)
won't work
Thanks in advance.
Since we prefer data over functions over macros, can we maybe represent a chain of processes as data rather than function calls?
Hello @borkdude I just happen to notice something odd when using $
to command a docker build process. I have the following code:
(def base-build-image-name "my-image")
(def jarfile "target/app.jar")
($ docker build
-t ~base-build-image-name
--build-arg jarfile=~jarfile
-f resources/Dockerfile .)
However, the expansion I see in the terminal after executing this command is the following:
docker build -t my-image --build-arg jarfile= target/app.jar -f resources/Dockerfile .
If you look closely there is a space after the =
sign. I started debugging this and I have a possible fix that I would like to discuss. Looks like $
macro produces a list of symbols to be manipulated during the process and in the end we get the following vector:
["docker" "build" "-t" "my-image-name" "--build-arg" "jarfile=" "target/app.jar" "-f" "resources/Dockerfile" "."]
The issue here is that "jarfile=target/app.jar" should be together. I think this is kind of general for these situations and instead of messing around the other parts of the code I created a function to be executed after this operation
process/src/babashka/process.clj
Line 115 in ac12195
What you think about this? I couldn't came up with more examples where this would be useful too.
It seems $
macro only work in one command execution, not pipelined (tested in bb).
user=> (-> ($ echo hello) ($ sed p) deref :out slurp println)
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: No method in multimethod 'do-copy' for dispatch value: [java.lang.ProcessImpl$ProcessPipeInputStream java.lang.ProcessImpl$ProcessPipeInputStream] [at <repl>:13:1]
user=> (-> (process '[echo hello]) (process '[sed p]) deref :out slurp println)
hello
hello
When using babashka.process with Clojure 1.9 a ClassNotFoundException
is thrown at load time.
❯ clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.9.0"} babashka/process {:mvn/version "0.1.3"}}}'
Clojure 1.9.0
user=> (require '[babashka.process :as p])
CompilerException java.lang.ClassNotFoundException: org.graalvm.nativeimage.ProcessProperties, compiling:(process.cljc:493:12)
Seems like an oddity in Clojure behaviour, it does not occur on Clojure 1.10+
❯ clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.10.0"} babashka/process {:mvn/version "0.1.3"}}}'
Clojure 1.10.0
user=> (require '[babashka.process :as p])
nil
Code that triggers failure is here.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.