nbb 2022-10-19

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?

@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

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

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 )

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

@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

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

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

Use the linux amd64 static one

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.

@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 #

are you sure you have aarch64 architecture?

and are you on 64bit?

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

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.

@rahul080327 might know something here too

πŸ™ 1

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

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

as alpine doesn't support dynamically linked binaries

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?

Is this a Node.js project?

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.

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

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

got the nbb example working with loadFile - thanks3

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'
}

@rahx1t There is a way to do that

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

Haha, sorry, I was a bit distracted

Let me whip up an example

❀️ 1

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

I'm running this as a script via bb task

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

can you maybe make a repro of this?

Sure! Give me one moment

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

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

=== 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'
}

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

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

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

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

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

πŸ™Œ 1

Of course you could wrap this in another macro too

Oh cool! TIL about (meta &form)

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

you can place macros anywhere you want

Ah right, thanks. This is really helpful

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

oh weird. might be your node version

what version are you on?

I'm on 17.8.0

I'll change the example to log then

no worries - I can look into that separately

I updated this in the example on main now

πŸ™Œ 1

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

πŸ’‘ 1
πŸ˜„ 1

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

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.

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

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

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)

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

This works because SCI controls try/catch

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

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

$ 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

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?

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?

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

so body. sym and expr do have metadata

but your generated call to print-error doesn't

gotta go now, sleep time

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 πŸ™‚

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

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?

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

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

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

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

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

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

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 πŸ˜‡

It seems to happen when the :deps map is empty

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

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.

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

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

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

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

πŸ™Œ 1

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