Cannot get REPL prompt with figwheel-main and Clojure Tools - clojure

My goal is to have a ClojureScript REPL (targeting Node.js) using Figwheel and Clojure Tools (not Leiningen). But when I launch Figwheel it seems to start correctly but never completes.
deps.edn
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/clojurescript {:mvn/version "1.11.60"}}
:aliases {:fig {:extra-deps {com.bhauman/figwheel-main {:mvn/version "0.2.18"}}}}
:paths ["src" "target" "resources"]}
dev.cljs.edn
{:main cljs-example.foo
:target :nodejs
:optimizations :none
:pretty-print true
:source-map true
:asset-path "js/dev"
:output-to "resources/public/js/dev.js"
:output-dir "resources/public/js/dev"}
Launch command
clj -M:fig -m figwheel.main -b dev -r
% clj -M:fig -m figwheel.main -b dev -r
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[Figwheel] Compiling build dev to "resources/public/js/dev.js"
[Figwheel] Successfully compiled build dev to "resources/public/js/dev.js" in 0.708 seconds.
[Figwheel] Watching paths: ("src") to compile build - dev
[Figwheel] Starting Server at http://localhost:9500
[Figwheel] Starting REPL
Prompt will show when REPL connects to evaluation environment (i.e. Node)
Figwheel Main Controls:
(figwheel.main/stop-builds id ...) ;; stops Figwheel autobuilder for ids
(figwheel.main/start-builds id ...) ;; starts autobuilder focused on ids
(figwheel.main/reset) ;; stops, cleans, reloads config, and starts autobuilder
(figwheel.main/build-once id ...) ;; builds source one time
(figwheel.main/clean id ...) ;; deletes compiled cljs target files
(figwheel.main/status) ;; displays current state of system
Figwheel REPL Controls:
(figwheel.repl/conns) ;; displays the current connections
(figwheel.repl/focus session-name) ;; choose which session name to focus on
In the cljs.user ns, controls can be called without ns ie. (conns) instead of (figwheel.repl/conns)
Docs: (doc function-name-here)
Exit: :cljs/quit
Results: Stored in vars *1, *2, *3, *e holds last exception object
Starting node ...
Node output being logged to: resources/public/js/dev/node.log
For a better development experience:
1. Open chrome://inspect/#devices ... (in Chrome)
2. Click "Open dedicated DevTools for Node"
And that's it! :(
It stays like that forever and I never get a prompt such as:
cljs.user=>
Addendum
I don't think my foo.cljs file has an error that would cause Figwheel to hang. When I launch a "native" REPL with:
clj -M -m cljs.main --target node --compile cljs-example.foo --repl
Then I get:
ClojureScript 1.11.60
cljs.user=> (require '[cljs-example.foo])
i am foo!!!!!!!!
nil
cljs.user=>

Related

requre deps in idea clojure repl, get FileNotFoundException

I created a new Leiningen project in idea, and imported some deps in project.clj as follows:
deps
and deps seems to be imported:
external libraries
But when I try to run repl, and required some deps that I imported in project.clj before, an FileNotFoundException has occurred:
FileNotFoundException
Starting nREPL server...
"E:\Program Files\Java\jdk-11.0.8\bin\java.exe" -Dfile.encoding=GBK -XX:-OmitStackTraceInFastThrow -Dclojure.compile.path=E:\idea_projects\clojure_test_second\target\classes -Dclojure_test_second.version=0.1.0-SNAPSHOT -Dclojure.debug=false "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.3\lib\idea_rt.jar=50688:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.3\bin" -classpath E:\idea_projects\clojure_test_second\test;E:\idea_projects\clojure_test_second\src;E:\idea_projects\clojure_test_second\dev-resources;E:\idea_projects\clojure_test_second\resources;E:\idea_projects\clojure_test_second\target\classes;C:\Users\asus\.m2\repository\org\clojure\clojure\1.10.1\clojure-1.10.1.jar;C:\Users\asus\.m2\repository\org\clojure\spec.alpha\0.2.176\spec.alpha-0.2.176.jar;C:\Users\asus\.m2\repository\org\clojure\core.specs.alpha\0.2.44\core.specs.alpha-0.2.44.jar;C:\Users\asus\.m2\repository\hiccup\hiccup\1.0.5\hiccup-1.0.5.jar;C:\Users\asus\.m2\repository\clojure\jdbc\clojure.jdbc\0.4.0\clojure.jdbc-0.4.0.jar;C:\Users\asus\.m2\repository\com\h2database\h2\1.4.193\h2-1.4.193.jar;C:\Users\asus\.m2\repository\nrepl\nrepl\0.6.0\nrepl-0.6.0.jar;C:\Users\asus\.m2\repository\clojure-complete\clojure-complete\0.2.5\clojure-complete-0.2.5.jar clojure.main -i C:\Users\asus\AppData\Local\Temp\form-init14732922726375964945.clj
Connecting to local nREPL server...
Clojure 1.10.1
nREPL server started on port 50817 on host 127.0.0.1 - nrepl://127.0.0.1:50817
(require '[clojure.java.jdbc :as jdbc])
Execution error (FileNotFoundException) at clojure-test-second.core/eval1555 (form-init14732922726375964945.clj:1).
Could not locate clojure/java/jdbc__init.class, clojure/java/jdbc.clj or clojure/java/jdbc.cljc on classpath.
jdk version is 11, idea version is 2021.1.3.
I searched solution for a long time, but didn't solve it. And I am a noob in clojure.
Thanks.
You have to
add [org.clojure/java.jdbc "0.7.12"] in project.clj file of your leiningen project folder. under :dependencies [ <add into this list> [org.clojure/java.jdbc "0.7.12"]].
Then you do $ lein deps from inside folder of your leiningen project folder, to ensure installation of that dependencies.
Then if you did M-x cider-jack-in from inside emacs opened inside project folder, you can do your require command. OR you just do $ lein repl from there and you can then do your (require '[clojure.java.jdbc :as j]) - voila!
Coming from Common Lisp, I was also quite lost like you.
Clojure requires Leiningen or Boot to function reasonably.
Whenever you have to install Clojure in a new machine, install first Leiningen, because you can install Cloure also using Leiningen.
With Boot, you can even create standalone scripts/executives which uses Clojure.
But also with Leiningen see here.
Install and use Boot
I think the way you want to use the repl - more freely -
suits more to Boot:
# install boot e.g. by (for linux)
$ sudo bash -c "cd /usr/local/bin && curl -fsSLo boot https://github.com/boot-clj/boot-bin/releases/download/latest/boot.sh && chmod 755 boot"
# upgrade!
$ boot -u
# create your project folders and move into toplevel
mkdir -p my-project/src
cd my-project
# then open repl
$ boot repl
;; within the repl:
;; declare resource-paths and dependencies
(set-env! :resource-paths #{"src"}
:dependencies '[[org.clojure/java.jdbc "0.7.12"]])
;; create a minimal project declaration
(task-options!
pom {:project 'my-project
:version "0.1.0"}
jar {:manifest {"Foo" "bar"}})
;; build the minimal project (to install the dependencies!)
(deftask build
"Build my project."
[]
(comp (pom) (jar) (install)))
;; call build
(build)
;; now, in the repl, you can do:
(require '[org.clojure/java.jdbc :as j])
Boot is more dynamic - since you can from the repl introduce dependencies and build.

Clojure: how to tell if code is running in the REPL or a JAR?

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

What is equivalent of leiningen :repl-options {:init-ns 'user} for tools.deps in Clojure?

I'm using Cursive and I set aliases to dev, when I run the REPL it does not load the namespace that defined in deps configuration file:
:aliases {:dev {:main-opts ["--init" "src/my/server/core.clj"
"--eval" "(my.server.core/-main)"]}}
I tried this at the command-line and it worked as expected, loading my.server.core and then running its -main function, so I suspect that Cursive is using -R on aliases rather than -A so it is only pulling in :extra-deps and not :main-opts (that's just a guess, I don't use Cursive). My best suggestion is to ask in the #cursive channel on the Clojurians Slack as that is the primary channel for Cursive support (as far as I know).
I'll also highlight Krisztian's comment that you could use "-m" "my.server.core" as your entire :main-opts since -m will load the namespace and run the -main within it.
However, those options are not the same as Leiningen's :init-ns -- what I think you need is:
{:aliases {:dev {:main-opts ["-e" "(require,'my.server.core)"
"-e" "(in-ns,'my.server.core)"]}}}
When you specify :main-opts, that will suppress starting a REPL:
$ clj -A:dev
#object[clojure.lang.Namespace 0x3dddbe65 "my.server.core"]
$
So you need to add -r to tell the Clojure CLI that you also want a REPL started:
clj -A:dev -r
#object[clojure.lang.Namespace 0x433ffad1 "my.server.core"]
my.server.core=> (doc -main)
-------------------------
my.server.core/-main
([& args])
I don't do a whole lot ... yet.
nil
my.server.core=>

Launch the REPL from within my app

Suppose that I have implemented a few functions for some personal calculation at work. I'd like to build a .jar (uberjar) that my colleagues would use too in a REPL, like:
megacorpcalcs.core=> (+ 2 2)
4
megacorpcalcs.core=> (salary 8 0.4)
666$
What code should I type for a REPL to start when the .jar launches?
EDIT: after your comments I've split this answer into 2
1. Running your uberjar with a REPL
Create your uberjar, and start it with:
java -cp /path/to/your/application-X.Y.Z-standalone.jar clojure.main -i #your_application/foo.clj -r
I had to add the -i parameter and point it to one of my clj files in order to get any of my classes in the project to actually load in the repl. There may be a better way to do this, but I haven't found it yet. Without it, you get a standard clojure repl but your application isn't loaded.
Note, you need the # symbol so that it loads from the jar file relative to the classpath (i.e from root of jar).
This should start a repl which you can change namespace and run your application functions in.
Additionally, you can install rlwrap and prepend the java command with it so you can use history and arrow keys sanely.
2. Embedding a REPL server in your application to connect to from another client
You can embed your own repl on startup of your application (e.g. a simple main that just starts a repl instance), and then your users can run your jar file, and separately connect to it with their choice of tool (cider-jack-in, 'lein repl connect ...')
The simple version of this is:
(start-server :port 7890 :handler cider-nrepl-handler)
Substitute the port you want, see below for the appropriate namespaces to import.
Here is a more complete example:
(ns your-app.server
(:gen-class)
(:require [cider.nrepl :refer (cider-nrepl-handler)]
[clojure.tools.nrepl.server :refer [start-server]]))
(def repl-server (atom nil))
(defn create-nrepl-server!
[repl-port]
(println (format "starting nrepl server on port %d" repl-port))
(reset! repl-server (start-server :port repl-port :handler cider-nrepl-handler)))
(defn -main []
;; ...
(let [repl-port 7890]
(create-nrepl-server! repl-port)
(spit ".nrepl-port" repl-port)))
You'll need the following in your project.clj file
:plugins [[cider/cider-nrepl "0.10.0"]] ;; or whatever version you prefer
:dependencies [[org.clojure/tools.nrepl "0.2.12"]]
Once connected to your custom repl, standard rules apply, just change namespace and call your functions.

How can I run lein repl outside of a project?

I spent some time last night messing with my leinigen profiles.clj to get rid of all the errors that were being printed when starting cider in my project. Today I went to start a repl from the terminal (I like to keep one open while I work) but it didn't work. I thought it was a cider issue so I tried it from Emacs but even in Emacs if I'm not in a project the repl won't start.
Here's the error:
Error loading refactor-nrepl.middleware: clojure.lang.ArityException: Wrong number of args (4) passed to: StringReader, compiling:(abnf.clj:186:28)
Exception in thread "Thread-4" java.lang.RuntimeException: Unable to resolve var: refactor-nrepl.middleware/wrap-refactor in this context, compiling:(NO_SOURCE_PATH:0:0)
...
Caused by: java.lang.RuntimeException: Unable to resolve var: refactor-nrepl.middleware/wrap-refactor in this context
My ~/.lein/profiles.clj
{:user {:plugins [[lein-try "0.4.3"]
[refactor-nrepl "1.1.0"]
[cider/cider-nrepl "0.9.1"]]
:dependencies [[org.clojure/tools.nrepl "0.2.12"]
[acyclic/squiggly-clojure "0.1.4"]
^:replace [org.clojure/tools.nrepl "0.2.12"]
[refactor-nrepl "1.1.0"]]}}
The versions of things when cider starts in a project
; CIDER 0.9.1 (Java 1.8.0_45, Clojure 1.7.0, nREPL 0.2.12)
I'm still pretty new to Clojure, Leinigen, Emacs, etc so I'm not sure why everything above made made my cider errors go away but it did. The cider errors I was getting were having to do with the nrepl version being too low and not having certain things installed (like refactor-nrepl).
When starting a repl from lein using lein repl, it really wants to run in a lein project dir. I keep an empty lein project named clj around in my home dir for this purpose. That way, my common dependencies are already there in the project.clj file, and lein is pre-configured just the way I like it.
You can start lein repl in an empty dir, but you get 10-20 error messages each time before it starts.
Another way is to use the plain repl built into the clojure jar file:
~/dummy > cp /home/alan/.m2/repository/org/clojure/clojure/1.8.0-RC1/clojure-1.8.0-RC1.jar .
~/dummy > d *
-rw-rw-r-- 1 alan alan 3935726 Nov 19 14:11 clojure-1.8.0-RC1.jar
~/dummy > java -jar clojure-1.8.0-RC1.jar
Clojure 1.8.0-RC1
user=>
As you can see, I created an empty directory named dummy and copied in the clojure-*.jar file. You can then run it with the syntax java -jar xxx.jar and it will fire up a repl completely independently of lein.
I also just keep a scratch project which I use for quick/simple repl sessions. There is a lien-oneoff plugin which is supposed to make it easy to work with simple single file lein projects which might be useful.
The other thing you could do is setup a boot config for basically getting a repl up to work with
what is your lein version, I am use 2.5.3, I can start lein repl anywhere.
Shell:~ >: lein repl
nREPL server started on port 52343 on host 127.0.0.1 - nrepl://127.0.0.1:52343
REPL-y 0.3.7, nREPL 0.2.10
Clojure 1.7.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_60-b27
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=> Bye for now!
Shell:~ >: lein version
Leiningen 2.5.3 on Java 1.8.0_60 Java HotSpot(TM) 64-Bit Server VM
Shell:~ >: cat .lein/profiles.clj
{:1.2 {:dependencies [[org.clojure/clojure "1.2.0"]]}
:1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]}
:1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]}
:user {:plugins [[lein-immutant "2.0.0-alpha2"]
[lein-clojars "0.9.1"]
[lein-ancient "0.5.5"]
[lein-kibit "0.0.8"]
[lein-try "0.4.3"]
[venantius/ultra "0.2.0"]]
:ultra {:color-scheme :solarized_dark}}}