why does compiling `*.clj` sources execute code as well? - clojure

I have the following non-sensical *.clj file:
(ns bar.zar.Foo
(:gen-class :main true))
(println "foo")
(defn -main [& args]
nil)
When compiling it to *.class (clojure.lang.Compile) with the following Ant target:
<target name="compile-clojure" description="Compile Clojure sources." depends="resolve">
<mkdir dir="${cljbuild.dir}"/>
<java classname="clojure.lang.Compile"
failonerror="true"
fork="true">
<classpath refid="compile.classpath"/>
<sysproperty key="clojure.compile.path" value="${cljbuild.dir}"/>
<arg value="${project.MainClass.name}"/>
</java>
</target>
I see on the output:
[java] Compiling bar.zar.Foo to /home/[ommitted]/build
[java] foo
That is, the (println "foo") expression was evaluated during compilation. Why is that? Does it have to do with the "Lisp blurs the compile-time / runtime distinction" thing?

The unit of compilation in Clojure is the top level s-expression which is read, expanded, evaluated, and often stored in the namespace as the file is loaded. There is no separate compile phase for the whole file. Doing it this way allows you to write function that define other functions, types, DSLs etc. and is the building block of the macro system.
put anything you don't want run into an init function, then include a call that function from main (or wherever your code starts)

Related

What is a common convention for storing a project version number in a Clojure project?

