babashka

https://github.com/babashka/babashka. Also see #sci, #nbb and #babashka-circleci-builds .
grazfather 2021-04-11T02:00:20.480700Z

is it looking like :when won’t be a thing?

grazfather 2021-04-11T02:07:35.481600Z

tasks are looking pretty damn good to me

borkdude 2021-04-11T08:41:27.484500Z

@grazfather Thanks. I had trouble figuring out what the behavior of :when in dependent tasks should be. E.g. invoke foo which depends on bar which depends on baz. But :when in bar returns false, then what?

borkdude 2021-04-11T11:56:16.496600Z

@grazfather I think you can have a creative workaround like this:

{:tasks {clojars-user-name
         (when-not (System/getenv "CLOJARS_USER_NAME")
           (println "Needs clojars username")
           (System/exit 1))
         deploy {:depends [clojars-user-name]
                 :task (println "Deploying...")}}}

pithyless 2021-04-11T11:56:18.496700Z

@borkdude at first I was skeptical, but I managed to convert some code to the new tasks format:

{:init
  (do
    (ns my.tasks
      (:require
       [proj.repo.task.version :as task.version]
       [proj.repo.ednopt :as ednopt]))

    (def default-args
      {:min-version/clojure  "1.10.3.814"
       :min-version/babashka "0.3.3"})

    (def args (merge default-args
                     (ednopt/parse *command-line-args*))))

  check-versions
  {:doc "Check runtime versions are supported"
   :task (task.version/check-versions args)}}
1. if dependent tasks do not allow passing args to each other (as it is currently implemented), they are consistent, in that, they act the same whether called by a different task or directly from the command line. :thumbsup: 2. I thought not passing args around would be a problem for my monorepo (where the reason I moved from Makefile to Justfile was better support for arguments in tasks); but it turns out if I only allow global arguments (via parsing kvs in :init and removing any conflicts via namespaced-keywords), I still get all the benefits of task parameterization, while also having the benefits of (1) and the nice task aliasing that a common bb tasks runtime convention provides 3. I think :when and :depends DSL is completely optional, since this all can be done via when and function calls in the task itself; but it raises some points: should the semantics be (when <guard> (run-dependent-tasks) (run-task)) or (do (run-dependent-tasks) (when <guard> (run task))) - all of which would be mute, if we took more care when writing dependent tasks to be idempotent (is that feasible?)

pithyless 2021-04-11T11:57:25.497900Z

(3) is kind of rambling, I'm just wondering if the current notions of task dependencies and guards in Makefile/Justfile/etc are just a poor-man's approach to idempotent functions (and maybe we can leverage clojure idioms more here)

borkdude 2021-04-11T11:58:08.499200Z

@pithyless co-incidentally, I just posted an example of :depends that replaces the need for :when possibly, around the same millisecond you posted your long message

😅 1
borkdude 2021-04-11T11:58:54.499600Z

I'll take a look at your other comments later today. Thanks for the feedback

borkdude 2021-04-11T12:29:03.001400Z

@pithyless 1+2, cool. 3 :when doesn't exist anymore. :depends is there to topologically sort tasks, so when two tasks depend on a third task and you depend on these two tasks, the third task is only executed once, which is a common feature in build systems.

pithyless 2021-04-11T12:30:57.002200Z

ad 3. Yeah, that makes sense, without :when there is no question about the semantics of how it interacts with :depends.

borkdude 2021-04-11T12:31:10.002400Z

right that was the reason I removed it (`:when`)

borkdude 2021-04-11T12:31:50.003400Z

you can just short-circuit using System/exit if needed

pithyless 2021-04-11T12:33:00.004600Z

I'm going to try to build up something more substantial with the existing bb tasks. For me the missing feature was an easy way to parameterize tasks, and I think I found it via combining the existing :init with that previous idea I talked about inspired by passing EDN kw-args on the command line.

pithyless 2021-04-11T12:33:19.005Z

Thanks for all the work @borkdude! 🙂

borkdude 2021-04-11T12:33:44.005300Z

Thanks for trying and looking forward to more feedback

grazfather 2021-04-11T14:11:48.005500Z

Yep I was using when/when-not instead,no big deal :)

grazfather 2021-04-11T14:13:52.005700Z

If foo depends on bar which depends on baz, then if bar’s :when says not to run, then it implies that baz is done as well.

grazfather 2021-04-11T14:14:39.005900Z

