bhauman / figwheel-main

Figwheel Main provides tooling for developing ClojureScript applications
https://figwheel.org
Eclipse Public License 1.0
640 stars 93 forks source link

File watching fails on MacOS Big Sur beta #253

Closed chancerussell closed 2 years ago

chancerussell commented 4 years ago

When starting up file watching via wkf/hawk, an exception is thrown. After a little digging, this looks to be caused by changes to how MacOS loads dylibs in 11.0+.

It looks like the JNA folks are already working on a fix, so once that lands, we can update (or fork) BarbaryWatchService, then update wkf/hawk to use it.

Stack trace:

[Figwheel:SEVERE] java.lang.UnsatisfiedLinkError: Unable to load library 'Carbon': dlopen(libCarbon.dylib, 9): image not found
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class com.barbarysoftware.jna.CarbonAPI
        at com.barbarysoftware.jna.CFStringRef.toCFString(CFStringRef.java:10)
        at com.barbarysoftware.watchservice.MacOSXListeningWatchService.register(MacOSXListeningWatchService.java:30)
        at com.barbarysoftware.watchservice.WatchableFile.register(WatchableFile.java:30)
        at com.barbarysoftware.watchservice.WatchableFile.register(WatchableFile.java:39)
        at hawk.watcher$fn__133.invokeStatic(watcher.clj:102)
        at hawk.watcher$fn__133.invoke(watcher.clj:99)
        at hawk.watcher$fn__38$G__29__47.invoke(watcher.clj:24)
        at hawk.core$watch_BANG_.invokeStatic(core.clj:83)
        at hawk.core$watch_BANG_.doInvoke(core.clj:59)
        at clojure.lang.RestFn.invoke(RestFn.java:410)
        at clojure.lang.AFn.applyToHelper(AFn.java:154)
        at clojure.lang.RestFn.applyTo(RestFn.java:132)
        at clojure.core$apply.invokeStatic(core.clj:657)
        at clojure.core$apply.invoke(core.clj:652)
        at figwheel.main.watching$alter_watches.invokeStatic(watching.clj:17)
        at figwheel.main.watching$alter_watches.invoke(watching.clj:11)
        at clojure.lang.Atom.swap(Atom.java:51)
        at clojure.core$swap_BANG_.invokeStatic(core.clj:2345)
        at clojure.core$swap_BANG_.invoke(core.clj:2337)
        at figwheel.main.watching$add_watch_BANG_.invokeStatic(watching.clj:22)
        at figwheel.main.watching$add_watch_BANG_.invoke(watching.clj:21)
        at figwheel.main.css_reload$start_STAR_.invokeStatic(css_reload.cljc:188)
        at figwheel.main.css_reload$start_STAR_.invoke(css_reload.cljc:187)
        at clojure.lang.Var.invoke(Var.java:381)
        at figwheel.main$watch_css.invokeStatic(main.cljc:1188)
        at figwheel.main$watch_css.invoke(main.cljc:1185)
        at figwheel.main$config_watch_css$fn__7373.invoke(main.cljc:1195)
        at figwheel.main$default_compile.invokeStatic(main.cljc:1848)
        at figwheel.main$default_compile.invoke(main.cljc:1812)
        at figwheel.main$build_main_opt.invokeStatic(main.cljc:526)
        at figwheel.main$build_main_opt.invoke(main.cljc:521)
        at cljs.cli$main.invokeStatic(cli.clj:636)
        at cljs.cli$main.doInvoke(cli.clj:625)
        at clojure.lang.RestFn.applyTo(RestFn.java:139)
        at clojure.core$apply.invokeStatic(core.clj:659)
        at clojure.core$apply.invoke(core.clj:652)
        at cljs.main$_main.invokeStatic(main.clj:61)
        at cljs.main$_main.doInvoke(main.clj:52)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invokeStatic(core.clj:657)
        at clojure.core$apply.invoke(core.clj:652)
        at figwheel.main$_main$fn__7768.invoke(main.cljc:2165)
        at clojure.core$with_redefs_fn.invokeStatic(core.clj:7434)
        at clojure.core$with_redefs_fn.invoke(core.clj:7418)
        at figwheel.main$_main.invokeStatic(main.cljc:2163)
        at figwheel.main$_main.doInvoke(main.cljc:2142)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:702)
        at clojure.core$apply.invokeStatic(core.clj:657)
        at clojure.main$main_opt.invokeStatic(main.clj:317)
        at clojure.main$main_opt.invoke(main.clj:313)
        at clojure.main$main.invokeStatic(main.clj:424)
        at clojure.main$main.doInvoke(main.clj:387)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:702)
        at clojure.main.main(main.java:37)
