babashka

https://github.com/babashka/babashka. Also see #sci, #nbb and #babashka-circleci-builds .
maxp 2021-04-13T03:30:32.071800Z

About parallel :depends . what about

:depends ["sequential" ... ]
or
:depends #{"parallel" ..}
?

borkdude 2021-04-13T07:30:55.072400Z

@maxp Interesting, I like the idea. What if you want to execute p1 sequentially and then p2 and p3 in parallel? [p1 #{p2 p3}]] ?

borkdude 2021-04-13T07:32:05.073100Z

maybe that case is getting too complex and one should just make another step in between

pithyless 2021-04-13T07:51:33.078300Z

:depends #{"parent" ...} implies that the task understands the internal details of parent and whether it can be parallelized. Having a task return future or value leaves that decision in the task. The latter kind of orchestration looks similar to the approach in pedestal interceptors and pathom resolvers: leave the details of sync/async to the the task itself, describe a tree of dependencies separately, and then a higher-level orchestrator is just responsible for calling each of them when it is appropriate (and blocking when necessary).

pithyless 2021-04-13T07:54:14.080Z

I figure, if it the orchestration gets any more complicated than that, maybe it's time to revisit a configuration ala onyx.

borkdude 2021-04-13T08:10:39.082200Z

@pithyless I think for something like make / bb.edn it's the other way around, at least in some cases. At work I now have this:

{:tasks {dev-cljs {:doc "Runs front-end compilation"
                   :task (clojure "-M:frontend:cljs/dev")}
         dev-less {:doc "Compiles less"
                   :task (clojure "-M:frontend:less/dev")}
         dev-backend {:task (clojure (str "-A:backend:backend/dev:" platform-alias)
                                     "-X" "dre.standalone/start")}
         -dev {:depends [dev-cljs dev-less dev-backend]}
         dev {:doc "Runs app in dev mode. Compiles cljs, less and runs JVM app in parallel."
              :task (shell "bb run --parallel -dev")}}}
I want dev-cljs, dev-less and dev-backend to run in parallel, but those tasks don't need to know that I want to do that, because they can also run standalone. I make the decision in the dev task (by invoking bb with the --parallel option) but if the proposal of maxp was implemented, I could leave out the dummy -dev task.

borkdude 2021-04-13T08:14:22.083400Z

I think make runs anything in parallel if you use -j4 (at most 4 jobs in parallel).

2021-04-13T08:24:08.084700Z

Wild idea and not properly thought through; if tasks are properly isolated, parallel could be the default, “sequential” could be a debugging option?

borkdude 2021-04-13T08:25:56.085400Z

I think sequential has its place too perhaps. When you run everything in parallel, the output may look more garbled, etc. So having some way to control it might be nice

👍 1
borkdude 2021-04-13T08:26:25.085800Z

Some people may depend on side effects happening in p1 first, etc

borkdude 2021-04-13T08:26:58.086400Z

Although then p2 should depend on p1

maxp 2021-04-13T08:31:16.088200Z

And good question - what means 'parallel' in this case. some special task does fork/join?

maxp 2021-04-13T08:32:29.089200Z

btw, for my mind 'depends' does not mean 'execute in strict particular order'

borkdude 2021-04-13T08:33:32.090300Z

A nice example provided by @grazfather is this one: https://gist.github.com/borkdude/356669d9b829fe5a891e44f920105f26 When you run it with bb coffeep it takes 11 seconds. When you run it with bb run --parallel coffeep it takes 6 seconds, because bb runs some of the dependencies in parallel.

borkdude 2021-04-13T08:34:22.091100Z

@maxp I think that's reasonable: if you say #{p1 p2} you don't care about the order, so bb can (and will by default?) optimize this by running them side by side. And wait for all of them to finish before continuing.

pithyless 2021-04-13T08:39:40.094700Z

What about just :depends [p1 p2] and the rule is always: all these tasks are dependencies that may by run in any-order and in parallel. Then you can supply a -j0 (sequential, any order) to -j4 to (in parallel, any order). If semantics are important (either ordering or using return values from one task in another) then those semantics will be explicit in the code - ie. don't use depends but write out the code in the :task ?

borkdude 2021-04-13T08:42:28.097100Z

@pithyless I think the nice thing about #{p1 p2} is that you have finer control over which things you want to run in parallel. E.g. for my work tasks I have:

clean (do (shell "bash -c 'rm -rf frontend/public/js/app.js'")
                   (shell "bash -c 'rm -rf frontend/public/js/app.out'")
                   (shell "bash -c 'rm -rf frontend/public/css/main*'"))
         dev:clean {:depends [clean dev]}
and I want clean to finish before dev is started when I run dev:clean, but for my dev task here (https://clojurians.slack.com/archives/CLX41ASCS/p1618301439082200) I do want to run the deps in parallel

borkdude 2021-04-13T08:43:43.098600Z

and I think you will use #{p1 p2} more sparingly, only for deps that take a few seconds or more probably

pithyless 2021-04-13T08:45:16.100200Z

yeah, I understand; I actually do like the #{p1 p2} and [p1 p2] distinction; I'm just worried we may go down a sugar-syntax rabbit hole. (1) Do we support something like [p1 #{p2 p3} p4]? (2) Will the semantics be obvious to me when I look at a script in a month, without looking up the babashka docs? :)

borkdude 2021-04-13T08:46:30.101400Z

@pithyless for [p1 #{p2 p3} p4] I intend not to support it. if you want this, then make it so: p0: depends [p1 p2 p3] p2: depends #{p4 p5}

pithyless 2021-04-13T08:49:45.103200Z

@borkdude BTW, how does your dev example work in practice? are those 3 processes that startup and block? Do they garble the output or do they not print to stdout?

borkdude 2021-04-13T08:51:20.104300Z

@pithyless those three processes just run in the background and the dev task waits for them to finish, which is never, or until I hit ctrl-c. I have a script for this:

#!/usr/bin/env bb

(ns dev)

(require '[babashka.process :refer [destroy-tree process]])

(def opts {:out :inherit
           :err :inherit
           :shutdown destroy-tree})

(defn cljs []
  (process ["clojure" "-Sforce" "-M:frontend:cljs/dev"] opts))

(defn less []
  (process ["clojure" "-Sforce" "-M:frontend:less/dev"] opts))

(defn clojure []
  (process ["clojure" "-Sforce"
            "-A:backend:backend/dev"
            "-X" "dre.standalone/start"]
           (assoc opts :in :inherit)))

(cljs)
(less)
(-> @(clojure) :exit (System/exit))
but now I can do all of this with bb tasks if I want

borkdude 2021-04-13T08:51:45.104800Z

and yes, the output may become garbled, but in practice this doesn't bother me that much, doesn't happen that often

borkdude 2021-04-13T08:53:18.105700Z

so the above script effectively becomes https://clojurians.slack.com/archives/CLX41ASCS/p1618301439082200

borkdude 2021-04-13T08:55:14.106200Z

it's similar to starting processes in bash with &

borkdude 2021-04-13T09:22:07.108400Z

Btw, I think it might be nice to release a stable version of bb tasks at ClojureD in June perhaps. You can vote for the talk topic here: https://twitter.com/borkdude/status/1381897272024305665

borkdude 2021-04-13T12:19:12.110Z

Click the link to see info about an informal meetup after the talk organized by Nikolay. It will be a kind of Russian Clojure meetup on Zoom. Join the Telegram channel for more info.

borkdude 2021-04-13T13:26:30.110500Z

Pasted my bb.edn from work here: https://gist.github.com/borkdude/35bc0a20bd4c112dec2c5645f67250e3

grazfather 2021-04-13T13:37:52.111100Z

does it make more sense to have an :env block or something outside of :tasks, even, that is used to set vars?

grazfather 2021-04-13T13:38:09.111400Z

e.g. for`platform-alias`

grazfather 2021-04-13T13:39:10.111800Z

and i imagine that soon people will want to override these vars

borkdude 2021-04-13T13:40:54.112400Z

@grazfather I thought about this, e.g :vars ... but what benefit would this have over :init?

grazfather 2021-04-13T13:41:13.112900Z

an invoker could provide these

borkdude 2021-04-13T13:41:13.113200Z

it would only be more restricting I thing, while introducing semantic problems, like ordering, etc

grazfather 2021-04-13T13:41:48.113900Z

e.g. bb run dev-backend platform-alias=Solaris would avoid running the thing that normally sets platform-alias

borkdude 2021-04-13T13:42:32.114800Z

I would say, just use an env var for that. BACKEND=foo bb dev and (def backend (or (System/getenv "BACKEND" ...))

👍 1
grazfather 2021-04-13T13:42:42.115100Z

Again, I am just taking make semantics… I don’t think it’s something we really need to worry about

grazfather 2021-04-13T13:43:07.115400Z

ok, yeah I agree

grazfather 2021-04-13T13:43:30.116Z

i think for tasks you should write a ‘recipe book’ about how to get things from make in BB. you showed the perfect example of one

grazfather 2021-04-13T13:43:58.116500Z

did you change your mind about :init vs just a ‘recipe’` for init being a dependency?

borkdude 2021-04-13T13:44:15.116900Z

@wilkerlucio convinced me that we should leave :init in it ;)

1
grazfather 2021-04-13T13:45:05.117200Z

can I read the reasoning somewhere?

grazfather 2021-04-13T13:45:22.117600Z

found it

borkdude 2021-04-13T13:45:32.117900Z

the main reason is that it can get more verbose

grazfather 2021-04-13T13:46:08.118400Z

sure, but you lose the ability to put ‘expensive’ init, since it runs for all tasks

borkdude 2021-04-13T13:46:22.118900Z

you can put an expensive init still in its own task

grazfather 2021-04-13T13:46:24.119Z

is 1 more word in each task really more verbose?

borkdude 2021-04-13T13:46:27.119300Z

you can have both options

wilkerlucio 2021-04-13T13:47:03.119400Z

it is when you have to change from

(my-task "foo")
to:
{:depends [init]
 :task (my-task "foo")}

grazfather 2021-04-13T13:47:15.119700Z

yep

grazfather 2021-04-13T13:47:34.120200Z

yeah I think I agree

borkdude 2021-04-13T13:47:46.120500Z

btw, I also introduced :requires so you can require expensive namespaces in tasks, so not for every task

borkdude 2021-04-13T13:47:50.120700Z

on master

grazfather 2021-04-13T13:47:58.121Z

i saw that in your work example

borkdude 2021-04-13T13:48:09.121300Z

and all implicit aliases go away, e.g. no automatic fs

grazfather 2021-04-13T13:48:22.121600Z

I think I like that too. I prefer less magic

grazfather 2021-04-13T13:48:28.121800Z

more explicit

borkdude 2021-04-13T13:48:55.122400Z

so if you want to use fs in multiple tasks you can do {:tasks {:requires ([babashka.fs :as fs])}}

borkdude 2021-04-13T13:49:17.122800Z

or just for one:

{:tasks {foo {:requires ([babashka.fs :as fs])}}}

borkdude 2021-04-13T13:49:40.123300Z

(built-in namespaces aren't expensive to require tho, interpreted ones can be)

grazfather 2021-04-13T13:50:07.123500Z

nice

borkdude 2021-04-13T13:52:26.124Z

I think I'll also include the idea from @maxp about :depends #{p1 p2}, it comes in quite handy for my work bb.edn

grazfather 2021-04-13T13:56:33.124200Z

to automatically parallelize?

borkdude 2021-04-13T13:57:27.124400Z

yes

grazfather 2021-04-13T14:00:32.127Z

I think it’s better if that were not supported. Tasks should be parallelizable by default by virtue that they should list all their dependencies, anything not listed, then, must be able to run at the same time. if you need them to run in a certain order, then you define the tasks that way If you want dev to clean and then build you don’t say dev depends on clean and build, you say that dev depends on build which depends on clean

borkdude 2021-04-13T14:09:31.127300Z

but build doesn't always depend on clean

borkdude 2021-04-13T14:09:42.127500Z

I also want to be able to run build without clean

borkdude 2021-04-13T14:12:02.128300Z

so having :depends [clean dev] with order semantics does cover that use while :depends #{p1 p2} means: don't care about the order

grazfather 2021-04-13T14:13:02.129400Z

but can’t you just do bb run clean dev?

borkdude 2021-04-13T14:13:27.129700Z

that doesn't work since clean can have command line args

grazfather 2021-04-13T14:13:52.129900Z

how do you provide args to a task?

borkdude 2021-04-13T14:14:01.130200Z

bb foo arg1 arg2

grazfather 2021-04-13T14:14:21.130500Z

ok, so you can’t kick off multiple tasks at all

borkdude 2021-04-13T14:14:32.130900Z

bb clean && bb dev

grazfather 2021-04-13T14:14:49.131500Z

haha i mean within one invocation obviously

borkdude 2021-04-13T14:14:50.131600Z

leiningen has this:

bb do clean foo bar, dev foo bar

borkdude 2021-04-13T14:15:23.132Z

so it uses the comma as a special delimiter in do

borkdude 2021-04-13T14:15:50.132300Z

I kinda like that: bb do clean, dev

grazfather 2021-04-13T14:15:59.132600Z

ah interesting

borkdude 2021-04-13T14:16:13.133200Z

we could also have:

:pre [clean]
:post [foobar]

grazfather 2021-04-13T14:16:14.133300Z

but yeah, it’s worth trying out the set dependencies thing

borkdude 2021-04-13T14:16:57.133800Z

but :depends [clean] also kind of means :pre

grazfather 2021-04-13T14:17:26.134100Z

yeah, supporting both seems it would get confusing quickly

borkdude 2021-04-13T14:23:43.136300Z

:depends [foo x], if foo depends on x then x is executed first anyway, so it can't always guarantee the order, but it will try to do it if possible

borkdude 2021-04-13T14:29:39.136800Z

feel free to convince me otherwise, but for me personally this is convenient. if it's really a bad idea I'd also like to know

borkdude 2021-04-13T14:30:57.137700Z

you can also do:

clean:dev (do (shell "bb clean") (shell "bb dev"))
but maybe it's a bit ugly to have to start bb twice within bb

2021-04-13T14:37:20.138700Z

What would be your recommended approach for something like this?

{:task (do (future (shell "bb watch-cljs")) (shell "bb run"))}

2021-04-13T14:39:20.139500Z

That's one I use inside a docker container that starts shadowcljs and the main server.

borkdude 2021-04-13T14:42:40.140500Z

@duck This would be solved if we implemented the :depends #{watch-cljs run} but now you can do bb run --parallel the-task, this will run the deps in parallel

borkdude 2021-04-13T14:43:06.140900Z

not sure if this will stay in if we also support #{foo bar}

2021-04-13T14:43:30.141300Z

yeah, I still need to grab the parallel updates

borkdude 2021-04-13T14:44:03.141600Z

so this is what I do myself right now: https://gist.github.com/borkdude/35bc0a20bd4c112dec2c5645f67250e3#file-bb-edn-L10-L18

👍 2
wilkerlucio 2021-04-13T20:04:36.143200Z

hello, just figured a cool snippet to copy things to clipboard (macos only) using babashka.process and wanted to share with you here 🙂

(proc/process "pbcopy" {:in "any string to put on clipboard"})

💯 1
✨ 4
Tomas Brejla 2021-04-14T10:15:05.144400Z

on linux, this seems to work.

;; I have copied this text to my clipboard..

(-> (pipeline
     (pb '[timeout 0.05 xclip -selection clipboard -o])
     (pb ["cat"]))
    last
    :out
    slurp)
;; => "I have copied this text to my clipboard.."
It could definitely be simplified. I'm just not experienced with babashka/process

Tomas Brejla 2021-04-14T10:18:17.144600Z

probably something like this:

(ns core
  (:require [babashka.process :as proc :refer [$]]))

;; I have copied this text to my clipboard..
(-> ($ timeout 0.05 xclip -selection clipboard -o) deref :out slurp)
;; => "I have copied this text to my clipboard.."

;; store to clipboard
(do (proc/process '[xclip -sel clip] {:in "text to put to " :out :inherit}) nil)

;; read again from clipboard
(-> ($ timeout 0.05 xclip -selection clipboard -o) deref :out slurp)
;; => "text to put to "