I don’t think it’s very critical that you have this feature, but if you do, then I would liteirally just copy the behaviour from make. I was using a makefile I wrote myself to compare my bb task

borkdude 2021-04-11T14:17:20.006400Z

does Make have something like when?

grazfather 2021-04-11T14:19:42.006600Z

only for non-phony targets, it uses the time stamp of the target vs any of its dependencies

a: b c
basically does:
a {:when (and (modified-since a b) (modified-since a c)}

grazfather 2021-04-11T14:21:03.006800Z

but

a: b
b: c
only checks the time of b vs a, and if b is newer it runs a. it doesn’t care about c.

grazfather 2021-04-11T14:21:45.007Z

i might have gotten the order of the args for modified-since wrong 🙂

borkdude 2021-04-11T14:24:24.007300Z

one example I had in mind was the above:

{:tasks {a {:depends [b]} {b {:when (System/getenv "CLOJARS_USER_NAME)}}}
` so if the condition of b didn't pass, I might want to skip as well

grazfather 2021-04-11T14:24:41.008Z

I think it’s pretty safe to not rush :when and let :depend ( which is more important) to shake out any issues first

👍 1
borkdude 2021-04-11T14:25:42.008200Z

let's say that b is deploying something based on that env var and a depends on the thing having been deployed

grazfather 2021-04-11T14:35:12.008500Z

oh, Make isn’t smart enough for that, the way you’d do it in make is you could put the env var ITSELF as an uinbuildable dependency

grazfather 2021-04-11T14:35:39.008700Z

so a and b depend on env and env doesn’t set anything, but just fails if the end var is not set

borkdude 2021-04-11T14:36:35.008900Z

right. we could encode some special return value like :tasks/invalid which would yield the entire dependency tree invalid, for example.

grazfather 2021-04-11T14:36:37.009100Z

that makes me wonder: Do you capture failure from tasks and propagate then?

grazfather 2021-04-11T14:36:48.009300Z

exactly 🙂

grazfather 2021-04-11T14:37:12.009500Z

again I stress that we should not make up new semantics as best we can

borkdude 2021-04-11T14:37:25.009700Z

right now this is done using System/exit. So if a shell command doesn't return with 0 the entire process quits with that exit code

borkdude 2021-04-11T14:37:46.009900Z

that might be the least surprising way indeed

borkdude 2021-04-11T14:40:43.010600Z

If you have any topics for a talk at a virtual Clojure conf, feel free to respond on Twitter or here (in a thread): https://twitter.com/borkdude/status/1380950518567145473

grazfather 2021-04-11T14:41:52.010800Z

yep that is perfect to me

grazfather 2021-04-11T14:48:56.011Z

nice. hidden tasks!

borkdude 2021-04-11T14:49:32.011200Z

yep. - will make it hidden, or :private true.

borkdude 2021-04-11T14:51:14.011800Z

You can still call it from the command line if you want, it just won't show up in the tasks list

grazfather 2021-04-11T14:51:59.012Z

beautiful

grazfather 2021-04-11T14:53:13.012200Z

I am still pretty slow writing lisps and edn but I think I will soon use this to write all my ‘test targets’. a lot of then depend on certain docker containers to be running, and some need to run inside the container. We do a lot with makefiles but it gets hacky do determine if a container is running and if you’re in a container

borkdude 2021-04-11T14:54:52.012400Z

great! keep me posted as we have still room to improve and break things if we need to

grazfather 2021-04-11T14:55:25.012600Z

Yep I will let you know if I hit any snags

grazfather 2021-04-11T15:12:28.013100Z

posted without comment 😈

grazfather 2021-04-11T15:12:37.013400Z

bash
$ time make coffee
measuring beans
grinding beans
pouring water
heating water
getting filter
getting mug
brewing coffee

real    0m15.121s
user    0m0.016s
sys     0m0.023s
$ time make -j4 coffee
pouring water
measuring beans
getting mug
getting filter
grinding beans
heating water
brewing coffee

real    0m9.039s
user    0m0.016s
sys     0m0.024s

borkdude 2021-04-11T15:14:45.013500Z

Can you post a comment anyway? What am I looking at? What's -j4 ?

grazfather 2021-04-11T15:15:44.013700Z

parallelizes (4 threads)

borkdude 2021-04-11T15:15:59.013900Z

zomg, yeah we could do that

grazfather 2021-04-11T15:16:05.014100Z

the topo sort can find independent chains

grazfather 2021-04-11T15:16:12.014300Z

see how it’s 15s vs 9 seconds?

borkdude 2021-04-11T15:16:30.014500Z

yeah

grazfather 2021-04-11T15:16:45.014700Z

first first heats water then grinds beans. the second does both at the same time. I use it as an example always to explain makefiles 🙂

borkdude 2021-04-11T15:17:14.014900Z

in tasks you can have futures or core.async and do this yourself as well

grazfather 2021-04-11T15:18:01.015100Z

yeah I am going to play with it right now. I was just brewing coffee and literally thought “might as well start the kettle while I grind” 😉

borkdude 2021-04-11T15:18:20.015300Z

keep the Java flowing

1
👌 1
grazfather 2021-04-11T15:33:50.017100Z

so I am not actually sure how/if you could share futures between tasks, I just made this dummy task that acts like a parallelized make coffee but it doesn’t properly specify dependencies

(defn measure-beans []
 (println "measuring beans")
 (Thread/sleep 1000)) 

(defn grind-beans []
  @(future (measure-beans))
  (println "grinding beans")
  (Thread/sleep 2000))

(defn pour-water []
 (println "pouring water")
 (Thread/sleep 1000)) 

(defn heat-water []
  @(future (pour-water))
  (println "heating water")
  (Thread/sleep 2000))

(defn get-filter []
  (println "getting filter")
  (Thread/sleep 1000))

(defn get-mug []
  (println "getting-mug")
  (Thread/sleep 1000))

(defn make-coffee []
 (let [grounds (future (grind-beans))
       hot-water (future (heat-water))
       filter (future (get-filter))
       mug (future (get-mug))]
   (println "brewing coffee")
   @grounds
   @hot-water
   @filter
   @mug
   (Thread/sleep 3000)))

(defn- main []
 (make-coffee)) 

(main)

borkdude 2021-04-11T15:37:08.017400Z

@(future (pour-water))
this won't really help, since you're immediately waiting for the thread

borkdude 2021-04-11T15:38:09.017600Z

How does make parallelization work when one of the tasks returns with a non-zero exit code?

grazfather 2021-04-11T15:44:23.017800Z

Yeah I know about the @(future thing 🙂 it’s just there so that I show that i am doing it in another thread, not real code

grazfather 2021-04-11T15:45:22.018Z

if anything in the dependency chain fails the failures percolate upward

grazfather 2021-04-11T15:45:27.018200Z

play with it 🙂

grazfather 2021-04-11T15:45:35.018400Z

.PHONY: a b c

coffee: grounds hot_water filter mug
	@echo "brewing coffee"
	@sleep 3

grounds: beans
	@echo "grinding beans"
	@sleep 2
	exit 1

hot_water: water
	@echo "heating water"
	@sleep 2

filter:
	@echo "getting filter"
	@sleep 1

mug:
	@echo "getting mug"
	@sleep 1

water:
	@echo "pouring water"
	@sleep 1

beans:
	@echo "measuring beans"
	@sleep 1

grazfather 2021-04-11T15:48:23.018600Z

I am not sure if/how Make stops ‘parallel’ tasks. I know it doesn’t interrupt them

grazfather 2021-04-11T15:49:14.018800Z

but say that a depends on b and c, and b depends on b2 and c on c2, if b2 and c2 are running and c2 fails, I am pretty sure that make will stop b from running (after b2)

grazfather 2021-04-11T15:49:26.019Z

but also I think that that’s less important

grazfather 2021-04-11T15:49:40.019200Z

make: *** [beans] Error 1
make: *** Waiting for unfinished jobs....

borkdude 2021-04-11T15:50:30.019400Z

how tasks are currently implemented, bb would just quit while the others are still running (because it has no concept of parallel jobs yet)

grazfather 2021-04-11T15:56:21.020700Z

makes sense

mike_ananev 2021-04-11T15:57:09.021100Z

I'm trying to use Cursive as IDE to write bb scripts. What library should I include to get babashka.deps resolved?

(require '[babashka.deps :as deps])
Execution error (FileNotFoundException) at tasks/eval17985 (REPL:1).

borkdude 2021-04-11T15:59:08.021200Z

unfortunately that lib isn't available from the JVM as this is very bb specific and not generally applicable

borkdude 2021-04-11T16:00:12.021400Z

maybe we could have a special (quit! 1) or so function that would delay the real exit while other tasks are still running

borkdude 2021-04-11T16:00:55.021600Z

(set-exit-code! 1) something in this vain

grazfather 2021-04-11T16:09:32.021800Z

yeah, it makes sense that we could have an implicit check before starting a task to see if the whole task tree has been abandoned. I think, actually, that the user shouldn’t have to see/use this. Just implicitly set the global fair when a task fails. would you support kicking off more than one task at a time? Then this failure signal would have to be local to that task chain

borkdude 2021-04-11T16:11:41.022Z

I guess throwing an exception would be the better, more supported alternative

grazfather 2021-04-11T16:11:54.022200Z

grazfather 2021-04-11T16:12:18.022600Z

Yeah I am thinking abstractly, not really how it would be implemented (I don’t really know Java)

grazfather 2021-04-11T16:14:02.022800Z

I am just thinking about the semantics and about whether the ‘ui’ would have people set the error and check it.. I think that they should not. It definitely can be considered ugly/less pure but it makes sense to me that if A is kicked off and b2 fails while c2 is running, c2 can finish (`Waiting for jobs to finish`) but c never starts since it implicitly checks if the job (which is A) is still running before starting

mike_ananev 2021-04-11T16:50:16.023100Z

@borkdude, thanks. I'm trying to rewrite my app and lib templates using babashka only. I want to throw away just or make utilities from templates.

borkdude 2021-04-11T17:25:19.023300Z

yeah, I'm thinking how people can signal the flow to stop and which exit code to use, you could do (throw (ex-info "" {:babashka/exit 1})) which would get caught by the consumer of your future and this exit could would then be handled by babashka

borkdude 2021-04-11T17:26:00.023500Z

this is already how futures work, once you deref them and an exception occurred, then the exception is re-thrown

borkdude 2021-04-11T17:27:43.023700Z

so for the above you could have:

(def b2 (future ....))
(def c2 (future ....))
(def b (future (do @b ....)))
(def c (future (do @c2 ...)))
(def a (do @b @c ...))
and it would work

borkdude 2021-04-11T17:34:56.023900Z

we could make this work relatively easy by letting people just write future around their task and handle the rest automatically. this would give some control about which tasks you want to run in a future and which you don't

borkdude 2021-04-11T17:47:16.024100Z

sounds cool :)

dabrazhe 2021-04-11T19:21:41.026200Z

Hi all. Is there possibility to have the autocomplete on the BB repl? I am running it on the Mac OS shell as rlwrap bb (I might have missed it in the previous discussions).

borkdude 2021-04-11T19:23:08.026700Z

@dennisa not with the terminal REPL, but with the nrepl server you will get more functionality like that

dabrazhe 2021-04-14T17:06:14.161700Z

@borkdude I gave the bb nrepl a go. It connects fine. The autocomplete fails on pressing the tab clojure.lang.ExceptionInfo: Could not resolve symbol: complete.core/completions

dabrazhe 2021-04-14T17:06:47.162Z

It is my setup or bb?

borkdude 2021-04-14T17:58:37.162600Z

@dennisa it seems to work for me. cc @bozhidar

dabrazhe 2021-04-14T18:06:35.165700Z

It's mine then. Where does bb take it's dependencies from, when connected to nrepl?

borkdude 2021-04-14T18:08:58.167300Z

what do you mean?

bozhidar 2021-04-14T19:13:46.167900Z

I’m puzzled by this error - to my knowledge bb doesn’t use clojure-complete (and neither does CIDER), so I don’t get where this error is coming from.

borkdude 2021-04-14T19:29:46.170600Z

yeah, I'm puzzled too :)

dabrazhe 2021-04-16T10:48:40.218300Z

I mean that perhaps I need to configure something similar like .clojure/deps.edn for bb to pick the cljoure-comlete dependency.

borkdude 2021-04-16T10:49:50.218500Z

@dennisa No, bb nrepl-server doesn't use this dependency. Also cider.el doesn't use this dependency. So I wonder how you got this error and so is @bozhidar

borkdude 2021-04-16T10:50:49.218700Z

@dennisa it could be that your nrepl client is trying to evaluate this code in the bb nrepl-server

borkdude 2021-04-16T10:50:56.218900Z

but why it's doing that is the question

borkdude 2021-04-16T10:51:24.219100Z

what editor are you using

dabrazhe 2021-04-16T10:53:47.219300Z

Mac OS iterm terminal application. I'll summarise my steps to reproduce it in a bit.

dabrazhe 2021-04-16T11:17:14.219600Z

babashka v0.3.3 Mac OS iTerm2 app Start the nrepl server bb nrepl-server

Started nREPL server at 127.0.0.1:1667
connect with lein lein repl :connect localhost:1667
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
Connecting to nREPL at localhost:1667
clojure.lang.ExceptionInfo: Could not resolve symbol: nrepl.core/version
nil
Error loading namespace; falling back to user
nil
user=> (inc 1)
2
(f
press tab after (f, or any other symbol, fails with the exception:
user=> (fclojure.lang.ExceptionInfo: Could not resolve symbol: complete.core/completions

borkdude 2021-04-16T11:19:35.219900Z

Ah, I see that. Yeah, it's not something bb can fix, but should be fixed in lein repl to work with nREPL servers that do not have complete.core/completions

borkdude 2021-04-16T11:19:43.220100Z

lein repl assumes too much

borkdude 2021-04-16T11:20:39.220300Z

completions do work in bb nrepl-server, but using the standard completions op: https://nrepl.org/nrepl/ops.html

borkdude 2021-04-16T11:21:09.220500Z

and an nrepl client should check the capabilities of an nrepl server and behave as such

dabrazhe 2021-04-16T11:21:28.220700Z

is there's smth I can fix myself? eg connect to the bb nrepl via another cli, ie non-lein?

borkdude 2021-04-16T11:23:46.220900Z

Maybe @bozhidar knows some other client. Calva has pretty good support for talking to the bb nrepl-server

borkdude 2021-04-16T11:24:12.221100Z

#calva

borkdude 2021-04-16T11:24:22.221300Z

but this is not a command line tool, more a full fledged editor

dabrazhe 2021-04-16T11:27:52.221500Z

Yes, I checked Calva in VSC, no issues with auto-complete. But... there's no (doc) function : (

(doc +)
; clojure.lang.ExceptionInfo: Could not resolve symbol: doc

borkdude 2021-04-16T11:28:21.221800Z

In calva it should be sufficient to hover on the symbol to see the docstring

👍 1
borkdude 2021-04-16T11:28:50.222Z

but doc is available as (require '[clojure.repl :refer [doc]])

dabrazhe 2021-04-16T11:29:02.222200Z

Yes, indeed, thank you!

dabrazhe 2021-04-11T19:23:37.026800Z

Great, thanks will give it a try

borkdude 2021-04-11T19:29:41.027Z

@grazfather On master. Binary in #babashka-circleci-builds for trying.

grazfather 2021-04-11T19:34:34.027400Z

you’re a machine!

grazfather 2021-04-11T19:43:05.027600Z

Just looked at the commit, very cool. I am learning a lot

grazfather 2021-04-11T19:45:19.028200Z

I love parallelized tasks!

$ time ./bb coffee
measuring beans
grinding beans
pouring water
heating water
getting filter
getting mug
brewing coffee

real    0m11.294s
user    0m0.023s
sys     0m0.042s

$ time ./bb coffeep
measuring beans
grinding beans
pouring water
heating water
getting filter
getting mug
brewing coffee

real    0m5.052s
user    0m0.018s
sys     0m0.018s

sh54 2021-04-11T20:04:59.032600Z

Any chance that java.nio.charset.Charset will be supported in the future? I was hoping to use https://github.com/helins/binf.cljc via babashka. That class is the current blocker. I have a custom format that I decode via that library and given I am supporting clojurescript too its nice to call something thats got the bit wrangling for both clojure/script sorted already. It would be great if a little babashka script could just call on my existing regular library clojure/script code to do the decoding rather than having to write a separate path.

sh54 2021-04-11T20:05:07.033Z

to reproduce:

sh54 2021-04-11T20:05:29.033500Z

(require '[babashka.deps :as deps]) (deps/add-deps '{:deps {io.helins/binf {:mvn/version "1.0.0-beta1"}}}) (require '[helins.binf :as binf])

borkdude 2021-04-11T20:06:15.033800Z

@slack1003 Please make an issue and I'll investigate

sh54 2021-04-11T20:09:41.034Z

k will do

sh54 2021-04-11T20:19:08.034100Z

https://github.com/babashka/babashka/issues/784

borkdude 2021-04-11T20:19:29.034400Z

Thanks!

borkdude 2021-04-11T22:05:02.034900Z

Implemented the `bb run --parallel <task>` option now so tasks run in parallel when they can.

🎉 3