bhauman commented 4 years ago

@chancerussell does the following watcher work on Big Sur?

https://github.com/gmethvin/directory-watcher

bhauman commented 4 years ago

Actually forget that.

It would be nice if we could fall back on a simple java based watcher when things like this happen.

chancerussell commented 4 years ago

Yeah, according to their issues they’re bit by the same exact problem.

rgm commented 4 years ago

I found a temporary workaround by falling back to polling, so Figwheel can at least run in a (slightly) degraded fashion. As far as I can tell, the major cost is degraded battery / higher power consumption.

The hawk lib checks to see if it's working on macOS, and uses a multimethod to set up a Barbary watcher. Thus, I can just swap in that multimethod before I start up figwheel. In my user.clj I've added:

(ns user
  (:require [figwheel.main.api :as fig]))

;; Patch hawk for macOS Big Sur
;; hawk can't find the carbon lib any more, so the underlying barbary watcher
;; doesn't work for getting OS-level file change events. We have to fall back to
;; polling by monkey-patching the barbary-vending multimethod for now.
(import [hawk SensitivityWatchEventModifier])
(require '[hawk.watcher])
(defmethod hawk.watcher/new-watcher :barbary [_]
  (prn "WARN - falling back to filesystem polling on macOS")
  (let [sensitivity SensitivityWatchEventModifier/HIGH]
    (hawk.PollingWatchService. sensitivity)))

(fig/start {:mode :serve} "dev")

It doesn't have to be in user.clj, but it does have to run before Figwheel gets going. I start the Figwheel process manually, so I haven't tried it with clj -m figwheel.main, but I would hope this would load and patch OK in that circumstance too.

rgm commented 4 years ago

Of course, now that I've taken enough time to look through the code in preparing a PR, I see that we can explicitly opt-out of using the OS's FS events with https://figwheel.org/config-options.html#hawk-options . I don't think I understood why that would be useful before. This is a lot simpler than my shenanigans above.

bhauman commented 4 years ago

In summary, this problem is a temporary problem that is being worked on and will hopefully be fixed by the time Big Sur is released this fall.

Folks that are using the Big Sur preview need to fall back to a :polling watcher in the :hawk-options Figwheel option.

I.E.

:hawk-options {:watcher :polling}

Big thanks to @rgm for taking the time to track this down.

rgm commented 4 years ago

I'm not sure where this leaves the "real" fix ... is it something like having figwheel rely directly on the patched jna dependency in deps.edn once one's available? Or do we work through getting the upstream deps updated through barbary -> hawk -> figwheel?

bhauman commented 4 years ago

We'll make sure that hawk has this fix.

dbwiddis commented 4 years ago

FYI, JNA 5.6.0 has been released.

rgm commented 4 years ago

@bhauman I got to thinking, dealing with this issue: would the ability to manually trigger builds be a useful add? I used to run into polling battery-drains in Meteor.js because of how they originally implemented their own file watcher and so I've tried to rely as much as possible on more dedicated tools like http://eradman.com/entrproject/ instead of a hodgepodge of a SASS watcher here and a gulp thing there, with another for the test watcher, etc. etc.

In Gary Bernhardt's old Destroy All Software videos he would set up a manual unit test trigger in his editor (eg. for vim it would be something like :map ,t :wa\|!ruby -Ilib %<CR> so that every time you hit ,t in normal mode, the focused unit test on the current file would run). Over time, I've come to personally prefer this approach. I know it's not a beginner thing so I don't think dumping all file watching code is very realistic.

But it did get me thinking: could the default Figwheel server on 9500 listen for a POST, instead of watching the filesystem? This would be reasonably straightforward to hook up per the above, eg. :map ,t :wa\|!curl -X POST http://localhost:9500/_build for us vimmers, or if using the Entr util, in another shell window:

> find src -name ".clj*" | entr curl -X POST http://localhost:9500/_build`.

I just got to thinking I'd been here before with Javascript file watchers rotting, and maybe this could add some extra advanced abilities, eg. being able to observe static EDN or JSON data under /resources, or rebuild when .clj sources change instead of just .cljs or .cljc files change.

I guess the challenge would be to communicate to Figwheel which namespaces to hot-reload ... maybe by hashing the contents of known files on the classpath?

I'm happy to try taking a crack at this if you think it's a reasonable idea.

(Apologies if some or all of this already exists. Often I don't appreciate the words in the manual until I re-read them after musing like this for a while).

bhauman commented 4 years ago

@rgm In order to capture the full behavior of figwheel a triggered build would have to run through all the files for changes, because figwheel watches javascript and plain Clojure files as well. After the build figwheel can do its own detection of what has changed and do its magic. So really this would be a manual file poll.

This is what gets called when the watcher finds changes: https://github.com/bhauman/figwheel-main/blob/master/src/figwheel/main.cljc#L266

This of course would also affect CSS watching etc.

Creating an end point for this makes sense. But of course there is also the REPL connection so you could add a build function to the figwheel.main.api which is intended to be used from the cljs repl. There is currently a figwheel.main/build-once in the repl but it's not very efficient and it doesn't handle the js and clj files.

This deserves more thought.

rgm commented 4 years ago

Creating an end point for this makes sense. But of course there is also the REPL connection so you could add a build function to the figwheel.main.api which is intended to be used from the cljs repl. There is currently a figwheel.main/build-once in the repl but it's not very efficient and it doesn't handle the js and clj files.

Hm, I hadn't really followed but I had some time to read through tonight. Perhaps it's enough to bind that (bound-fn ,,,) to something we can get a hold of from an editor repl connection, presuming that calling it is enough to kick off the whole rebuild -> hot-reload process. (Which I presume it does if it's all hawk knows about; I had some trouble following things all the way through). If it were bound in the build registry, it'd be pretty straightforward to map a shortcut to it, and just leave the file watching off entirely.

il-tmfv commented 3 years ago

After upgrading to macOS 11.5.1 with M1 chip I started to get similar error when starting figwheel-main:

...
[Figwheel:SEVERE] /private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/jna4678795419772323478.tmp: dlopen(/private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/jna4678795419772323478.tmp, 1): no suitable image found.  Did find:
    /private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/jna4678795419772323478.tmp: no matching architecture in universal wrapper
    /private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/jna4678795419772323478.tmp: no matching architecture in universal wrapper
[Figwheel] Validating figwheel-main.edn
...
Syntax error (NoClassDefFoundError) compiling at (/private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/form-init5239701982387845518.clj:1:125).
Could not initialize class com.barbarysoftware.jna.CarbonAPI
$ java -version                              
openjdk version "1.8.0_302"
OpenJDK Runtime Environment (Zulu 8.56.0.23-CA-macos-aarch64) (build 1.8.0_302-b08)
OpenJDK 64-Bit Server VM (Zulu 8.56.0.23-CA-macos-aarch64) (build 25.302-b08, mixed mode)

This setting helps:

:hawk-options {:watcher :polling}

but feels not great.

I've tried to run master branch version and it works fine. Looks like https://github.com/bhauman/figwheel-main/pull/299 helps not only with the speed 🙂

Could you, please publish it if possible?

kendagriff commented 3 years ago

I'm experiencing the same thing on the Apple M1. Any update on a new release?

EDIT: Ack, my bad! My issue is purely wkf/hawk related, not figwheel. Sorry!

timothypratley commented 2 years ago

@bhauman this issue can be closed (see comment by @il-tmfv) a release has seen been made, so AFAIK this is resolved.