GithubHelp home page GithubHelp logo

babashka / process Goto Github PK

View Code? Open in Web Editor NEW
204.0 6.0 29.0 267 KB

Clojure library for shelling out / spawning sub-processes

License: Eclipse Public License 1.0

Clojure 99.83% Shell 0.17%
babashka clojure

process's People

Contributors

agilecreativity avatar bobisageek avatar borkdude avatar brunchboy avatar burinc avatar casselc avatar frenchy64 avatar grzm avatar hansbugge avatar holyjak avatar hugoduncan avatar ikappaki avatar jangler avatar lispyclouds avatar lread avatar nha avatar pesterhazy avatar severeoverfl0w avatar sohalt avatar wandersoncferreira avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

process's Issues

Snapshot builds?

It would be handy to get a jar for each build, could we use a SNAPSHOT version during dev?

exec arg0 should be the first (program) name by default

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"})

Documentation about tee

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)

Also see https://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/output/TeeOutputStream.html

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)

How can it be installed with lein

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

Add bb tests

$ bb -cp src:test -e "(require '[clojure.test :refer :all] 'babashka.process :reload 'babashka.process-test) (clojure.test/run-tests 'babashka.process-test)"

Figure out why this example doesn't work as expected

(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))))))

exception in clojure.pprint for `check` with failed process

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.

Support for fine-tunning stdout/stderr/stdin?

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

misc notes

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.

Piping to file in last process of pipeline doesn't work

(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")

compatibility with clojure.java.shell

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?

Add note / ADR about why :shutdown isn't set by default

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.

Support chaining of pb

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)
    )

babashka.process crashes when there are certain GraalVM classes on the classpath

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"

Support :out :bytes

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

0.8.2 -> 0.8.156 task shell tokenize command differently causing command falure

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 '

Document issue with large string output and check / deref

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

Duplicate text in README

image

Was this intentional?

I'm not sure exactly what the intent was. But as it reads right now, it leaves me a bit confused.

tokenize incorrectly handles nested single quotes inside double quotes.

$ 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"]

Support :dir on $-macro

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.

api table of contents truncated descriptions

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.

Table of contents

  • babashka.process - Shell out in Clojure with simplicity and ease.
    • $ - Convenience macro around process
    • *defaults* - Dynamic var containing overridable default options
    • check - Takes a process, waits until is finished and throws if exit code is non-zero.
    • destroy - Takes process or map
    • destroy-tree - Same as destroy but also destroys all descendants
    • exec - Replaces the current process image with the process image specified
    • pb - Returns a process builder (as record).
    • pipeline - Returns the processes for one pipe created with -> or creates
    • process - Takes a command (vector of strings or objects that will be turned
    • sh - Convenience function similar to clojure.java.shell/sh that sets
    • shell - 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

Windows: using DOS command line arguments is not possible

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?

`process` :inherit semantics

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:

(defn- build

(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:

  1. Jack-in to babashka with Emacs Cider
  2. In the repl run (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
# ...
  1. The error is hidden instead in the *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.

  1. While if we nilify the :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

:extra-env does not respect case insensitivy of Windows environment variables

Repro

On Windows 10, given a Path environment variable:
image

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

Expected Behaviour

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.

Actual Behaviour

We do not see out some-path prefixed:

>clojure -M fiddle.clj
C:\Program Files\Parallels\Parallels Tools\Applica

Diagnosis

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.

Option 1: Do nothing

If we take the stance that:

Then maybe we will do nothing.

Pros:

  • easy for us

Cons:

  • confusing for users who bump into this (it was for me).

Option 2: Document Peculiarity

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:

  • at least we've explained the quirk for users

Cons:

  • We might have to do more work to understand and document peculiarities than fix the issue.

Option 3: Tweak env merging for Windows only

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:

  • cross-platform support, ex. 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:

  • what if JDK folks agree this is a bug and finally fix this for Windows? Our fix would still be active but unnecessary for JDK versions with fix.

Cons:

  • can't think of one other than more code, more maintenance, more chance for bugs.

Proposal

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.

Next steps

Happy to PR if you decide option 3 is the path (pun intended) as well.

Shutdown hook

Add option :shutdown-hook which defaults to (fn [proc] (.destroy (:proc proc))) and can be overridden with any arity-1 function.

`check` fails when `process`' `:err` opt is a clj stream

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.

Add cljdoc config

Adding a cljdoc config will allow babashka/process docs to build correctly on cljdoc.

I shall follow up with a PR.

Add Windows Support.

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.

Data driven approach

Since we prefer data over functions over macros, can we maybe represent a chain of processes as data rather than function calls?

Incorrect expansion when using = in the arguments for some script

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

cmd (mapv str-fn cmd)
which will scan the vector above and if it encounters a parameter with a "=" sign it will merge the next element into it.

What you think about this? I couldn't came up with more examples where this would be useful too.

'$' macro does not work in '->'

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

Check for graal fails under Clojure 1.9

Symptom

When using babashka.process with Clojure 1.9 a ClassNotFoundException is thrown at load time.

Reproduction

❯ 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) 

Diagnosis

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.