I've been with figwheel for most of the day, then suddenly after a restart (not the first) it started to fail to compile.
I get the following message when I run lein figwheel.
Figwheel: Starting server at http://localhost:3449
Figwheel: Watching build - dev
[0mCompiling "resources/public/js/compiled/game.js" from ["src"]...
{:file #object[java.net.URL 0x79b3937a "file:/home/dan/dev/org/danjoe/game/src/game/state.cljs"], :line 1, :column 1, :tag :cljs/analysis-error}
ANALYSIS ERROR: at line 1 file:/home/dan/dev/org/danjoe/game/src/game/state.cljs on file file:/home/dan/dev/org/danjoe/game/src/game/state.cljs, line 1, column 1
Subprocess failed
I checked out some stable code from earlier and it still fails.
I blew away everything in my ~/.m2 directory and went again. I checked out a new copy of the repo and got the same error there.
The only meaningful part of the error is the reference to an ANALYSIS ERROR on line 1 of some file — just for the sake of my sanity, here it is.
(ns game.state
(:refer-clojure :exclude [get])
(:require [reagent.core :as reagent]
[game.views.heroes :as default-view]))
If I go to that file and deliberately break the namespace (switch it to something incorrect) then run lein figwheel again, the analysis error simply switches to point at another file in my project instead. If I break all the namespaces, it then starts to throw analysis errors for line 2 (or wherever the next s-expression is).
Here are the project dependencies.
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]
[org.clojure/core.async "0.2.374"]
[reagent "0.5.0"]
[secretary "1.2.0"]]
And the plugins I'm using.
:plugins [[lein-cljsbuild "1.1.1"]
[lein-figwheel "0.5.0-1"]]
It sounds like you may have some cached javascript which is causing the compile to fail.There are lots of things which can cause this, but I have run into this problem when I've updated a dependency to use a later version. The problem is that figwheel does not realise your cljs file depends on the changed dependency version, so does not re-compile the source.
The fix is to run lein clean. However, note that you also need to set the :clean-targets key in your project.clj file. Thie value is a list of directories to be cleaned when you run lein clean. By default, it just cleans what is below target. However, most people tend to put their javascript below resources/public. I have the following in my project.clj
:clean-targets ^{:protect false} [:target-path
[:cljsbuild :builds :app :compiler
:output-dir]
[:cljsbuild :builds :app :compiler
:output-to]]
which basically adds the directories where I store output from cljsbuild to the set of directories defined in :target-path. Running lein clean will now remove what is in :output-dir and :output-to. Note that you must include the :protect false value otherwise lein will ignore your additions.
The error ended up being a circular dependency, but due to a bug in Clojurescript 1.7.170 the analyzer wasn't catching the dependency and instead recursively analyzing both dependencies in an infinite cycle (until the stack overflowed).
The bug is now fixed in master, but I'll leave a rundown of how it was identified here anyway.
Use the standalone Clojurescript compiler to make sure that the tooling isn't swallowing errors.
Compile with the {:verbose true} option.
See the debugging output start to show the circular dependency:
Reading analysis cache for jar:file:/home/dan/Downloads/cljs.jar!/cljs/core.cljs
Compiling src/game/ui/widgets.cljs
Analyzing file:/tmp/lispjam/src/game/state.cljs
Analyzing file:/tmp/lispjam/src/game/views/heroes.cljs
Analyzing file:/tmp/lispjam/src/game/state.cljs
Analyzing file:/tmp/lispjam/src/game/views/heroes.cljs
Analyzing file:/tmp/lispjam/src/game/state.cljs
Analyzing file:/tmp/lispjam/src/game/views/heroes.cljs
Analyzing file:/tmp/lispjam/src/game/state.cljs
Analyzing file:/tmp/lispjam/src/game/views/heroes.cljs
Analyzing file:/tmp/lispjam/src/game/state.cljs
Analyzing file:/tmp/lispjam/src/game/views/heroes.cljs
Analyzing file:/tmp/lispjam/src/game/state.cljs
...
Resolving the circular dependency fixed everything, but in future versions of Clojurescript, you should get a compile time analysis warning, letting you know that your code has a circular dependency.
Related
I am writing a CLI framework in Clojure called OneCLI. The main center piece of this framework is a function called go! which parses the command line, environment variables, and config files "for you" and runs one of several different user provided functions based on what was provided in those inputs.
Typically, go! is called from the -main function of the user's calling Clojure program. I use my own library, for example, in another "uberjar" style app called zic. The function go! calls System/exit as part of its run, passing it an exit code that comes from the result of the user provided function. This works great "in production", but it also means that I can't run the zic.cli/-main function from the REPL, as whenever I do it calls System/exit and the REPL exits.
Before you ask, running it from the REPL while developing on a raspberry pi avoids the expensive 45 seconds it takes to run lein uberjar/1 minute 30 seconds to run clj -X:depstar uberjar :jar ....
My question is: Is there some var or value I can check as part of Clojure's standard library that tells my OneCLI code whether it's running from the REPL or if it's running from a JAR?
Such a variable would enable me in OneCLI to detect that we're running from a REPL so that it can avoid calling System/exit.
Instead of trying to have one function that magically detects what environment you're running from, it's quite simple to just have two functions that behave differently.
Extract out the shared behavior to a function that is not part of -main. Call it run or whatever.
Have -main call that function, and then call System/exit
When you wish to use the program from a repl, call run instead of -main. It will finish normally, and not call System/exit.
I don't know how to detect if you're running at a REPL. I took a quick look through Clojure's launching code (clojure.main), but I didn't see any hooks to detect whether you're in a REPL compared to something run via clojure -m.
If you're using AOT (like you are in zic) then you could check whether any of the "REPL" variables (*1, *2, *3, and *e) are bound.
;; returns true in a REPL and `clojure -m`, and
;; returns false in an AOT jar file run with java -jar
(bound? #'*1)
This solves your question as it was asked, but I don't love this "magical" mechanism of guessing the programmer's intent. It might work for your use case (given I think AOT saves on startup time, and CLI tools probably want to start quickly), but none of the projects I work on use AOT at all.
Another option to solve your problem in the clojure -m case as well would be to require developers to explicitly opt out of the "exit on completion" behaviour. One way to do that could be to use a property.
(defn maybe-exit [exit-code]
(cond
(= (System/getProperty "onecli.oncompletion") "remain") (System/exit exit-code)
(= exit-code 0) nil
:else (throw (ex-info "Command completed unsuccessfully" {:exit-code exit-code}))))
Using this code, in a development environment you can add
:jvm-opts ["-Donecli.oncompletion=remain"]
to your deps.edn or project.clj file, but leave it out when running "in production". This has the advantage of being more explicit, but the cost is that developers have to be more explicit.
This is an interesting question because it's usually dreadful to put JVM shutdown into a library, but on the other hand a "real app" involves lots of boilerplate that would be great to share... such as hiding the jar's splash gif at the right time, or (re)opening a Windows terminal if the app wants stdio.
Your uberjar will contain clojure.main, so it is quite possible (and useful) to run the REPL in your uberjar (java -cp my-whole-app.jar clojure.main). Therefore, "detecting" clues on the classpath might not help.
Instead, manage JVM-shutdown work in the -main in the namespace that your jar's manifest declares as its Main-Class. That is: if you run it as java -jar my-whole-app.jar, then it should shut everything down properly.
But I do not always want -main to shut everything down, you say. Then you need two -mains. Make a second -main in a different namespace. Let the jar's Main-Class -main do absolutely nothing but (1) delegate to the second main and (2) shut down the JVM at the end. When you're in the REPL, invoke the second -main, the one that won't clobber the JVM. You can factor out most of each -main into a library. If you went "full framework" you could even make the framework own the uberjarring process and the Main-Class.
Every Java JAR file must have the file META-INF/MANIFEST.MF
added. If it isn't present, you cannot be running in a (normal) JAR file. While you could fool this detector by putting a bogus file on the classpath (i.e. in ./resources, for example), it is a reliable way of detecting a normal JAR file.
Problem:
Dependency JAR files are sometimes sloppy and will pollute the classpath with their own META-INF/MANIFEST.MF files, so the presence of any random META-INF/MANIFEST.MF is not enough to determine the answer in the presence of "noise" files. So, you need to check for the existence of your own specific META-INF/MANIFEST.MF file. This is easy to do if you know the Maven values for ArtifactId and GroupId.
In a Leiningen project, the first line of project.clj looks like
(defproject demo-grp/demo-art "0.1.0-SNAPSHOT"
for a group ID of demo-grp and an artifact ID of demo-art. If your file looks like this:
(defproject demo "0.1.0-SNAPSHOT"
then both the group ID and artifact ID will be demo. Your particular MANIFEST.MF will look like
> cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: Leiningen 2.9.1
Built-By: alan
Build-Jdk: 15
Leiningen-Project-ArtifactId: demo-art
Leiningen-Project-GroupId: demo-grp
Leiningen-Project-Version: 0.1.0-SNAPSHOT
Main-Class: demo.core
Set up a function using the to ID strings to detect the presence of your particular project MANIFEST.MF:
(ns demo.core
(:require [clojure.java.io :as io])
(:gen-class))
(def ArtifactId "demo-art")
(def GroupId "demo-grp")
(defn jar-file? []
(let [re-ArtifactId (re-pattern (str ".*ArtifactId.*" ArtifactId))
re-GroupId (re-pattern (str ".*GroupId.*" GroupId))
manifest (slurp (io/resource "META-INF/MANIFEST.MF"))
f1 (re-find re-ArtifactId manifest)
f2 (re-find re-GroupId manifest)
found? (boolean (and f1 f2))]
found?))
(defn -main []
(println "main - enter")
(println "Detected JAR file: " (jar-file?))
)
You can now test the code:
~/expr/demo > lein clean ; lein run
main - enter
Detected JAR file: false
~/expr/demo > lein clean ; lein uberjar
Compiling demo.core
Created /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT.jar
Created /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT-standalone.jar
~/expr/demo > java -jar /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT-standalone.jar
main - enter
Detected JAR file: true
Example of "noise" JAR file: If we do a lein clean; lein run, and add a line to our main program
(println (slurp (io/resource "META-INF/MANIFEST.MF")))
we get out:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: jenkins
Created-By: Apache Maven 3.2.5
Build-Jdk: 1.8.0_111
I have no idea where this is coming from to get on the CLASSPATH.
P.S. for Leiningen JAR files
When using lein to build a JAR file, it always places a copy of the project.clj file at the location:
META-INF/leiningen/demo-grp/demo-art/project.clj
so you could also use this file's presence/absence as a detector.
Update
OK, it looks like the the MANIFEST.MF file is highly dependent on your build tool. See
https://docs.oracle.com/javase/tutorial/deployment/jar/defman.html
https://www.baeldung.com/java-jar-manifest
So, your choices appear to be:
For lein, you can use the above technique.
You could use the REPL trick of *1 from the other answer.
You could always have your build tool include a custom key-value pair in the manifest and then detect that.
Update #2
An alternate answer, and perhaps easier, is to use the lein-environ plugin and environ library (you need both) to detect the environment (assuming you are using lein to create your REPL). Your project.clj should look like:
:dependencies [
[clojure.java-time "0.3.2"]
[environ "1.2.0"]
[org.clojure/clojure "1.10.2-alpha1"]
[prismatic/schema "1.1.12"]
[tupelo "21.01.05"]
]
:plugins [[com.jakemccrary/lein-test-refresh "0.24.1"]
[lein-ancient "0.6.15"]
[lein-codox "0.10.7"]
[lein-environ "1.2.0"]
]
and you need a profiles.clj:
{:dev {:env {:env-mode "dev"}}
:test {:env {:env-mode "test"}}
:prod {:env {:env-mode "prod"}}}
and a namespace demo.config like:
(ns demo.config
(:require
[environ.core :as environ]
))
(def ^:dynamic *env-mode* (environ/env :env-mode))
(println " *env-mode* => " *env-mode*)
And then you get results like:
*env-mode* => dev ; for `lein run`
*env-mode* => test ; for `lein test`
*env-mode* => nil ; from `java -jar ...`
You need to type:
lein with-profile :prod run
to produce
*env-mode* => prod
This is asking for help regarding the same question as How to change Clojure or Lein's Clojure default version?
but is not an answer to that question. Should I have nevertheless have written it as an answer under that thread?
Specifying a Clojure version to be used when the "(lein repl)" command is issued outside of a project directory seems to be a natural thing to want. Of course there may be good design reasons for not allowing it, but do you think an error message like the following,
to be displayed when Leiningen detects that the ~/.lein/profiles.clj (or other relevant profile files) specify a different Clojure version from the one hard coded into Leiningen,
would be a good idea?
"specifying the Clojure version to be used with certain software, such as tools.nrepl and clojure-complete, is only allowed when this command is issued within a project directory"
A lot of people apparently have spent a lot of time on Stack Overflow trying to find out how to do this.
References:
"Updating ~/.lein/profiles.clj to {:repl {:dependencies [^:displace [org.clojure/clojure "1.9.0"]]}} does not seem to work. – Petrus Theron Jan 10 2018 at 9:05" How do you change Clojure version in Leiningen and LightTable?
"I asked technomancy on IRC just now. He said: "REPL's outside projects are hard coded to lein's version of clojure". – David J. Feb 24 '14 at 19:52" How to change Clojure or Lein's Clojure default version?
"this ^:displace trick will not work with tools.nrepl or clojure-complete." https://github.com/technomancy/leiningen/blob/master/doc/PROFILES.md
How to upgrade nrepl version of leiningen?
I think you may try to declare different profiles in your project.clj where each of them has its own Clojure version. For example:
:profiles
{;; :default [:base :system :user :provided :dev]
:server-jvm {:jvm-opts ^:replace ["-server"]}
:1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]}
:1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]}
:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]}
:1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
:1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]}}
Now that, when running any Lein command, specify a profile as follows:
lein with-profile 1.7 repl
The REPL session of Clojure 1.7 should start.
I grabbed that fragment from carmine sources. The official Lein profiles manual also has the same example.
I'm trying to launch the latest stable [org.clojure/clojurescript "1.9.946"], using boot-cljs and getting this error: cljs/core.cljs [line 988, col 14] No reader function for tag Inf.
I've learned that 1.9.946 introduced new ##Inf syntax: https://cljs.github.io/api/syntax/Inf. but not sure why I'm getting this error. I've also tried to do this: [adzerk/boot-cljs "2.1.4" :exclusions [org.clojure/clojurescript]] but it changed nothing.
Edit: just tried to build cljs version from repo and hooked up npm deps with lein-npm, server rendering of react worked as expected. May be something to do with boot-cljs deps or some other my components.
Support for ##Inf was added in tools.reader 1.1.0, which ClojureScript 1.9.946 depends upon.
If you see this error, it is because you have tools.reader 1.0.6 or earlier on your classpath.
Note that, while ClojureScript 1.9.946 specifies [org.clojure/tools.reader "1.1.0"] as a dependency, explicitly specifying an older version in your project configuration would cause the issue.
Also note that this is independent of the Clojure version being used. If desired, you can run the ClojureScript 1.9.946 compiler using Clojure 1.8.0 and successfully compile ClojureScript code that makes use of ##Inf; Clojure 1.9.0 is not required.
I'm experimenting with luminus, and all new luminus projects are giving me the cider version/ cider-nrepl version mismatch error when I connect to the repl started by lein run using emacs M-x cider-connect:
WARNING: CIDER's version (0.14.0) does not match cider-nrepl's version (nil). Things will break!
If I ignore the automatically started nrepl and use M-x cider-jack-in to start a new repl I don't see the error. This is what I would normally use for a clojure project but there appear to be certain things that don't work as well using luminus (starting and stopping an h2 database if I recall correctly, but that is another issue).
I have removed my ~/.lein/profiles.clj file and replaced it with one containing just the cider-nrepl plugin, ie:
{:user
{:plugins
[[cider/cider-nrepl "0.14.0"]]}}
I have also tried adding the plugin via the project.clj file but I still get the error.
lein deps :tree gives me a few possible conflicts and suggests exclusions, but none of them involve nrepl or cider.
What am I missing here?
I seems you are not using ciders nrepl but luminus-nrepl - therefor you get
not match cider-nrepl's version (nil)
if you create a luminus project like so
lein new luminus <project-name> +cider
the warning should disappear.
addendum, lein deps :tree (which was a good approach to analyse the problem)
without +cider
[luminus-nrepl "0.1.4"]
[org.clojure/tools.nrepl "0.2.12" :exclusions [[org.clojure/clojure]]]
with +cider
[cider/cider-nrepl "0.15.0-20170626.002218-19"]
[luminus-nrepl "0.1.4"]
[org.clojure/tools.nrepl "0.2.12" :exclusions [[org.clojure/clojure]]]
I've been trying to follow a number of tutorials on building a web app in Clojure, but I keep running into the same problem. To take the simplest case, I tried following this tutorial: http://drtom.ch/posts/2012-12-10/An_Introduction_to_Webprogramming_in_Clojure_-_Ring_and_Middleware/
When I get to the step that starts the server (run-jetty handler {:port 8383}), I get the following error:
NoSuchMethodError org.slf4j.helpers.MessageFormatter.arrayFormat(Ljava/lang/String;[Ljava/lang/Object;)Lorg/slf4j/helpers/FormattingTuple; org.eclipse.jetty.util.log.JettyAwareLogger.log (JettyAwareLogger.java:613)
I asked lien to show me the classpath, and sure enough, org.slf4j.helpers.MessageFormatter isn't in there anywhere.
I've run into this on pretty much every ring-based web tutorial I've tried, so either I've got something configured weird (I updated and reinstalled lein, blew away my ~/.m2 and rebuilt, etc), or something has changed in the myriad dependencies that get put together to make the classpath.
Any ideas what's going on here?
EDIT
I've got further information -- I created a VM in virtualbox, installed OpenJDK and lein, and created a project there. It worked fine. Since I had created it in a directory shared with the host, I then tried doing "lein ring server" in the same directory from the host, and it failed with the above error.
So I did "lein classpath" both in the vm and in the host and compared the results -- they're identical. I also checked that they're running the same build of the same JVM (OpenJDK 64-bit build 24.51-b03).
So, if they're running the same JVM with identical classpaths, what's left?
Can you try updating the dependencies like the following?
(defproject ..........
:dependencies [[org.clojure/clojure "1.5.1"]
[ring/ring-core "1.1.8"]
[ring/ring-jetty-adapter "1.1.8"]
[compojure "1.1.3"]]
:main quickstart.core
:min-lein-version "2.0.0"
:plugins [[lein-ring "0.8.10"]]
:ring {:handler quickstart.core/handler})
If you use the lein ring plugin as configured above, you can start the application like:
lein ring server