Our team is working with Clojure projects that use deps.edn
We are used to NPM projects where we store the version number in the package.json file. It looks like Leiningen users may have stored the version number in a project.clj file before. Is there a community-wide convention for versioning Clojure projects? I have seen that people and official docs mention using Git tags but wondering if there is some popular way that developers using deps.edn store the version number in a file.
Thank you!
I have only used Leiningen to publish JAR files so far. In this case, the version string is at the top of the file like
(defproject tupelo/demo "0.1.0-SNAPSHOT"
and it is most easily maintained by just editing project.clj.
Just last month, the final version of tools.build was released, which is the "official" way of publishing JAR files using the Deps/CLI system. I haven't had a chance to play with it yet but the example in the guide shows how to include the version string when building a JAR file. The example even uses an automation technique based on counting commits in git.
(ns build
(:require [clojure.tools.build.api :as b]))
(def lib 'my/lib1)
(def version (format "1.2.%s" (b/git-count-revs nil)))
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def jar-file (format "target/%s-%s.jar" (name lib) version))
(defn clean [_]
(b/delete {:path "target"}))
(defn jar [_]
(b/write-pom {:class-dir class-dir
:lib lib
:version version
:basis basis
:src-dirs ["src"]})
(b/copy-dir {:src-dirs ["src" "resources"]
:target-dir class-dir})
(b/jar {:class-dir class-dir
:jar-file jar-file}))
(defproject my.domain/service
(-> "resources/service.VERSION" slurp .trim)
...
as for leiningen projects
I don't know that there is an established convention, but this is what I use.
For an app (in contrast to a lib) what I've done is store the version in resources/version.edn (which is NOT kept under version control) and read that as part of the config of the app during startup. I use the major.minor.gitcount versioning, but you can change that easily to suit your needs. I use juxt/aero for configuration management, but any similar library or bespoke config management should work. Here is an excerpt from my build.clj.
(ns build
"A build script for myapp."
(:require [clojure.tools.build.api :as b]
[org.corfield.build :as bb]))
(def major-version 0)
(def minor-version 2)
(def app 'com.myorg/myapp)
(def main 'com.myorg.myapp)
(def version (format "%d.%d.%s" major-version minor-version (b/git-count-revs nil)))
(def version-file "resources/version.edn")
(defn uber [opts]
(b/write-file {:path version-file :content {:version-string version}})
(-> opts
(assoc :lib app :version version :main main)
(bb/uber)))
Here is an excerpt from my config.edn (for juxt/aero)
{:app-config #merge [{:name "My App Name"
:credentials-file-path #or [#env CRED_PATH #join [#env HOME "/credentials.json"]]
:other-config "something"
}
#include "version.edn"]
}
And, the -main function looks like
(defn -main
[& args]
(try
(log/info "")
(log/info ">>>>>>>>>>>>>>>>> Starting Log >>>>>>>>>>>>>>>>>")
(let [{:keys [options arguments exit-message ok?]} (validate-args args cli-options)]
(log/debug "Options:" options)
(log/debug "Arguments:" arguments)
(cond exit-message
(exit (if ok? 0 1) exit-message)
;;
:else
(let [system-config (config/system-config :prod options)]
(log/info "Initializing My App.")
(cond (not (config/config-valid? system-config)) (exit 1 (config/explain-config system-config))
(:version options) (exit 0 (str "Version: " (config/app-version-string system-config)))
:else
(start-the-system)
))
(catch Exception e (log/error e))))
I would say it depends on the intended use of the version number you would like to store, as well as the toolchain you are using. Since you're apparently not using Lein, this answer won't refer to any idioms related to Lein.
A convention in the deps.edn world, would be to store the project version data in the pom.xml file, which can be generated from your deps.edn file by invoking clojure -Spom in the same directory as your deps.edn file, and then editing the version number in that file manually or via a script. A fair amount of tooling in the deps ecosystem relies on the existence of a pom.xml file and directly or transitively reads version info from that file.
Unfortunately, XML isn't very ergonomic to interact with in Clojure, so if you need to parse the version in the running code of your project, this can be a bit of a nuisance. To achieve this, I have seen the independent maintenance of multiple versions before (not ideal) in the POM and in Clojure elsewhere, I have seen POM's read in as resources and the version parsed out, and I have seen libraries like versioneer used as well.
I personally would recommend using versioneer in your case, which will traverse system properties and then the pom.properties file, looking for a version. The source is straightforward and easy to understand. Both depstar and tools.build will generate a pom.properties file which versioneer can read.

Distributing a simple library via clojars

I have implemented a hyphenation algorithm (at namespace hyphenator-clj.core), defined it as org.clojars.nikonyrh.hyphenator-clj 0.1.0 at defproject and pushed it to Clojars. Uberjar seems to have files like core__init.class, core.clj and core.class.
However when I try to use it as a dependency on an other project I get this error:
$ lein uberjar
Retrieving org/clojars/nikonyrh/hyphenator-clj/org.clojars.nikonyrh.hyphenator-clj/0.1.0/org.clojars.nikonyrh.hyphenator-clj-0.1.0.pom from clojars
Retrieving org/clojars/nikonyrh/hyphenator-clj/org.clojars.nikonyrh.hyphenator-clj/0.1.0/org.clojars.nikonyrh.hyphenator-clj-0.1.0.jar from clojars
Compiling example.core
java.io.FileNotFoundException: Could not locate org/clojars/nikonyrh/hyphenator_clj__init.class or org/clojars/nikonyrh/hyphenator_clj.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name., compiling:(core.clj:1:1)
Exception in thread "main" java.io.FileNotFoundException: Could not locate org/clojars/nikonyrh/hyphenator_clj__init.class or org/clojars/nikonyrh/hyphenator_clj.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name., compiling:(core.clj:1:1)
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3657)
at clojure.lang.Compiler.compile1(Compiler.java:7474)
at clojure.lang.Compiler.compile1(Compiler.java:7464)
at clojure.lang.Compiler.compile(Compiler.java:7541)
at clojure.lang.RT.compile(RT.java:406)
at clojure.lang.RT.load(RT.java:451)
at clojure.lang.RT.load(RT.java:419)
at clojure.core$load$fn__5677.invoke(core.clj:5893)
...
Must I change my project's folder structure so that it matches the expected org/clojars/nikonyrh/hyphenator_clj__init.class, or can I somehow override the current behavior? If there is a good tutorial about this out there I would be happy to read it.
Basically I would like to get this example project to work. project.clj:
(defproject example "0.0.1-SNAPSHOT"
:description ""
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojars.nikonyrh.hyphenator-clj "0.1.0"]]
:javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"]
:aot [example.core]
:main example.core)
src/example/core.clj:
(ns example.core
(:require [org.clojars.nikonyrh.hyphenator-clj :as h])
(:gen-class))
(defn -main [& argv] (doseq [arg argv] (println (h/hyphenate arg :hyphen \-))))
I'm suspecting I also have the english.txt in a wrong directory, as it isn't contained in the uberjar but resource files are an other topic.
You would probably be better off not using a hyphen in the namespace hyphenator-clj. Why not just use hyphenator? But if you do I suspect that the name of the directory should have an underscore in it rather than a hyphen, so be: hyphenator_clj.
If fixing that issue doesn't help then another thing to look out for, which I can't see from your question, is where exactly is core.clj in the directory structure, and does the project.clj reflect that? For instance is the path for the namespace hyphenator-clj.core in a src directory off the root of your project? The root of your project being defined as where the project.clj is located.
Something else that would be good to see in the question is whether you can get the program to work just locally, without having packed it up into an uberjar and shipped it to clojars. My guess would be that it does work locally, but it would help for that to be stated.
Okay taking a look at your links now. You might like to read a working project deployed to clojars, that has hypens in its name, for instance here. The first difference you might notice is the project name you use is rather long: org.clojars.nikonyrh.hyphenator-clj. It should just be hyphenator-clj. Also I would recommend having an identifier ending in "-SNAPSHOT" as that project does.
But taking a look at the bigger picture, a great idea you suggested in the comments is to test without Clojars being in the mix at all. To do this use lein install on the library you want to use from another lein project.
Ahaa at least I understand the process a bit better now, basically my require has to match the structure of the used JAR file, which may be very different from the project's name. For example cc.qbits/spandex is actually required as qbits.spandex.
The english.txt dependency was fixed by moving it to resources folder, deploying the new version to Clojars and importing the dependency as it exists in the JAR:
(ns example.core
(:require [hyphenator-clj.core :as h])
(:gen-class))
(defn -main [& argv] (doseq [arg argv] (println (h/hyphenate arg :hyphen \-))))

Can I have a main method from another Leiningen project?

I'm writing a framework. Hopefully I'd like implementing projects to use the main method for the framework.
In my demo project, in definition.clj I have:
(ns definition
(:gen-class))
(def widget
{:this :that})
(defn -main [& args] (prn "MAIN"))
In my project.clj file I have :main ^:skip-aot definition. When I lein run I get the expected
lein run
"MAIN"
When I lein repl I can see the widget and -main functions:
definition=> definition/-main
#object[definition$_main 0x3ce2a73f "definition$_main#3ce2a73f"]
definition=> definition/widget
{:this :that}
When I call (all-ns) I can see the namespace as #object[clojure.lang.Namespace 0x64965348 "definition"].
Here's the problem: if I change the main method to that defined in another project (which is a dependency):
:main ^:skip-aot the-framework.core
The main method works. But the namespaces are no longer visible:
the-framework.core=> definition/widget
CompilerException java.lang.RuntimeException: No such namespace: definition, compiling:(/private/var/folders/gc/_1drfv2n2m588qnq5xz35g080000gn/T/form-init8820469930602256143.clj:1:1325)
The only thing that changed was the :main value in the Leiningen project.clj definition.
Is there a way to change the main method to an namespace not defined in the project without breaking the namespaces in the project?

access library functions in leiningen REPL

For the development of a library I started from a lein project, invoked like so:
lein new mylib
if I call lein install now, I can access my library in other projects. But trying to immidiately test the functions I wrote failed:
lein repl
...
(dir mylib.core)
Exception No namespace: mylib.core found clojure.core/the-ns (core.clj:4008)
Do I have to add something to the project.clj file maybe?
In order to use a library you must cause the code to be loaded - that it be on the classpath is not sufficient.
You can do this easily in an ns declaration in a file of course, but in the repl it can be easier to use (require '[my-lib.whatever :as w]) after which one can call (w/foo) (w/bar) etc. as expected. You can also use (in-ns 'my-lib.whatever) in order to switch to the namespace, but this will not give you a good result unless you have previously used require or use or load-file etc. to get the definitions first.
Let's say you created a new library named clj-foo.
% lein new clj-foo
Start your repl.
% cd clj-foo
% lein repl
In the repl, load the main entry point to your library and switch to its namespace.
(load-file "src/clj_foo/core.clj")
(ns clj-foo.core)
Now you're in the clj-foo.core namespace, make sure to add back in the repl ns to get things like doc available.
(use 'clojure.repl)
That's it. You're all set to start calling functions in your library. Note that other library files will be available from the clj-foo.core namespace if they were loaded by namespace declaration at the top of clj_foo/core.clj. If not, then you'll need to invoke load-file with their path as well.
If you make changes in core.clj. You can invoke load-file again to pick up the new code. As you progress, you can use cider to facilitate loading of individual functions and files. But that's for another question. :)
You need to add a dependency to use your library from another project. To do this add a vector (a tuple-2) to the vector that is the value of the :dependencies key in the project.clj file. Here's an example:
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]
[org.clojure/core.async "0.2.371"]
[default-db-format "0.1.0-SNAPSHOT"]
[com.andrewmcveigh/cljs-time "0.3.14"]]
My own local library is called default-db-format. Its really no different to adding a dependency for com.andrewmcveigh/cljs-time.
As you say you can already do this, but are having trouble getting a REPL connection to the project of the library itself. When you go (in-ns 'some-path), you need the single quote in front of some-path. Note that some-path is a different thing to the name of your library.
Rather than use lein repl you can use the figwheel repl - if your project is setup with figwheel. My library has only one entry point and that is lein figwheel devcards. After that I had no problem going to a namespace and trying out a function:
cljs.user=> (in-ns 'default-db-format.core)
nil
default-db-format.core=> (check 1 2)
As noisesmith mentioned having a REPL in your IDE is the best setup. No fiddly typing just bring up pre-configured REPLs (per namespace) with the click of a button (or keystroke). Figwheel/Cursive setup instructions here.
I was also facing the same issue with the following configuration:
Leiningen 2.9.0 on Java 1.8.0_201 Java HotSpot(TM) 64-Bit Server VM
My file looks like this, and from the repl I desired to invoke the foo function
(ns cljtest.test
(:gen-class))
(defn foo [input]
(assoc {} "a" 123))
Both these approaches worked fine for me on the repl.
1)Switch to the appropriate name space:
cljtest.core=> (in-ns 'cljtest.test)
#object[clojure.lang.Namespace 0x90175dd "cljtest.test"]
cljtest.test=> (foo nil)
{"a" 123}
cljtest.test=>
2)Require the appropriate name space:
cljtest.core=> (require '[cljtest.test :as test])
nil
cljtest.core=> (test/foo nil)
{"a" 123}
cljtest.core=>

How can i minimize the AOT compilation in leiningen (Clojure)

When you create a Clojure project with leiningen all the *.clj-files are compiled AOT. Normally the AOT compilation is not necessary and I would like to minimize it.
This is necessary for me to raise acceptance of Clojure as a complement in a Java-dominated environment. It is easier to "sell" a single class-file as the glue together with a couple of kB clj-files against the alternative of having 250+ kB class-files with strange names and hidden amongst them a little clj-file (which isn't even read any more during execution).
Ideally the result of "lein compile" would be only a single small class file which (together with the clj-files and the clojure-library) implements the needed class-instance.
What is the easiest way to achieve this? I would prefer not to write a single line of Java (of course).
Update after feedback from technomancy
I do not suspect this being a leiningen problem. Let me explain what I am after with an example. Please forgive the length of the example. I am using leiningen 1.3.1 but I think 1.4.0-SNAPSHOOT behaves the same way.
$ lein new dummy
Created new project in: dummy
$ cd dummy
Now change project.clj to (added ":main dummy.core"):
(defproject dummy "1.0.0-SNAPSHOT"
:description "FIXME: write"
:dependencies [[org.clojure/clojure "1.2.0"]
[org.clojure/clojure-contrib "1.2.0"]]
:main dummy.core)
and src/dummy/core.clj to:
(ns dummy.core
(:gen-class))
(defn -main [& args]
(println "This is Clojure code, args=" args))
Now compile it:
$ lein compile
Compiling dummy.core
This generates the following files in classes/dummy:
core.class
core__init.class
core$loading__4410__auto__.class
core$_main.class
This is all correct, I can execute the result:
$ java -cp lib/*:classes dummy.core Hello
This is Clojure code, args= (Hello)
Now comes what I want to have instead, but I do it manually:
I can delete all class-files except core.class and copy the core.clj into classes/dummy which now looks very empty:
$ ls classes/dummy/
core.class
core.clj
The core.class contains code to load the core.clj at runtime and the result is still the same, I still can do:
$ java -cp lib/*:classes dummy.core Hello
This is Clojure code, args= (Hello)
I can also modify core.clj (note: in classes/dummy!) and of course changes do not need to be recompiled.
Now my question boils down to this: Is there an easier way to get just this core.class?
Leiningen has done no AOT by default for quite some time now; perhaps you have an older version? But there is a new feature (in 1.4.0-SNAPSHOT) that ensures .class files created due to transitive AOT get deleted before jar creation, (see Clojure assembla #322) which may also interest you.