Clojure: How to set :repositories in project.clj - clojure

Im working on compiling jars from a local repository in leiningen. This works in my project.clj:
:repositories {"local" ~(str (.toURI (java.io.File. "local_mvn_repo")))}
but this fails:
:repositories [["local" (str (.toURI (java.io.File. "local_mvn_repo")))]]
$ lein deps
java.lang.UnsupportedOperationException: nth not supported on this type: Symbol
Even though the latter looks in compliance with the official example. My question is this:
What does the ~ do above, which do I need it, and why can't i use the vector form?

The ~ is the unquoting function in this case, it tells lieningen to run the form after it and use the value produced by running it instead of trying to use it directly.
The first example if the format for Leiningen version 1.x while the second from is the newer form and is failing because it is missing the ~ and some { }
:repositories [["local" {:url ~(str (.toURI (java.io.File. "local_mvn_repo")))}]]
ps: I'm not sure if the map form is required and I'm assuming you are using lein2

Related

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

Clojure: Cannot launch cider repl with boot

I have the following build.boot file,
(set-env!
:resource-paths #{"src"}
:dependencies '[[me.raynes/conch "0.8.0"]
[boot.core :as boot]])
(task-options!
pom {:project 'myapp
:version "0.1.0"}
jar {:manifest {"Foo" "bar"}})
(boot/deftask cider "CIDER profile"
[]
(require 'boot.repl)
(swap! #(resolve 'boot.repl/default-dependencies)
concat '[[org.clojure/tools.nrepl "0.2.12"]
[cider/cider-nrepl "0.15.0"]
[refactor-nrepl "2.3.1"]])
(swap! #(resolve 'boot.repl/default-middleware)
concat '[cider.nrepl/cider-middleware
refactor-nrepl.middleware/wrap-refactor])
identity)
following this documentation: https://github.com/boot-clj/boot/wiki/Cider-REPL
However, upon doing cider-jack-in I get the error "refusing to run as root. set BOOT_AS_ROOT=yes to force.", and yet after doing export BOOT_AS_ROOT=yes, I get the same error. What's wrong?
This wiki page is super outdated, so I don't recommend following it. I'd be best to read CIDER's documentation instead.
Just make sure you're using Boot 2.8.3. You don't really need any special CIDER profile. Just create a Boot project, open a file from it in CIDER and do M-x cider-jack-in-clj.

Spyscope Java RuntimeException No reader function for tag spy/p

I'm trying out spyscope, and following the documented example, I'm getting:
user=> (take 20 (repeat #spy/d (+ 1 2 3)))
RuntimeException No reader function for tag spy/d clojure.lang.LispReader$CtorReader.readTagged (LispReader.java:1245)
RuntimeException Unmatched delimiter: ) clojure.lang.Util.runtimeException (Util.java:221)
RuntimeException Unmatched delimiter: ) clojure.lang.Util.runtimeException (Util.java:221)
My ~/.lein/profile.clj is:
{:user {:dependencies [[spyscope "0.1.6"]]
:injections [(require 'spyscope.core)
(use 'clojure.tools.trace)]
:plugins [[lein-try "0.4.3"]]
}
}
My version of Leiningen is:
$ lein --version
Leiningen 2.8.1 on Java 1.8.0_212 OpenJDK 64-Bit Server VM
I can't seem to find any answers on how to resolve this.
Answering my own question to make it easier for the next person to looking for the solution.
The answer can be traced to [this issue]: (https://github.com/dgrnbrg/spyscope/issues/8)
Lein 2 allows users to fire lein repl in a non project directory and that's the case
You have to use spyscope within a directory with a project.clj file.
You can use spyscope this way at your profiles.clj
; try/catch to workaround an issue where `lein repl` outside a project dir
; will not load reader literal definitions correctly:
(try (require 'spyscope.core)
(catch RuntimeException e))

Clojure jdbc create-table statement does not run unless using Leiningen REPL

I've got a small Clojure program that uses the Clojure JDBC tools to create a table in an HSQL database. However, it only seems to actually create the table if I run it from Leiningen's REPL. It does not create the table if I run the code using lein run or from my IDE (IntelliJ). There are no exceptions reported. In both cases, the output is just "(0)".
Here's the code snippet:
(ns tramway.core
(:require [clojure.java.io :as io]
[clojure.java.jdbc :as sql]))
(def hsql-db {:subprotocol "hsqldb"
:subname "file:/tmp/tramwaydb"
:user "SA"
:password ""})
(defn -main []
(println (sql/with-connection hsql-db (sql/create-table
:footfall
[:id "INTEGER" "GENERATED ALWAYS AS IDENTITY(START WITH 1)"]
[:sample_date "DATE"]
[:exhibition "varchar(255)"]))))
And since I'm using Leiningen, here's my project.clj:
(defproject tramway "1.0.0-SNAPSHOT"
:description "Description here"
:dependencies [[org.clojure/clojure "1.3.0"]
[org.clojure/java.jdbc "0.1.4"]
[org.hsqldb/hsqldb "2.2.8"]]
:main tramway.core)
If I do:
$ lein repl
tramway.core=> (-main)
(0)
nil
and then check /tmp/tramway.log I can see the CREATE TABLE executed successfully.
However, if I do:
$ rm -rf /tmp/tramway.*
$ lein run
(0)
and then check the same file, it is empty. It does create the .log, .properties, and .script files. All but the .log file have content; there's just no record of the CREATE TABLE having been run.
What am I doing wrong? I would expect to have the same result whether I run my (-main) function from the REPL or have Leiningen run it automatically.
I have also tried taking the table creation out of the -main function and running it just as a script through my IDE, and I still get the same bad result.
This is a common HSQLDB configuration question.
HSQLDB's default configuration is not intended for test usage. As a result, it delays writing and synching the .log entries by 500 milliseconds and it does not shutdown the database when the connection is closed. Try either of these settings in your URL:
:subname "file:/tmp/tramwaydb;hsqldb.write_delay=false"
or
:subname "file:/tmp/tramwaydb;shutdown=true"
See the various option here: http://hsqldb.org/doc/2.0/guide/dbproperties-chapt.html