2

I am working on an webapp that relies on a certain data file to be slurped at runtime. Without the datafile present I don't seem to be able to compile. Why is this?

This is in my core.clj

(def my-data (slurp "my-file.txt"))

Then when I try to compile:

$ lein ring war

I get this exception

Exception in thread "main" java.io.FileNotFoundException: my-file.txt (No such file or directory), compiling:(core.clj:24:28)

How can I compile my war? I don't need the file to be slurped or even check for existence at compile time. Thanks in advance!

[UPDATE]

This is not specific to war file packaging or ring, for example:

(ns slurp-test.core
    (:gen-class))

(def x (slurp "/tmp/foo.txt"))

(defn -main [& args]
    (println x))

Then:

 $ lein uberjar
 Compiling slurp-test.core
 (ns slurp-test.core
 Exception in thread "main" java.io.FileNotFoundException: /tmp/foo.txt (No such file or directory), compiling:(core.clj:4:8)

How can I fix this?

David Williams
  • 8,388
  • 23
  • 83
  • 171

1 Answers1

5

Compiling a Clojure source file involves evaluating all top-level forms. This is in fact strictly necessary to support the expected semantics -- most notably, macros couldn't work properly otherwise1.

If you AOT compile your code, top-level forms will be evaluated at compile time, and then again at run time as your compiled code is loaded.

For this reason, it is generally not a good idea to cause side effects in code living at top level. If an app requires initialization, it should be performed by a function (typically -main).


1 A macro is a function living in a Var marked as a macro (with :macro true in the Var's metadata; there's a setMacro method on clojure.lang.Var which adds this entry). Macros must clearly be available to the compiler, so they must be loaded at compile time. Furthermore, in computing an expansion, a macro function may want to call non-macro functions or otherwise make use of the values of arbitrary Vars resulting from evaluating any top-level code occurring before the point where the macro is invoked. Removing these capabilities would cripple the macro facility rather badly.

Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
  • So in a webservice with a framework like compojure, would that happen in the app section? Which function hooks into the main? `(def app (compojure.handler/site main-routes))` – David Williams Jun 05 '13 at 01:45
  • I'd like to have the app start up with a set of big initial data, and then have that data rebuilt periodically and reloaded. – David Williams Jun 05 '13 at 01:48
  • It would happen in whichever function your deployment environment will call to start your app. With `lein run`, it'll be `-main` in the main namespace (see `:main` in `project.clj`; you could change this, but there's no reason to). A Java container will want to call a method in a class you supply, so you'll need to implement such a method (that's what `gen-class` is for -- see `(doc gen-class)` and the excellent [gen-class – how it works and how to use it](http://kotka.de/blog/2010/02/gen-class_how_it_works_and_how_to_use_it.html) blog post by Meikel Brandmeyer for more details). – Michał Marczyk Jun 05 '13 at 01:52
  • Replying to the second comment: no problem -- write a function to prepare and load your data, then have your init function call it *and* make arrangements so that it's called at intervals as the app is running. In this case I'd probably put that data in an Atom rather than a Var. – Michał Marczyk Jun 05 '13 at 01:55
  • Hm, thats interesting, I'm trying to think of what would qualify as an init script. Would that be in say, core.clj, ie where I define routes? – David Williams Jun 05 '13 at 02:29
  • ie like here: `(compojure.core/defroutes main-routes (compojure.core/GET "/hello" [] "Hello") (compojure.route/not-found "Not found")) (def app (compojure.handler/site main-routes))` – David Williams Jun 05 '13 at 02:32
  • With the hook in project.clj `:ring {:handler myapp.core/app}` – David Williams Jun 05 '13 at 02:33
  • 1
    `lein-ring` takes an `:init` parameter which is just the ticket. See the "General options" section of the [README](https://github.com/weavejester/lein-ring/blob/0.8.5/README.md) (link to the 0.8.5 release). – Michał Marczyk Jun 05 '13 at 02:42