Accessing vars from another clojure namespace? - clojure

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.

Related

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)

Why can't Leiningen always use my :gen-class properly?

Let's say I create a new Leiningen project (lein new app example) and add some code in example/src/example/core.clj that makes use of :gen-class:
(ns example.core
(:gen-class :extends javafx.application.Application))
(defn -start [this stage]
(.show stage))
(defn -main [& args]
(javafx.application.Application/launch example.core args))
If I then create a JAR (lein uberjar) and run it, everything works fine. However, if I instead try to run my app directly (lein run), I get a ClassNotFoundException. In addition, if I open a REPL (lein repl), I first get the same error as before, but after running this code:
(compile 'example.core)
the error no longer appears in lein run or lein repl.
Could someone please explain to me what exactly is going on here, and how I can run my app directly without needing to manually compile my code from a REPL?
Edit: After fooling around a bit more, I found that the solution to this problem is to add
:aot [example.core]
to project.clj. (Thanks #Mars!) I'm still confused, though, because I had previously tried simply removing ^:skip-aot, which (according to the docs) should work:
This will be AOT compiled by default; to disable this, attach
^:skip-aot metadata to the namespace symbol.
But it doesn't. Why?
Another edit (if I should split any of this into a separate question, let me know and I'll do so): I've been playing with hyphens (lein new app my-example), and weird stuff has been happening. This doesn't work:
(ns my-example.core
(:gen-class :extends javafx.application.Application))
;; ...
(defn -main [& args]
(javafx.application.Application/launch my-example.core args))
But this does:
(ns my-example.core
(:gen-class
:name my-example.Core
:extends javafx.application.Application))
;; ...
(defn -main [& args]
(javafx.application.Application/launch my-example.Core args))
So my class name can either start with a lowercase letter (example.core) or contain a hyphen (my-example.Core), but not both? I really don't get it.
And finally, lein uberjar fails on that final example (with the explicit :name), because
Warning: The Main-Class specified does not exist within the jar. It may not be executable as expected. A gen-class directive may be missing in the namespace which contains the main method.
As far as I can tell, the only way to fix that is to split the Application subclass into a separate namespace. Is there another way?
Agreed with #Mars, the problem is that lein run does not AOT the example.core namespace. The default Leiningen template made the example.core non AOT:
(defproject example "0.1.0-SNAPSHOT"
...
:main ^:skip-aot example.core
...)
My best guess is that you could define your app using defrecord and use that as a class instead of the namespace.

Evaluate Leiningen Code In Project

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

clojure/ring/jetty: I am using > lein ring server. How do I configure the jetty instance that gets instantiated?

When I was calling the jetty handler directly, I was able to pass in a configurator like so:
(def header-buffer-size 8388608)
(defn start [port]
(ring/run-jetty
(var app)
{:port port
:join? false
:host "127.0.0.1"
:configurator
(fn [jetty]
(doseq [connector (.getConnectors jetty)]
(.setHeaderBufferSize connector header-buffer-size)))}))
I had to do this because I kept getting a FULL HEAD error when posting. Now I refactored things to use > lein ring server directly, which gets called from the command line.
> lein ring server
This uses some configuration specified in my project.clj:
:ring {:handler caribou.api.core/app
:servlet-name "caribou-api"
:init caribou.api.core/init
:port 33443}
This works great, but now I am getting the FULL HEAD issue again. So I tried to add a configurator in there:
:ring {:handler caribou.api.core/app
:servlet-name "caribou-api"
:init caribou.api.core/init
:configurator
(fn [jetty]
(doseq [connector (.getConnectors jetty)]
(.setHeaderBufferSize connector 8388608)))
:port 33443})
And this fails with this stacktrace:
Exception in thread "main" java.lang.ClassCastException:
clojure.lang.PersistentList cannot be cast to clojure.lang.IFn
at ring.adapter.jetty$run_jetty.invoke(jetty.clj:66)
at ring.server.standalone$serve$fn__833.invoke(standalone.clj:78)
at ring.server.standalone$try_port.invoke(standalone.clj:12)
at ring.server.standalone$serve.doInvoke(standalone.clj:75)
at clojure.lang.RestFn.invoke(RestFn.java:423)
at ring.server.leiningen$serve.invoke(leiningen.clj:20)
I figured this had to do with putting the function directly in the map like that, so I defined it outside the project (in caribou.api.core) and tried referring to it like I do the rest of the functions defined elsewhere:
;; in caribou/api/core.clj
(def header-buffer-size 8388608)
(defn full-head-avoidance
[jetty]
(doseq [connector (.getConnectors jetty)]
(.setHeaderBufferSize connector header-buffer-size)))
;; in project.clj
:ring {:handler caribou.api.core/app
:servlet-name "caribou-api"
:init caribou.api.core/init
:configurator caribou.api.core/full-head-avoidance
:port 33443})
This spins up the app, but I still get the 413: FULL HEAD error when posting. Any ideas? Thanks!
Stuff written inside a defproject form is not evaluated by default, but you can use ~ (unquote) in defproject when you need it to:
(defproject foo "1.2.3"
...
:some-fn-key ~(fn [& args] ...))
In this particular situation, you'd unquote the fn form defining your configurator function (which without unquoting is being used as a list containing a bunch of symbols, rather than compiled into a function).
Alternatively, you can define the configurator outside the defproject form and refer to its name inside defproject as in your second approach, but then you need to unquote the name inside defproject -- otherwise it'll be treated as a symbol. NB. in the latter case no exception is produced, because symbols are in fact callable and accept arbitrary arguments (but only ever return nil when called with something other than a map or a set; with maps or sets, they look themselves up in their arguments).
The answer is not to use ring server and just start jetty directly with ring/ring-jetty-adapter. That way I can still pass in the configurator, which BTW has changed to this:
(defn full-head-avoidance
[jetty]
(doseq [connector (.getConnectors jetty)]
(.setRequestHeaderSize connector header-buffer-size)))

Setting Clojure "constants" at runtime

I have a Clojure program that I build as a JAR file using Maven. Embedded in the JAR Manifest is a build-version number, including the build timestamp.
I can easily read this at runtime from the JAR Manifest using the following code:
(defn set-version
"Set the version variable to the build number."
[]
(def version
(-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
(.getCodeSource)
(.getLocation))
"!/META-INF/MANIFEST.MF")
(URL.)
(.openStream)
(Manifest.)
(.. getMainAttributes)
(.getValue "Build-number"))))
but I've been told that it is bad karma to use def inside defn.
What is the Clojure-idiomatic way to set a constant at runtime? I obviously do not have the build-version information to embed in my code as a def, but I would like it set once (and for all) from the main function when the program starts. It should then be available as a def to the rest of the running code.
UPDATE: BTW, Clojure has to be one of the coolest languages I have come across in quite a while. Kudos to Rich Hickey!
I still think the cleanest way is to use alter-var-root in the main method of your application.
(declare version)
(defn -main
[& args]
(alter-var-root #'version (constantly (-> ...)))
(do-stuff))
It declares the Var at compile time, sets its root value at runtime once, doesn't require deref and is not bound to the main thread. You didn't respond to this suggestion in your previous question. Did you try this approach?
You could use dynamic binding.
(declare *version*)
(defn start-my-program []
(binding [*version* (read-version-from-file)]
(main))
Now main and every function it calls will see the value of *version*.
While kotarak's solution works very well, here is an alternative approach: turn your code into a memoized function that returns the version. Like so:
(def get-version
(memoize
(fn []
(-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
(.getCodeSource)
(.getLocation))
"!/META-INF/MANIFEST.MF")
(URL.)
(.openStream)
(Manifest.)
(.. getMainAttributes)
(.getValue "Build-number")))))
I hope i dont miss something this time.
If version is a constant, it's going to be defined one time and is not going to be changed you can simple remove the defn and keep the (def version ... ) alone. I suppose you dont want this for some reason.
If you want to change global variables in a fn i think the more idiomatic way is to use some of concurrency constructions to store the data and access and change it in a secure way
For example:
(def *version* (atom ""))
(defn set-version! [] (swap! *version* ...))