nbb

Daniel Gerson 2022-10-19T13:47:41.040139Z

Question: What's the easiest way to get node and babashka on an alpine docker image? I know there's a babashka alpine image, but I was using the official node image (and probably should stick with that)... however I need bb in order to get deps from nbb.edn. Anyone have this issue?

borkdude 2022-10-19T13:48:53.324469Z

@danielmgerson There are multiple ways. E.g. use a staged docker build and copy the binary from one layer to another, or simply curl-install the bb alpine binary

borkdude 2022-10-19T13:49:37.587699Z

I think I would already fetch the deps in the image build, so you won't have to fetch them on execution

Daniel Gerson 2022-10-19T13:50:48.255939Z

The following installs but doesn't run. So you're probably saying I should fetch the binaries from the alpine image (which I'll have to look up how to do)

RUN apk update && apk add bash curl
RUN bash < <(curl -s )

borkdude 2022-10-19T13:51:16.137279Z

@danielmgerson You need to provide the --static flag to the installer script.

lispyclouds 2022-10-20T09:11:36.154669Z

@danielmgerson re: but I thought that docker obscured that? on M1/2 macs, docker uses mac's virtualization tech to create the linux vm and hence it advertises itself as linux/arm64. so if you do a docker pull its gonna be pulling for that os/arch combo. since your base image is alpine your probably getting affected by a particular version of glibc not being there. in general the pattern i follow is that if i need to install anything else in an alpine image which isnt there in apk, i'll assume theres a good chance it won run and I'll use a ubuntu base image which is only slightly fatter

Daniel Gerson 2022-10-20T11:46:13.573119Z

@rahul080327 Thanks. Yesterday I just removed -alpine to FROM node:18.1.0 and switching to apt-get and installing release-jre it worked.

borkdude 2022-10-19T14:09:05.929929Z

You can also just download a binary from Github releases, hard-coded

borkdude 2022-10-19T14:09:13.089049Z

Use the linux amd64 static one

Daniel Gerson 2022-10-19T14:20:01.030279Z

Thanks. Replying in 🧡 . I had to google to figure out how to pass --static but the following seemed to work during image creation. πŸ˜…

RUN curl -s  | bash -s -- --static
Alas, I still have the errors connecting via cli and running bb. Do you think downloading it manually will be different? That's next for me to investigate.

Daniel Gerson 2022-10-19T14:35:18.339509Z

@borkdude Presumably the aarch64 is the right one chosen via the --staticcommand below? I wasn't able to see during image creation, but cli'ing in results in the following:

/app # curl -s  | bash -s -- --static
Downloading  to /tmp/tmp.NdhjNE
Moving /usr/local/bin/bb to /usr/local/bin/bb.old
Successfully installed bb in /usr/local/bin
/app # cd /usr/local/bin
/usr/local/bin # ls
bb                    docker-entrypoint.sh  npx
bb.old                node                  yarn
corepack              npm                   yarnpkg
/usr/local/bin # bb
/bin/sh: bb: not found
/usr/local/bin # sh bb
bb: line 1: ELFοΏ½οΏ½οΏ½οΏ½@pοΏ½@8: not found
bb: line 2: syntax error: unexpected ")"
/usr/local/bin #

borkdude 2022-10-19T14:36:32.544049Z

are you sure you have aarch64 architecture?

borkdude 2022-10-19T14:37:37.054289Z

and are you on 64bit?

borkdude 2022-10-19T14:38:49.772599Z

So, the aarch64 binaries are not fully static and dynamically linked against libc. You can find more info here: https://stackoverflow.com/a/47602147/6264 Or use amd64

Daniel Gerson 2022-10-19T14:39:12.759039Z

I'm on a Macbook pro M1, but I thought that docker obscured that? I'm not familiar enough with these bits and pieces, will look at your links shortly.

borkdude 2022-10-19T14:41:16.834139Z

@rahul080327 might know something here too

πŸ™ 1
borkdude 2022-10-19T14:47:35.900009Z

@danielmgerson If it's an option, using an ubuntu based image might be easier too

Daniel Gerson 2022-10-19T14:49:36.021399Z

@borkdude Yeah, I think you're right. Alpine seems too minimal. Will try. Thanks for the help πŸ‘

borkdude 2022-10-19T13:51:27.472429Z

as alpine doesn't support dynamically linked binaries

Brice Ruth 2022-10-19T13:55:55.519309Z

Question (sorta re-post from a question in #clojurescript, after getting some feedback there) - I’m trying to put together a sample project that would help teams in my org that are working on JS projects (backend, to start) have a path to incorporating CLJS incrementally in their work - I have a similar project in Java land using Gradle/clojurephant/java-interop, where everything can live under the same source tree (you can just start working in src/main/clojure versus src/main/java, for instance). My goal, I guess, was to achieve something similar for CLJS - that is, assuming I have an existing Node.JS-based project, how would I incorporate CLJS incrementally … but, based on what I’m seeing and the feedback I got on my initial question, pointing at #shadow-cljs and #nbb - I’m wondering if a more workable approach is to isolate the CLJS code into it’s own node module and depend on that from the original project, versus trying to make the original project a hybrid of some sort … am I on the right track?

borkdude 2022-10-19T13:58:14.505649Z

Is this a Node.js project?

borkdude 2022-10-19T14:02:46.047669Z

It can work with #nbb. The way to do this is similar to this: https://github.com/babashka/nbb/blob/main/doc/aws_lambda.md So loadFile is the thing you need to load a .cljs file. There is no compilation there, the .cljs is interpreted. If performance isn't that import for that code, it's ok. There is also a lighter-weight compiler being worked on, named #cherry which is available from npm:

npm install cherry-cljs
npx cherry compile foo.cljs
This emits a foo.mjs file which you can then import from the rest of your CLJS. This is also achievable with #shadow-cljs but you have to use the JVM to produce the JS output there. Similarly there is a project called #squint for which the output doesn't depend on CLJS at all and emits "raw JS" without the immutable data structure, for smaller JS output. You still get to use CLJS syntax and and a library which imitates the core functions in JS.

Brice Ruth 2022-10-19T14:26:54.520779Z

> Is this a Node.js project? yes, at least, that’s what I want to demo - doing so for a front-end project looks to be more involved (and I don’t have a simple base to demo from)

Brice Ruth 2022-10-19T14:28:37.773859Z

thanks3 - looking at the nbb lambda example now - this looks promising πŸ™‚

Brice Ruth 2022-10-19T14:52:40.754499Z

got the nbb example working with loadFile - thanks3

borkdude 2022-10-19T14:53:06.239919Z

cool :)

alex 2022-10-19T17:55:42.629799Z

We're currently using Playwright with nbb (thanks!) Is there a way to get a more useful stack trace when Playwright throws an exception due to failing test/assertion? Right now we get something like the below -- a bunch of nbb internals.

locator.waitFor: Timeout 30000ms exceeded.
=========================== logs ===========================
waiting for selector "audio"
============================================================
    at Xba (file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:312:60)
    at gca (file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:322:137)
    at file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:529:453
    at P (file:///Users/alex/code//ui_tests/node_modules/nbb/lib/nbb_core.js:290:469)
    at file:///Users/alex/code//ui_tests/node_modules/nbb/lib/nbb_core.js:576:419
    at P (file:///Users/alex/code//ui_tests/node_modules/nbb/lib/nbb_core.js:290:469)
    at file:///Users/alex/code//ui_tests/node_modules/nbb/lib/nbb_core.js:577:274
    at P (file:///Users/alex/code//ui_tests/node_modules/nbb/lib/nbb_core.js:290:469)
    at file:///Users/alex/code//ui_tests/node_modules/nbb/lib/nbb_core.js:383:307
    at file:///Users/alex/code//ui_tests/node_modules/nbb/lib/nbb_promesa.js:39:342 {
  name: 'TimeoutError'
}

borkdude 2022-10-19T17:57:45.816289Z

@rahx1t There is a way to do that

alex 2022-10-19T17:58:59.128329Z

That's what I was hoping to hear! I'd be happy to dig into it if you could provide a pointer πŸ™‚

borkdude 2022-10-19T17:59:17.883889Z

Haha, sorry, I was a bit distracted

borkdude 2022-10-19T17:59:28.414039Z

Let me whip up an example

❀️ 1
borkdude 2022-10-19T18:12:08.157759Z

How are you running this, as a script or from the REPL?

alex 2022-10-19T18:12:41.500429Z

I'm running this as a script via bb task

borkdude 2022-10-19T18:16:25.343049Z

I'm trying to reproduce this here: https://github.com/babashka/nbb/commit/98bd4a86c38931a3a87bd59d4fa3f8ed0008f9c6 but the stacktrace isn't yet showing up

borkdude 2022-10-19T18:17:50.650459Z

can you maybe make a repro of this?

alex 2022-10-19T18:22:10.753449Z

Sure! Give me one moment

alex 2022-10-19T18:23:14.460229Z

If you add a p/catch (before p/finally ) for the TimeoutError thrown by Playwright, we can log the error/stacktrace

(p/catch (fn [err]
                    (js/console.log err)
                    (is false)))

alex 2022-10-19T18:23:44.522349Z

I copied your test to my test file and added that block to generate the stacktrace

alex 2022-10-19T18:24:18.852489Z

=== borkdude-test

locator.waitFor: Timeout 100ms exceeded.
=========================== logs ===========================
waiting for selector "audio" to be visible
============================================================
    at Xba (file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:312:60)
    at gca (file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:322:137)
    at file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:529:453
    at P (file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:290:469)
    at file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:576:419
    at P (file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:290:469)
    at file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:577:274
    at P (file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:290:469)
    at file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_core.js:383:307
    at file:///Users/alex/code/ui_tests/node_modules/nbb/lib/nbb_promesa.js:39:342 {
  name: 'TimeoutError'
}

borkdude 2022-10-19T18:24:21.416629Z

but if you log the err, isn't that stacktrace expected?

borkdude 2022-10-19T18:26:07.143709Z

I do get your wish to get a better stacktrace there. Let me see. With sync code this is possible now by async may complicate this...

alex 2022-10-19T18:31:45.122229Z

> but if you log the err, isn't that stacktrace expected? I guess I'm not 100% sure what I expected here. I would hope that the TimeoutError stacktrace shows the Playwright code that resulted in the error. Are you saying the stacktrace is expected due to the p/let macro + nbb?

borkdude 2022-10-19T18:35:06.157039Z

The trace you're seeing is showing what JS was executing at the moment of the error, which is nbb itself, which is interpreting your script. There is something you can do about this in user space: https://github.com/babashka/nbb/commit/c30d9315d42347cc6a69d04b1ec659ecdc62a34b

πŸ’‘ 1
borkdude 2022-10-19T18:35:36.566949Z

It might not be so automatic as you wish it to be but at least you get reasonable locations for your error

πŸ™Œ 1
borkdude 2022-10-19T18:35:49.129609Z

Of course you could wrap this in another macro too

alex 2022-10-19T18:41:47.487119Z

Oh cool! TIL about (meta &form)

alex 2022-10-19T18:42:14.436879Z

Does this macro need to go into a separate macro namespace? I recall that's the pattern I've used for other macros in cljs space

borkdude 2022-10-19T18:42:46.438389Z

Not in nbb

borkdude 2022-10-19T18:42:52.097099Z

you can place macros anywhere you want

alex 2022-10-19T18:45:33.362729Z

Ah right, thanks. This is really helpful

alex 2022-10-19T18:48:14.239549Z

Interestingly the macro's console.error doesn't log anything for me, but after switching to console.log, it works fine

borkdude 2022-10-19T18:48:28.203199Z

oh weird. might be your node version

borkdude 2022-10-19T18:48:32.690429Z

what version are you on?

borkdude 2022-10-19T18:48:54.276389Z

I'm on 17.8.0

alex 2022-10-19T18:49:25.506479Z

node 16.14.2

borkdude 2022-10-19T18:50:03.546889Z

not sure

borkdude 2022-10-19T18:50:15.105289Z

I'll change the example to log then

alex 2022-10-19T18:50:23.778409Z

no worries - I can look into that separately

borkdude 2022-10-19T18:52:43.282769Z

I updated this in the example on main now

πŸ™Œ 1
Daniel Gerson 2022-10-19T19:53:35.106319Z

There's always the Java version of Playwright from Clojure... 🫣

πŸ’‘ 1
πŸ˜„ 1
alex 2022-10-19T20:22:58.895029Z

> Of course you could wrap this in another macro too Just realized that the test output prints the line number where (print-error error) is invoked, rather than the assertion/test form that generated the error. In order to get the actual form that generates the error, I'm thinking I'll need to write a macro, that among other things, attaches (p/catch print-error) to each async form?

borkdude 2022-10-19T20:29:07.252339Z

Yes. If the code were synchronous this would work much better (e.g. in bb) but async makes this harder. As an aside, I'm also working on #cherry (CLJS compiler which works in JS) and #squint (same compiler basically, but outputs direct JS without CLJS, so mutable objects all the way down). Both support js/await and ^:async functions. The error reports from those would be much better (with proper source map support). They are a bit experimental though, although some people have been using them already.

borkdude 2022-10-19T20:29:58.568139Z

Both are also supposed to get REPL support (but this part is immature right now)

borkdude 2022-10-19T20:33:27.973709Z

What I meant earlier is: if the code is sync, e.g. babashka has a way for the user to get access to the interpreter stacktraxce

borkdude 2022-10-19T20:35:32.053379Z

E.g. try this script with the newest bb:

(ns dude
  (:require [sci.core :as sci]))

(defn foo []
  (try (/ 1 0)
       (catch ^:sci/error Exception e
         (run! prn (sci/format-stacktrace (sci/stacktrace e))))))

(foo)

borkdude 2022-10-19T20:35:49.727189Z

$ bb /tmp/foobar/dude.clj
"clojure.core// - <built-in>"
"dude           - /tmp/foobar/dude.clj:5:8"

borkdude 2022-10-19T20:36:15.464719Z

This works because SCI controls try/catch

alex 2022-10-19T21:23:08.468279Z

> As an aside, I'm also working on #cherry (CLJS compiler which works in JS) and #squint (same compiler basically, but outputs direct JS without CLJS, so mutable objects all the way down). > Both support js/await and ^:async functions. The error reports from those would be much better (with proper source map support). They are a bit experimental though, although some people have been using them already. That sounds cool! Is the compilation time negligible with #squint i.e. comparable to nbb? Also would it be separate or used in conjunction with nbb? Since it compiles to JS, it would no longer need the nbb interpreter right?

borkdude 2022-10-19T21:24:05.330729Z

It compiled pretty fast. Just try it with npx squint run foo.cljs

borkdude 2022-10-19T21:27:08.044829Z

$ cat package.json
{
  "dependencies": {
    "squint-cljs": "^0.0.0-alpha.49"
  },
  "type": "module"
}

 $ cat foo.cljs
(println "Hello world")

$ npx squint run foo.cljs
[squint] Running foo.cljs
Hello world

alex 2022-10-19T21:27:28.067859Z

I'm playing around with your plet macro to enhance it with the p/catch print-error logic, but I'm admittedly a bit out of my depth w/ macros

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)
                    err (gensym)]
                (list '.catch
                      (list '.then expr (list 'clojure.core/fn (vector sym)
                                              body))
                      (list 'clojure.core/fn (vector err)
                            `(do
                               (print-error ~err)
                               ;; TODO rethrow the error but pass a modified
                               ;; data structure so other `catch` handlers know
                               ;; the error has already been printed?
                               (p/rejected ~err))))))
            body
            binding-pairs)))
When I replace the p/let with plet in your test example, the (meta &form) calls seem to return nil so line/col is nil. Do you know why that might be?

alex 2022-10-19T21:28:57.630899Z

Also wondering if I'm sort of heading down a dead end. Once the macro expansion happens, there's no telling if the line/col #s will match up to the actual source forms, right?

borkdude 2022-10-19T21:31:27.501309Z

The issue here is that line and col are only added by the forms that are read by the reader, but they aren't part of macro-expansions, unless you explicitly take them from the original forms and add them to the expanded forms

borkdude 2022-10-19T21:32:18.186759Z

so body. sym and expr do have metadata

borkdude 2022-10-19T21:32:28.587499Z

but your generated call to print-error doesn't

borkdude 2022-10-19T21:33:11.742559Z

gotta go now, sleep time

alex 2022-10-19T21:33:19.391409Z

Gotcha! So it sounds like I just need to extract from the original forms and potentially pass them along. I'll noodle on this a bit more. Appreciate the pointers πŸ™‚

alex 2022-10-21T20:00:53.757709Z

I ended up with the following, and it seems to be working well. Thanks again for your direction!

(defmacro catch-line-col-and-reject
  "Attaches a promise catch handler that enhances an error map w/the line &
  column numbers of the thrown/rejected `expr`, then rejects the error map.
  If the `error#` already has a `:line`, we reject so it can be handled
  downstream.
  "
  [p expr]
  (let [{:keys [line column]} (meta expr)]
    `(p/catch ~p
              (fn [error#]
                (if (:line error#)
                  (p/rejected error#)
                  (let [new-error {:line ~line :column ~column :error error#}]
                    (p/rejected new-error)))))))

(defmacro do'
  "Modified from `p/do!` to attach `catch-print-reject` on each of the promise forms."
  [& exprs]
  `(pt/-bind
    (pt/-promise nil)
    (fn [_#]
      ~(condp = (count exprs)
         0 `(pt/-promise nil)
         1 `(catch-line-col-and-reject (pt/-promise ~(first exprs)) ~(first exprs))
         (reduce (fn [acc e]
                   `(catch-line-col-and-reject (pt/-bind (pt/-promise ~e) (fn [_#] ~acc)) ~e))
                 `(catch-line-col-and-reject (pt/-promise ~(last exprs)) ~(last exprs))
                 (reverse (butlast exprs)))))))

(defmacro let'
  "Modified from `p/let` to attach `catch-line-col-and-reject` on each of the promise forms."
  [bindings & body]
  `(pt/-bind
    (pt/-promise nil)
    (fn [_#]
      ~(c/->> (reverse (partition 2 bindings))
              (reduce (fn [acc [l r]]
                        `(catch-line-col-and-reject (pt/-bind (pt/-promise ~r) (fn [~l] ~acc)) ~r))
                      `(do' ~@body))))))

borkdude 2022-10-21T20:05:09.465529Z

nice!

alex 2022-10-21T20:09:51.902549Z

I'm keeping these macros in a util file. If I import the macros for use in test files, is there a nice way to determine the file name of those test files from within this macro?

alex 2022-10-21T20:10:01.431349Z

Or do I need to pass those in explicitly via *file*?

borkdude 2022-10-21T20:26:11.312459Z

You should be able to read *file* during macroexpansion: it should have the name of your test file

borkdude 2022-10-21T20:26:25.652639Z

So not in the expansion itself, but while it's expanding

borkdude 2022-10-21T20:27:53.748439Z

$ nbb
Welcome to nbb v1.0.137!
#'user/foo
user=> (defmacro foo [] (str "from: " *ns*))
#'user/foo
user=> (ns bar)
#object[dr bar]
bar=> (defn bar-fn [] (user/foo))
#'bar/bar-fn
bar=> (bar-fn)
"from: bar"

borkdude 2022-10-21T20:28:54.006669Z

When defining bar-fn the macro user/foo is called, so at that moment *ns* is bound to the namespace bar. Similar to *file*

πŸ™Œ 1
alex 2022-10-21T20:45:00.992959Z

Ah, way easier than I was imagining! Thanks πŸ˜„

Daniel Gerson 2022-10-19T20:01:32.456869Z

There might have been a regression with bb for nbb.edn, or perhaps I'm doing it wrong.

cat nbb.edn
{
 :paths ["."
         ]
 :deps {
        }
 }
which yields
Downloading dependencies...
Exception in thread "main" java.lang.Exception: The uberjar task needs a classpath.
	at babashka.main$exec.invokeStatic(main.clj:1023)
	at babashka.main$main.invokeStatic(main.clj:1082)
	at babashka.main$main.doInvoke(main.clj:1052)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:667)
	at babashka.main$_main.invokeStatic(main.clj:1115)
	at babashka.main$_main.doInvoke(main.clj:1107)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at babashka.main.main(Unknown Source)
An error occurred when calling (nbb.impl.deps/init)
Command failed: bb --config .nbb/.cache/bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f/deps.edn uberjar .nbb/.cache/bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f/nbb-deps.jar
Exception in thread "main" java.lang.Exception: The uberjar task needs a classpath.
	at babashka.main$exec.invokeStatic(main.clj:1023)
I suspect https://github.com/babashka/babashka/commit/020d42a94fab2a2f80abd15fa05b19b355b1b545 but unsure. Tried checking out bb but failed to get uberjar-test to run so thought I'd just post here πŸ˜‡

borkdude 2022-10-19T20:14:33.704549Z

I'll try

borkdude 2022-10-19T20:16:15.583049Z

It seems to happen when the :deps map is empty

borkdude 2022-10-19T20:17:39.764739Z

It might be related to this: https://github.com/babashka/babashka/commit/835244861191a246a83b5908b7bf7905f7c15c56

Daniel Gerson 2022-10-19T20:17:43.570879Z

Just realised I ran the babashka repl wrong (re my attempt to fix it), so ignore that bit. Thanks for looking into it.

Daniel Gerson 2022-10-19T20:20:25.028659Z

Once I deleted .nbb and upgraded bbit happened. Just upgrading doesn't make it happen from what I can see. Moving stuff around while making a docker image made me come across this.

borkdude 2022-10-19T20:21:37.379069Z

Hmm yeah. I think the classpath is empty when you have no deps:

$ bb --config .nbb/.cache/bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f/deps.edn -e '(prn (babashka.classpath/get-classpath))'
nil
and this is what trips the uberjar command

borkdude 2022-10-19T20:22:02.539959Z

instead of nil we probably have to use an empty string or so

borkdude 2022-10-19T20:22:48.055159Z

Thanks for reporting, I'll fix this in the next bb

borkdude 2022-10-19T20:23:16.559219Z

As a workaround, remove :deps or put some deps in it

πŸ™Œ 1
Daniel Gerson 2022-10-19T20:24:26.606839Z

Ah thanks. I had one dep that I worked out wasn't necessary so removed. Still was using the :paths (but removed both for reporting).

borkdude 2022-10-19T20:32:23.308509Z

(https://github.com/babashka/babashka/issues/1399)

βœ… 1