Make javac run in a subprocess. Fixes #809.

If we're not making javac run in a subprocess, libraries the project
depends on which leiningen has another version of will clash. This is
because leiningen append its standalone to java's bootclasspath, which
makes the ToolProvider add these classpaths automatically into the java
compiler it returns. By starting a subprocess without leiningen added to
the bootclasspath, we avoid this library clashing.

UI changes as a result of this patch: Whenever javac fails, the task one
wanted to run will be responsible for aborting Leiningen, whereas javac
did this itself when it discovered that the compilation failed. As we're
running in a subprocess, an abort message such as

    Uberjar aborting because jar/compilation failed: Subprocess failed

will appear, though this will only be appended to the javac error
message and the "Compilation of Java sources (lein javac) failed."
message.

This commit also adds the overhead of starting a subprocess when one
have to compile java sources, but it will not start one if no
.class-files are outdated/not existing.

Not tested on Windows.
This commit is contained in:
Jean Niklas L'orange 2012-12-13 18:03:56 +01:00
parent b4558ec274
commit 377a98f45e

View file

@ -1,7 +1,10 @@
(ns leiningen.javac
"Compile Java source files."
(:require [leiningen.classpath :as classpath]
[leiningen.core.main :as main]
[leiningen.core
[main :as main]
[eval :as eval]
[project :as project]]
[clojure.java.io :as io])
(:import java.io.File
javax.tools.ToolProvider))
@ -66,24 +69,41 @@
"-d" (:compile-path project)]
files)))
;; Pure java projects will not have Clojure on the classpath. As such, we need
;; to put it there ourselves for compiling.
(def subprocess-profile
{:dependencies [['org.clojure/clojure (clojure-version)]]
:eval-in :subprocess})
;; We can't really control what is printed here. We're just going to
;; allow `.run` to attach in, out, and err to the standard streams. This
;; should have the effect of compile errors being printed. javac doesn't
;; actually output any compilation info unless it has to (for an error)
;; or you make it do so with `-verbose`.
(defn- run-javac-task
"Run javac to compile all source files in the project."
(defn- run-javac-subprocess
"Run javac to compile all source files in the project. The compilation is run
in a subprocess to avoid it from adding the leiningen standalone to the
classpath, as leiningen adds itself to the classpath through the
bootclasspath."
[project args]
(let [compile-path (:compile-path project)
files (stale-java-sources (:java-source-paths project) compile-path)]
files (stale-java-sources (:java-source-paths project) compile-path)
javac-opts (vec (javac-options project files args))
form `(do (println "Compiling" ~(count files)
"source files to" ~compile-path)
(.mkdirs (io/file ~compile-path))
(when-not (zero? (.run (ToolProvider/getSystemJavaCompiler)
nil nil nil
(into-array String ~javac-opts)))
(.println System/err
"Compilation of Java sources(lein javac) failed.")
(System/exit 1)))] ; Ok here, as we're in a subprocess.
(when (seq files)
(if-let [compiler (ToolProvider/getSystemJavaCompiler)]
(do
(main/info "Compiling" (count files) "source files to" compile-path)
(.mkdirs (io/file compile-path))
(when-not (zero? (.run compiler nil nil nil
(javac-options project files args)))
(main/abort "Compilation of Java sources (lein javac) failed.")))
(if (ToolProvider/getSystemJavaCompiler)
;; compiler will be available from subprocess if available from here
(eval/eval-in
(project/merge-profiles project [subprocess-profile])
form)
(main/abort "lein-javac: system java compiler not found;"
"Be sure to use java from a JDK\nrather than a JRE by"
"either modifying PATH or setting JAVA_CMD.")))))
@ -99,4 +119,4 @@ Like the compile and deps tasks, this should be invoked automatically when
needed and shouldn't ever need to be run by hand. By default it is called before
compilation of Clojure source; change :prep-tasks to alter this."
[project & args]
(run-javac-task project args))
(run-javac-subprocess project args))