How to compile Java code after Clojure code in leiningen
Asked Answered
P

3

6

In my Leiningen project:

(defproject com.stackoverflow.clojure/tests "0.1.0-SNAPSHOT"
  :description "Tests of Clojure test-framework."
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [instaparse "1.3.4"]]
  :aot [com.stackoverflow.clojure.testGenClass]
  :source-paths      ["src/main/clojure"]
  :java-source-paths ["src/main/java"]
  :test-paths        ["src/test/clojure"]
  :java-test-paths   ["src/test/java"]
  )

I'm generating Java-classes with gen-class:

(ns com.stackoverflow.clojure.testGenClass
  (:gen-class
     :name com.stackoverflow.clojure.TestGenClass
     :implements [com.stackoverflow.clojure.TestGenClassInterface]
     :prefix "java-"))

(def ^:private pre "START: ")

(defn java-addToString [this text post]
  (str pre text post))

which I want to use in Java:

package com.stackoverflow.clojure;

public class TestGenClassTest {

    private TestGenClassTest() {
    }

    public static void main(String[] args) {
        TestGenClassInterface gc = new TestGenClass();
        System.out.println(gc.addToString("Called from Java!", " :END"));
    }
}

Starting lein compile throws the following error:

Compiling 4 source files to /home/eddy/workspace/TestSkripts/target/classes
/home/eddy/workspace/TestSkripts/src/main/java/com/stackoverflow/clojure/TestGenClassTest.java:9: error: cannot find symbol
        TestGenClassInterface gc = new TestGenClass();
                                       ^
  symbol:   class TestGenClass
  location: class TestGenClassTest
1 error

It seems to me, that during compilation of the Java-code (here: TestGenClassTest) the Class is not available. What I usually did is

  1. commenting out those parts that use the gen-class generated class (here TestGenClass)
  2. run lein compile (to generate the class-file)
  3. then taking the outcommented code in again
  4. and run lein compile again.

I' sure there is a better way, that makes all manual steps redundat.

Plater answered 17/10, 2014 at 7:37 Comment(0)
T
1

You want to add a pre-compilation step. Run the pre-compile ...then you're good to go.

  1. Put your interface into a seperate file path src/main/pre/interface/clojure

  2. add the below to :profiles

    (defproject com.stackoverflow.clojure/tests "0.1.0-SNAPSHOT"
     :description "Tests of Clojure test-framework."
     :url "http://example.com/FIXME"
     :dependencies [[org.clojure/clojure "1.6.0"]
                 [instaparse "1.3.4"]]
     :source-paths      ["src/main/clojure"]
     :java-source-paths ["src/main/java"]
     :test-paths        ["src/test/clojure"]
     :java-test-paths   ["src/test/java"]
     :profiles { :precomp { :source-paths ["src/main/pre/interface/clojure"]
                         :aot [parser.ast] } })
    

Then you can run lein with-profile precomp compile after which lein test should work

Turbulence answered 16/11, 2016 at 0:9 Comment(0)
S
0

Let me refer you again to Polyglot (Clojure, Java) Projects With Leiningen, and particularly this part: Interleaving Compilation Steps.

Be default, Leiningen is executing javac and then compile. What you can achieve also compile -> javac -> compile.

Saito answered 17/10, 2014 at 8:21 Comment(2)
So how would a profile look like, that first compiles the Interface com.stackoverflow.clojure.TestGenClassInterface using javac (if necessary) then compiles the Clojure-part that implements the interface (com.stackoverflow.clojure.TestGenClass) and afterwards compiles the Java main Programm? Is it two additional profiles, or a single one? Is the correct compiler called by using the path-statements?Plater
No, as much as I can see the correct compiler is not automatically infered from the path statements.Portraiture
P
0

Here is a sample project.clj from a project where the clojure code relies on Java code, and AOT compiles so that it is available to upstream tests Java code. So here we have three layers of compilation dependencies: Java → Clojure → Java and it all works:

(defproject myproject "0.1.0-SNAPSHOT"
  :min-lein-version "2.0.0"
  :source-paths      ["src/clojure"]/
  :java-source-paths ["src/java"]
  :javac-options     ["-target" "1.8" "-source" "1.8"]
  :dependencies [[org.clojure/clojure "1.8.0"]]
  :aot [the class that java-test needs]
  :profiles {:java-tests-compile
    {:java-source-paths ["src/java-test"]}}
  :aliases {
    "java-tests" ["do" "compile," "with-profile" "java-tests-compile" "javac," "run" "-m" "myorg.myProject.java-test-code"]
    "all-tests" ["do" "test," "java-tests"]
  })

; https://github.com/technomancy/leiningen/issues/847#issuecomment-289943710

The upstream java tests code finds all it needs when it compiles and runs. (The upstream java layer in the project where this sample comes from just happens to be java tests code, but there's nothing here that is specific to tests code, the technique should work generically, when you want Java code to use your gen-class generated clojure code).

The original question's code is just missing the profile and alias parts shown here, that tell leiningen how to compile the Java, after clojure compilation. This is needed because by default, in the current versions of leiningen, leiningen compiles Java before it compiles clojure; it does not attempt to order the compilation of source files across languages.

So in this solution, we add another compilation step as a separate leiningen task, through the use of an alias. Having this configuration, we only need to issue lein java-tests which first compiles clojure, then the upper-stream Java layer. Voila.

P.S. maybe we can even override lein's compile task rather than add a new task, but that was not worth the play for me...

Portraiture answered 31/3, 2017 at 17:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.