babashka 2025-06-14

Hey @borkdude I started getting some errors after upgrading 1.12.196 -> 1.12.201.

✅ 1

$ bb -m com.fnguy.phuzql.cli.picker
----- Error --------------------------------------------------------------------
Type:     clojure.lang.ExceptionInfo
Message:  Could not resolve symbol: taoensso.truss/try*
Data:     {:type :sci/error, :line 17, :column 1, :file "com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc", :phase "analysis"}
Location: com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:35:7
Phase:    analysis

----- Context ------------------------------------------------------------------
31:   (log/debug "Event loop started")
32:   (let [running? (atom true)]
33:     (async/go-loop []
34:       (async/<! (async/timeout resolution-ms))
35:       (enc/catching
          ^--- Could not resolve symbol: taoensso.truss/try*
36:         (sp/receive-events! event-queue env
37:           (fn [env {:keys [target] :as event}]
38:             (log/trace "Received event" event)
39:             (if-not target
40:               (log/warn "Event did not have a session target. This queue only supports events to charts." event)

----- Stack trace --------------------------------------------------------------
com.fulcrologic.statecharts.event-queue.core-async-event-loop         - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:35:7
com.fulcrologic.statecharts.event-queue.core-async-event-loop/loop    - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:35:7
clojure.core/loop                                                     - <built-in>
clojure.core/fn                                                       - <built-in>
com.fulcrologic.statecharts.event-queue.core-async-event-loop/go      - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:33:5
... (run with --debug to see elided elements)
com.fulcrologic.statecharts.event-queue.core-async-event-loop         - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:17:1
com.fnguy.phuzql.statecharts                                          - /tmp/dev_mounted_phuzql/src/com/fnguy/phuzql/statecharts.cljc:2:3
com.fnguy.phuzql.map-select-walker2                                   - /tmp/dev_mounted_phuzql/src/com/fnguy/phuzql/map_select_walker2.clj:2:3
com.fnguy.phuzql.cli.picker                                           - /tmp/dev_mounted_phuzql/src/com/fnguy/phuzql/cli/picker.clj:2:3
user                                                                  - <expr>:1:10

(py3-9_global) ✘-1 /tmp/dev_mounted_phuzql [main L|✚ 4…10] 
+13:06 $ /tmp/bb --version
babashka v1.12.196
The older version still works though.

I wonder if it's related to our last convo about Timbre https://clojurians.slack.com/archives/CLX41ASCS/p1749058086178649

can you do some more bisecting, e.g. try the 200 version and see if that still worked? also a more minimal code repro would be nice

👍 1

Yeah, it's only the latest that breaks.

I upgraded timbre to 6.7.0 and perhaps it changed some macro to emit code to "truss" which I never saw before in bb related code.

encore seems like a high churn library

taoensso/timbre uses encore (so I had to use encore macros in bb) but now encore uses truss

I could just downgrade by bb for now, not a big deal. Just thought I'd give you a heads up about the breaking change.

thank you very much, fixing it now

Wonder if I should just make the jump to Telemere. Besides the point, I suppose. Thanks for taking a look.

telemere doesn't work in bb btw

FWIW, part of why we moved away from timbre was the high level of churn and the interdependence between all of that maintainer's libraries.

What other logging options are there for babashka? Can it use any of the basic Java logging stuff?

@seancorfield bb has clojure.tools.logging as the logging "interface" and timbre as the logging impl. I chose timbre because you don't need any config files (which is a no go for one-file scripts) and it's easy to use from clojure

it has been part of bb for several years now (long before telemere)

the taoensso.encore/catching this is just exposed for timbre since one of their logging macros expands into it, but incidentally it also made other libraries like mentioned in the OP work (which is a nice bonus). but the macroexpansion changed to yet another sub library

Ah, interesting... I know slf4j can be a nuisance in terms of nop/multiple deps (which is c.t.l's default) and log4j requires some minimal configuration. What about j.u.l as the implementation? Does it have reasonable defaults?

I'm by no means a logging expert. In the commercial Clojure projects I've worked on, I've always encountered timbre, it wasn't even my own choice, so at the time it seemed like a good choice. Of course I could add j.u.l. but so far not a lot of people have asked for it. I won't remove timbre since that would be a major breaking change

I guess for a library like in the OP it would be better to just rely on clojure.tools.logging and not bring in other logging deps

Do you mean it would be better if the SC lib didn't use Timbre? Users of the lib don't really have much say about that, other than forking ofc.

I mean, if I would write a library that needed logging, I'd try to rely solely on clojure.tools.logging and not bring in other deps (like encore or truss just because they provide some minor convenience around exception catching)

the library doesn't even directly depend on encore, yet it uses it, which is sloppy: https://github.com/fulcrologic/statecharts/blob/b7ebeb080724dbeead94f73bcdc1963eecc531ff/deps.edn#L3

Understandable. I guess authors could have any number of reasons for choosing the stack for their libs. Perhaps in this case it's just to be more consistent with the rest of the author's related libs.

@alex.sheluchin do you perhaps have the whole code of your cli? I'm trying to add some CI tests for statecharts but I don't know which namespaces it uses. Not familiar with the lib

The test suite of statecharts unfortunately isn't able to run in bb since it loads ClojureScript, etc.

I'll try to add your program as a sort of test

hmm:

[com.fnguy.statecharts :as fnsc]

is there some simple example I could add as a test?

That ns is just a few helper functions to simplify interacting with the statecharts.

Do you need it exposed as a top-level var or something?

I just want one smoke gun test for statecharts to see if it runs in bb

as a single script

claude wrote this, can you check if it makes any sense?

#!/usr/bin/env bb

(require '[babashka.deps :as deps])

;; Add the dependency
(deps/add-deps '{:deps {com.fulcrologic/statecharts {:mvn/version "1.3.0"}}})

;; Require the statecharts namespace
(require '[com.fulcrologic.statecharts :as sc])

(defn smoke-test []
  "One simple test to verify fulcro-statecharts works"
  (println "🔥 Testing fulcro-statecharts in Babashka...")
  
  (try
    ;; Create a simple toggle statechart
    (let [chart-def {:id     :toggle
                     :initial :off
                     :states  {:off {:on {:TOGGLE {:target :on}}}
                              :on  {:on {:TOGGLE {:target :off}}}}}
          
          chart (sc/statechart chart-def)
          initial-state (sc/start chart)]
      
      (println "✓ Created statechart")
      (println (str "✓ Initial state: " (sc/current-state initial-state)))
      
      ;; Test transition
      (let [toggled-state (sc/trigger chart initial-state :TOGGLE)]
        (println (str "✓ After TOGGLE: " (sc/current-state toggled-state)))
        
        (if (= :on (sc/current-state toggled-state))
          (println "🎉 SUCCESS! Fulcro Statecharts works in Babashka!")
          (println "❌ FAIL: Unexpected state"))))
    
    (catch Exception e
      (println (str "❌ ERROR: " (.getMessage e))))))

;; Run the test
(smoke-test)

sc/statechart
doesn't even seem to exist. But this is the kind of code I need. Just a simple example. I can't even see such an example in the README of statecharts and when looking at the test suite, I don't know even where to begin

No, that code's a bit weird. But if I add a bb.edn to my repo we can trigger that failure easily with bb -m com.fnguy.statecharts alone.

There's probably a much more minimal smoke test though. lms

for starters, I could just require the main namespace and do nothing else.

I'm guessing that would be sufficient.

usually that's only a good start, but a test would reveal runtime errors

I'll just make this as a start, if you want to make another PR with some example, up to you

I'll add a flip-once you can call.

One a working version:

+14:23 $ /tmp/bb -m com.fnguy.coin-flip/flip-once
2025-06-14T18:24:06.556Z xps DEBUG [com.fulcrologic.statecharts.event-queue.core-async-event-loop:31] - Event loop started
2025-06-14T18:24:06.560Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:757] - Processing event on statechart => :com.fnguy.coin-flip/coin-flips
2025-06-14T18:24:06.561Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:199] - target(s) => [:state/coin]
2025-06-14T18:24:06.561Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:184] - target => :state/coin
2025-06-14T18:24:06.561Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:186] - target-element => :state/coin
2025-06-14T18:24:06.561Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:205] - (get-effective-target-states env t) => #{:state/coin}
2025-06-14T18:24:06.563Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:206] - (chart/nearest-ancestor-state statechart t) => :initial23309
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:213] - (:id (chart/find-least-common-compound-ancestor statechart (into (if tsource [tsource] []) tst
ates))) => :ROOT
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:199] - target(s) => [:state/coin]
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:184] - target => :state/coin
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:186] - target-element => :state/coin
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:368] - entry set => [#{:state/coin :state/heads} #{:state/coin} {}]
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:372] - Enter :state/coin
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:340] - Execute content of {:id :transition23310, :target [:state/heads], :type :external, :node-type 
:transition, :parent :initial23311}
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:386] - (chart/final-state? statechart s) => false
2025-06-14T18:24:06.564Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:372] - Enter :state/heads
2025-06-14T18:24:06.565Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:386] - (chart/final-state? statechart s) => false
2025-06-14T18:24:06.565Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:404] - after enter states:  => #{:state/coin :state/heads}
2025-06-14T18:24:06.565Z xps DEBUG [com.fulcrologic.statecharts.algorithms.v20150901-impl:434] - conflicting? => #{}
showing state {:wmem #:com.fulcrologic.statecharts{:history-value {}, :statechart-src :com.fnguy.coin-flip/coin-flips, :configuration #{:state/coin :state/heads}, :running? true, :macrostep-d
one? false, :initialized-states #{}, :session-id 1}}

/tmp/bb -m com.fnguy.coin-flip/flip-once gives the above but latest bb gives the error:

+14:24 $ bb -m com.fnguy.coin-flip/flip-once
----- Error --------------------------------------------------------------------
Type:     clojure.lang.ExceptionInfo
Message:  Could not resolve symbol: taoensso.truss/try*
Data:     {:type :sci/error, :line 17, :column 1, :file "com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc", :phase "analysis"}
Location: com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:35:7
Phase:    analysis

----- Context ------------------------------------------------------------------
31:   (log/debug "Event loop started")
32:   (let [running? (atom true)]
33:     (async/go-loop []
34:       (async/<! (async/timeout resolution-ms))
35:       (enc/catching
          ^--- Could not resolve symbol: taoensso.truss/try*
36:         (sp/receive-events! event-queue env
37:           (fn [env {:keys [target] :as event}]
38:             (log/trace "Received event" event)
39:             (if-not target
40:               (log/warn "Event did not have a session target. This queue only supports events to charts." event)

----- Stack trace --------------------------------------------------------------
com.fulcrologic.statecharts.event-queue.core-async-event-loop         - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:35:7
com.fulcrologic.statecharts.event-queue.core-async-event-loop/loop    - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:35:7
clojure.core/loop                                                     - <built-in>
clojure.core/fn                                                       - <built-in>
com.fulcrologic.statecharts.event-queue.core-async-event-loop/go      - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:33:5
... (run with --debug to see elided elements)
com.fulcrologic.statecharts.event-queue.core-async-event-loop         - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:17:1
com.fulcrologic.statecharts.event-queue.core-async-event-loop         - com/fulcrologic/statecharts/event_queue/core_async_event_loop.cljc:17:1
com.fnguy.statecharts                                                 - /home/alex/repos/personal-projects/fnguy-examples/examples/fulcro_statecharts/src/com/fnguy/statecharts.cljc:2:3
com.fnguy.coin-flip                                                   - /home/alex/repos/personal-projects/fnguy-examples/examples/fulcro_statecharts/src/com/fnguy/coin_flip.cljc:2:3
user                                                                  - <expr>:1:10

is it possible to make it self-contained, i.e. not dependent on another namespace in your project and avoid the logging, so it can be tested against a value or so?

no sweat if you don't but it would make testing easier and more robust.

I'll get back to you with a snippet a bit later

👍 1

I merged the PR. Feel free to follow up with something later. Once master CI finishes you can test with:

bash <(curl )  --dev-build --dir /tmp

I'll probably make a release for this, since it's annoying it broke again

Sounds good. Thanks again. I'll add a PR for a minimal statecharts test today or tomorrow. Just got back from a short vacation and have a few things to juggle.

sure, no hurry and thanks in advance!

Do you just throw whatever code you want to try running into that statechart_test.clj file? No asserts and stuff?

no, all the code are deftest + is based tests or I just straightup run the library code of existing libs

only in special cases do I copy tests + adapt them a bit but with statecharts, I just don't know where to begin

I'd do a smoketest myself if I saw an example in the README but it's pretty terse

Would this do?

(ns babashka.statecharts-test
  (:require
    [com.fulcrologic.statecharts :as sc]
    [com.fulcrologic.statecharts.chart :refer [statechart]]
    [com.fulcrologic.statecharts.elements :refer [state transition]]
    [com.fulcrologic.statecharts.events :refer [new-event]]
    [com.fulcrologic.statecharts.protocols :as sp]
    [com.fulcrologic.statecharts.simple :as simple]))

(def light-switch
  (statechart {:initial :off}
    (state {:id :on}
      (transition {:event :toggle, :target :off}))
    (state {:id :off}
      (transition {:event :toggle, :target :on}))))

(let [;; 1. Set up the environment and processor
      env       (simple/simple-env)
      _         (simple/register! env ::switch light-switch)
      processor (::sc/processor env)

      ;; 2. Start the machine and get initial state (s0)
      _  (println "--- Starting Light Switch Machine ---")
      s0 (sp/start! processor env ::switch {::sc/session-id 1})
      _  (println "Initial state:" (::sc/configuration s0))

      ;; 3. Send the first event, get next state (s1)
      _  (println "\nSending event: :toggle")
      s1 (sp/process-event! processor env s0 (new-event :toggle))
      _  (println "Current state:" (::sc/configuration s1))

      ;; 4. Send the second event, get final state (s2)
      _  (println "\nSending event: :toggle")
      s2 (sp/process-event! processor env s1 (new-event :toggle))]

  ;; 5. Print the final state from the last step
  (println "Current state:" (::sc/configuration s2)))

Pretty simple, just had Gemini whip it up with few small adjustments.

fails with v1.12.201

Oh, I think I misread your last message. So you would prefer a few assertions against the lib fn calls?

ok let me try it

works:

Ran 1 tests containing 3 assertions.
0 failures, 0 errors.
{:test 1, :pass 3, :fail 0, :error 0}

Cool. That should cover the basics to make sure the lib can run in bb, I think.

thanks, I'll add it