Evaluate Leiningen Code In Project - clojure

I have a lein plugin and I need to evaluate its code within the project context. I can do trivial stuff like this
(leiningen.core.eval/eval-in-project project '(+ 1 2))
but once I require code from the plugin itself
(leiningen.core.eval/eval-in-project
project
'(my-ns/my-fn)
'(require '[my-plugin.my-ns :as my-ns])
its not on the load path of the target project! How can I edit the project map to include plugin code within the project?

The first argument to eval-in-project must be the project map. The second argument is the form to evaluate, and the third is an init form "that is evaluated up-front before the main form". In "Evaluation in project context" there's a link to a post explaining how to avoid "the gilardi scenario", which is defined as:
The Gilardi Scenario refers to a case where you want to evaluate some code that both loads in a new var and refers to that var in the same piece of code
The issue in your code is that you are defining an alias in the init form and expect it to be defined in your main code too.
So, your code might be something like:
(defn my-plugin [project & opts]
(leiningen.core.eval/eval-in-project
project
'(my-plugin.my-ns/my-fn)
'(require 'my-plugin.my-ns)))

We need a way to get source code from our plugin into the target project classpath. Once way is to copy the source from the leiningen classpath into a source folder in the target project
(import java.io.File)
(import clojure.lang.RT)
(require '[clojure.java.io :as io])
(defn copy-src [& name-spaces]
(doseq [name-space name-spaces]
(let [
resource (-> name-space
str
(.replace "." "/")
(.replace "-" "_")
(str ".clj"))
on-classpath (RT/getResource (RT/baseLoader) resource)
parent-dir (File.
(str
".cljsserve/"
(.substring resource 0 (.lastIndexOf resource "/"))))
src-file (File. (str ".cljsserve/" resource))
]
(.mkdirs parent-dir)
(with-open [in (io/input-stream on-classpath)]
(io/copy in src-file)))))
We may then update the project map to include our "temp" source folder and eval in project
(let [new-project (update-in project [:source-paths] concat [".cljsserve"])]
(leiningen.core.eval/eval-in-project
new-project
'(cljs-server.web/-main)
'(require 'cljs-server.web)))
An alternative is just to put the plugin logic within a separate dependency. In my case I created a dependency [cljs-server "1.0.0-SNAPSHOT"] and then made my plugin into a wrapper around it
(leiningen.core.eval/eval-in-project
(update-in project [:dependencies] concat [[cljs-server "1.0.0-SNAPSHOT"]])
`(cljs-server.web/-main ~port ~ssl? ~ssl-port ~src)
'(require 'cljs-server.web)) ;cljs-server.web is defined within the dependency

Related

Issue with dependencies & function being recognized in the repl

I am having a similar issue to this person: Problems while creating a deps.edn file
However, I'm on MacOS and trying to follow the book and use deps.edn instead of leiningen, so I wasn't able to solve my issue from reading the answers in that post.
I'm using my terminal window and just text files, or Emacs.
Within the terminal, I created a folder called tennisProject. Then I created 2 files, deps.edn and tennisProject.clj inside that folder. Then I put the csv file of tennis data in that folder.
Then I go back to the terminal and restart it. I make tennisProject the current directory. I type in "clj" to start a repl. Then I do (in-ns 'packt-clj.tennisProject) to get into the right namespace. Then, I type (first-match "match_scores_1991-2016_unindexed_csv.csv"), and I get an error:
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: first-match in this context
The contents are as follows (I copied and pasted from the book).
deps.edn:
{:deps
{org.clojure/data.csv {:mvn/version "1.0.0"}
semantic-csv/semantic-csv {:mvn/version "0.2.1-alpha1"}}}
tennisProject.clj:
(ns packt-clj.tennisProject
(:require
[clojure.data.csv :as csv]
[clojure.java.io :as io]
[semantic-csv.core :as sc]))
(defn first-match [csv]
(with-open [r (io/reader csv)]
(->> (csv/read-csv r)
sc/mappify
first)))
I have a few things different than the book: I changed the name from tennis to tennisProject because I kept making new folders after getting errors. I also changed the data.csv version from "0.1.4" to "1.0.0" because that's what was in the answer I linked, but that didn't resolve my issue. Then I also have semantic-csv/semantic-csv but in the book it's just semantic-csv. I changed that because the repl advised me to make the change.
If I just require the dependencies one by one in the repl, and define the function in the repl, everything works fine, but I really want to understand how all these files work together and I appreciate your help!
By default, the Clojure CLI / deps.edn assumes your source code is going to be in a tree under a folder called src.
The namespace in a Clojure file must "match" its filepath relative to src so for packt-clj.tennisProject, the file should be src/packt_clj/tennisProject.clj -- note the - in a namespace corresponds to an _ in the filepath.
If you reorganize your project like that, and restart your REPL, you should be able to require your code and work with it.
As a stylistic note, we don't use camelCase much in Clojure: it would be more idiomatic to have tennis-project as the namespace (which means tennis_project.clj as the filename).
(edited to add this example session)
(! 556)-> pwd
/Users/sean/clojure/tennisProject
(! 557)-> ls
deps.edn example.csv src
(! 558)-> tree
.
|____deps.edn
|____example.csv
|____src
| |____packt_clj
| | |____tennisProject.clj
(! 559)-> clj
Clojure 1.10.3
user=> (require 'packt-clj.tennisProject)
nil
user=> (in-ns 'packt-clj.tennisProject)
#object[clojure.lang.Namespace 0x128c502c "packt-clj.tennisProject"]
packt-clj.tennisProject=> (first-match "example.csv")
{:some "42", :headers "A value", :in "1", :this "2", :file "3.333"}
packt-clj.tennisProject=> ^D
(! 560)-> cat src/packt_clj/tennisProject.clj
(ns packt-clj.tennisProject
(:require
[clojure.data.csv :as csv]
[clojure.java.io :as io]
[semantic-csv.core :as sc]))
(defn first-match [csv]
(with-open [r (io/reader csv)]
(->> (csv/read-csv r)
sc/mappify
first)))
(! 561)-> cat example.csv
some,headers,in,this,file
42,"A value",1,2,3.333

How to set a dynamic var before aot compile

I want to have a *flag* variable in a given namespace that is set to true only when :aot compiling.
Is there a way to do that?
Your issue is kind of complicated because the definition of Clojure's own dynamic nature, so there's no rough equivalent of C's #ifdef or some other mechanism that happens at compile time, but here's a workaround:
I created a Leiningen project with lein new app flagdemo. This trick detects when AOT is performed, as #Biped Phill mentioned above, using the dynamic var *compile-files*, and saves a resource in the classpath of the compiled code:
The code looks like this:
(ns flagdemo.core
(:gen-class))
(def flag-path "target/uberjar/classes/flag.edn")
(defn write-flag [val]
(try (spit flag-path (str val)) (catch Exception _)))
(defn read-flag []
(some-> (clojure.java.io/resource "flag.edn") slurp clojure.edn/read-string))
(write-flag false)
(when clojure.core/*compile-files*
(write-flag true))
(defn -main
[& args]
(println "Flag is" (read-flag)))
So, when the file loads using, say, lein run or when you load it in the REPL, it will try to write an EDN file with the value false.
When you compile the package using lein uberjar, it loads the namespace and finds that *compile-files* is defined, thus it saves an EDN file that is packaged with the JAR as a resource.
The function read-flag just tries to load the EDN file from the classpath.
It works like this:
$ lein clean
$ lein run
Flag is nil
$ lein uberjar
Compiling flagdemo.core
Created /tmp/flagdemo/target/uberjar/flagdemo-0.1.0-SNAPSHOT.jar
Created /tmp/flagdemo/target/uberjar/flagdemo-0.1.0-SNAPSHOT-standalone.jar
$ java -jar target/uberjar/flagdemo-0.1.0-SNAPSHOT-standalone.jar
Flag is true
Credit to #Biped Phill and #Denis Fuenzalida for explaining it to me.
I think this works as well:
(def ^:dynamic *static* false)
(when (and *compile-files* boot/*static-flag*)
(alter-var-root #'*static* true))
then in profiles.clj:
:injections [(require 'boot)
(intern 'boot '*static-flag* true)]
First you need to indicate that a variable will change over time:
(def ^:dynamic *the-answer* nil)
*the-answer*
;=> nil
The ^:dynamic bit will allow that variable to be rebound later:
(binding [*the-answer* 42] *the-answer*)
;=> 42
ADDENDUM 1
Here's what would happen if you didn't use ^:dynamic:
(def *the-answer* nil)
(binding [*the-answer* 42] *the-answer*)
;=> [...] Can't dynamically bind non-dynamic var [...]
ADDENDUM 2
These kind of variables are commonly referred to as "earmuffs". The naming convention is unenforced but strongly encouraged.
Here's what happen in the REPL when you use this naming without declaring a dynamic variable:
(def *the-answer* nil)
; Warning: *the-answer* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *the-answer* or change the name. (/tmp/form-init7760459636905875407.clj:1)

Clojure: boot repl in a particular namespace

I have boot-clj installed and want to be able to edit a .clj file in an external editor and separately have a command line REPL running from which I can call the functions that I change in the .clj file. No special reloading commands should be required.
Another thing is I don't want to have to manually type commands to include namespaces - I would like to just run a script that brings me into the namespace, so I can call existing functions right away.
Name of the file:
C:\dev\my-project\src\my_project\utils.clj
Something of what is inside the file:
(ns my-project.utils
(:require
[clojure.string :as s]))
(defn my-range [start end]
(take (- end start) (iterate inc start)))
I would like to go straight into a REPL and go (my-range 0 3) and see if it produces the result I want.
What's the setup for this? What would the script file I need to run look like?
My current understanding is that the answer will look something like this:
(deftask dev-repl
(set-env! …)
(repl))
at the command line
You can achieve this to some degree at the command line, without creating a build.boot file:
In C:\dev\my_project:
boot -r src repl -n my-project.utils
boot -r src: starts boot with src on the "resource paths", which is the set of directories that will be accessible within the JVM.
repl -n my-project.utils starts a REPL, requires your namespace, and enters it.
While the REPL is running, and after you have edited C:\dev\my_project\src\my_project\utils.clj, you can reload it at the REPL like this:
my-project.utils=> (require 'my-project.utils :reload)
nil
minimal build.boot
Alternatively, you could create the file C:\dev\my_project\build.boot with these contents:
(set-env! :resource-paths #{"src"})
(deftask dev
"Run a development REPL"
[]
(repl :init-ns 'my-project.utils))
Then, in C:\dev\my_project:
boot dev
Which will also start a REPL in your namespace, but requires less command-line configuration as we've performed the configuration in build.boot, which boot will automatically evaluate.
Note: from a Clojure REPL, regardless of build tool, you can require any namespace (as long as it's on the JVM's class path) with the require function and enter it with the in-ns function.
build.boot with automatic reloading
Finally, it's possible to combine features of Boot to achieve a development workflow oriented around automatically reloading code.
In C:\dev\my_project\build.boot:
(set-env! :resource-paths #{"src"})
(require '[boot.core :as core]
'[boot.pod :as pod])
(deftask load-ns
"Loads the my-project.utils namespace in a fresh pod."
[]
(let [pods (pod/pod-pool (core/get-env))]
(core/with-pre-wrap [fileset]
(pod/with-eval-in (pods :refresh)
;; We require indirectly here so that errors from my-project.utils have
;; proper line and column information.
(require 'my-project.load-impl))
fileset)))
(deftask dev
"Watches source code and loads my-project/utils every time code changes."
[]
(comp (watch)
(load-ns)))
In C:\dev\my_project\src\my_project\load_impl.clj:
(ns my-project.load-impl)
(require 'my-project.utils)
In C:\dev\my_project\src\my_project\utils.clj:
(ns my-project.utils
(:require
[clojure.string :as s]))
(defn my-range [start end]
(take (- end start) (iterate inc start)))
(println "In the code!")
(println "(my-range 0 10) = " (my-range 10 20))
Back at the command prompt, type boot dev. You should see some println output, and every time you edit and save the file you should see it again, reflecting any changes you made.

Clojure JavaFX -- Toolkit Not Initialized error

JavaFX 8, Java 1.8.0_31, Windows 7 x64
I have a minimal JavaFX program in Clojure. The (ns...) clause is able to import the required Java packages fine except the classes in javafx.scene.control, such as Button and TextField, etc.
I have to put the import for these after initializing the toolkit. Why can't I import these classes before the toolkit is initialized? I'm not actually creating any objects yet... so I'm guessing JFX is somehow doing something in the background while these classes are imported, requiring the initialization first. Below is my complete lein project (minimized from the actual application where I saw this problem, and without all the nice macros that clean up the JFX syntax):
File project.clj:
(defproject jfx-so "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.6.0"]]
:main jfx-so.core)
File src/jfx_so/core.clj:
(ns jfx-so.core
(:import [javafx.scene Scene]
[javafx.scene.layout BorderPane]
[javafx.stage Stage]))
(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))
;; For some reason the following must be imported after initting the toolkit
(import [javafx.scene.control Button])
(defn -main [& args]
(javafx.application.Platform/runLater
#(doto (Stage.)
(.setScene (Scene. (BorderPane. (Button. "Hello"))))
(.show))))
Thanks! :)
I haven't had a problem with this. Perhaps it has to do with your defonce?
I do my imports first. But I do make sure to init the FX-envoronment before instanciating any FX-classes. So after your -main-method I would put:
(defn -main [& args]
;;body here
)
;; initialze the environement
(javafx.embed.swing.JFXPanel.)
;; ensure I can keep reloading and running without restarting JVM every time
(javafx.application.Platform/setImplicitExit false)
;; then
(-main)
Hope this helps.

Accessing vars from another clojure namespace?

In my main namespace, I have a top level var named "settings" which is initialized as an empty {}.
My -main fn sets the contents of settings using def and conj based on some command line args (different database hosts for production/development, etc).
I'm trying to access the contents of this map from another namespace to pull out some of the settings. When I try to compile with lein into an uberjar, I get a traceback saying "No such var: lb/settings".
What am I missing? Is there a more idiomatic way to handle app wide settings such as these? Is it safe to use "def" inside of -main like I am, or should I be use an atom or ref to make this threadsafe?
Thanks!
(ns com.domain.main
(:use com.domain.some-other-namespace.core)
(:gen-class))
(def settings {})
(defn -main [& args]
(with-command-line-args... ;set devel? based on args
(if (true? devel?)
(def settings (conj settings {:mongodb {:host "127.0.0.1"}
:memcached {:host "127.0.0.1"}}))
(def settings (conj settings {:mongodb {:host "PRODUCTION_IP"}
:memcached {:host "PRODUCTION_IP"}})))
;file2.clj
(ns com.domain.some-other-namespace.core
(:require [main :as lb]
...)
;configure MongoDB
(congo/mongo!
:db "dbname" :host (:host (mongodb lb/settings))))
...
Ok, I found the problem. It looks like it was a circular reference. I was ":require"ing com.domain.some-other-namespace.core from com.domain.main. Since the "require" is called before (def settings {}) in com.domain.main, the var does not yet exist when the other namespace is compiled...
I moved the settings map into a separate namespace (named settings naturally) and changed it from a Var to an Atom just to be safe. Seems to work great now!
A couple things to check:
typically clojure namespaces have at least one . in them project.main I think leiningen may depend on this.
check the classes folder to make sure the main and some-other-namespace class files are being compiled.