Run eval-in-project using clojure.java.shell. Fixes #239.

This commit is contained in:
Phil Hagelberg 2011-07-25 13:32:59 -07:00
parent 32ffd7818d
commit 53141eab86
5 changed files with 75 additions and 111 deletions

View file

@ -166,7 +166,14 @@
;; You can set JVM-level options here.
:jvm-opts ["-Xmx1g"]
;; If your project is a Leiningen plugin, set this to skip the subprocess step
:eval-in-leiningen false)
:eval-in-leiningen false
;; Leiningen includes a workaround for a problem with Clojure's
;; agent thread pool. If you see RejectedExecutionException using
;; futures or agents, you may be working with a plugin that doesn't
;; take this workaround into account yet--see the "Threads" section
;; of doc/PLUGINS.md. This key will disable Leiningen's workaround.
;; It may cause some other plugins to fail to exit when they finish.
:skip-shutdown-agents true)
;; You can use Robert Hooke to modify behaviour of any task function,
;; but the prepend-tasks function is shorthand that is more convenient

View file

@ -4,8 +4,7 @@
[leiningen.deps :only [find-jars]]
[leiningen.util.paths :only [leiningen-home]]
[clojure.java.io :only [file]]
[clojure.string :only [join]])
(:import (org.apache.tools.ant.types Path)))
[clojure.string :only [join]]))
(defn- read-dependency-project [dep]
(let [project (.getAbsolutePath (file dep "project.clj"))]
@ -35,15 +34,6 @@
:when (re-find #"\.jar$" (.getName jar))]
(.getAbsolutePath jar)))
;; TODO: move to lancet?
(defn ^:internal make-path
"Constructs an ant Path object from Files and strings."
[& paths]
(let [ant-path (Path. nil)]
(doseq [path paths]
(.addExisting ant-path (Path. nil (str path))))
ant-path))
(defn get-classpath
"Answer a list of classpath entries for PROJECT."
[project]

View file

@ -3,18 +3,13 @@
(:use [leiningen.deps :only [deps find-jars]]
[leiningen.core :only [defdeprecated user-settings *interactive?*]]
[leiningen.javac :only [javac]]
[leiningen.classpath :only [make-path get-classpath]]
[leiningen.classpath :only [get-classpath-string]]
[clojure.java.io :only [file resource reader]]
[clojure.java.shell :only [sh]]
[leiningen.util.ns :only [namespaces-in-dir]])
(:require [leiningen.util.paths :as paths]
[lancet.core :as lancet])
(:require [leiningen.util.paths :as paths])
(:refer-clojure :exclude [compile])
(:import (java.io PushbackReader)
(org.apache.tools.ant.taskdefs Java)
(org.apache.tools.ant.types ZipFileSet)
(java.lang.management ManagementFactory)
(java.util.regex Pattern)
(org.apache.tools.ant.types Environment$Variable)))
(:import (java.io PushbackReader)))
(declare compile)
@ -70,6 +65,8 @@
(.lastModified class-file)))))
(compilable-namespaces project)))
;; eval-in-project
(defdeprecated get-os paths/get-os)
(defdeprecated get-os paths/get-arch)
@ -79,10 +76,20 @@
"NUL"
"/dev/null")))
(defn- get-jvm-args [project]
`(~@(when-let [opts (System/getenv "JVM_OPTS")] [opts])
(defn- system-property [[k v]]
(format "-D%s=%s" (name k) v))
(defn ^{:internal true} get-jvm-args [project]
`(~@(let [opts (System/getenv "JVM_OPTS")]
(when (seq opts) [opts]))
~@(:jvm-opts project)
~@(:jvm-opts (user-settings))))
~@(:jvm-opts (user-settings))
~@(map system-property {:clojure.compile.path (:compile-path project)
(str (:name project) ".version") (:version project)
:clojure.debug (boolean (or (System/getenv "DEBUG")
(:debug project)))})
~@(when (.exists (file (:native-path project)))
[(system-property :java-library-path (:native-path project))])))
(defn- injected-forms []
(with-open [rdr (-> "robert/hooke.clj" resource reader PushbackReader.)]
@ -103,9 +110,10 @@
;; non-daemon threads will prevent process from exiting;
;; see http://tinyurl.com/2ueqjkl
(finally
(when-not (or (= "1.5" (System/getProperty
(when-not (or ~*interactive?*
(= "1.5" (System/getProperty
"java.specification.version"))
~*interactive?*)
~(project :skip-shutdown-agents))
(shutdown-agents)))))]
;; work around java's command line handling on windows
;; http://bit.ly/9c6biv This isn't perfect, but works for what's
@ -125,12 +133,37 @@
(:checksum-deps project))
(deps project)))
(defn- add-system-property [java key value]
(.addSysproperty java (doto (Environment$Variable.)
(.setKey (name key))
(.setValue (name value)))))
(defn eval-in-leiningen [project form-string]
;; bootclasspath workaround: http://dev.clojure.org/jira/browse/CLJ-673
(require '[clojure walk repl])
(require '[clojure.java io shell browse])
(when (:debug project)
(System/setProperty "clojure.debug" "true"))
;; need to at least pretend to return an exit code
(try (binding [*warn-on-reflection* (:warn-on-reflection project), *ns* *ns*]
(eval (read-string form-string)))
0
(catch Exception e
(.printStackTrace e)
1)))
(defn- escape [arg]
(if (= :windows (paths/get-os))
(format "\"%s\"" (.replaceAll arg "\"" "\\\\\""))
arg))
(defn eval-in-subprocess [project form-string]
(let [command (map escape `(~(or (System/getenv "JAVA_CMD") "java")
"-cp" ~(get-classpath-string project)
~@(get-jvm-args project)
"clojure.main" "-e" ~form-string))
{:keys [exit out err]} (apply sh command)]
(println (if (string? out) out (String. out)))
(when (seq err)
(binding [*out* *err*]
(println err)))
exit))
;; TODO: split this function up
(defn eval-in-project
"Executes form in an isolated classloader with the classpath and compile path
set correctly for the project. If the form depends on any requires, put them
@ -139,48 +172,12 @@
(when skip-auto-compile
(println "WARNING: eval-in-project's skip-auto-compile arg is deprecated."))
(prep project skip-auto-compile)
(if (:eval-in-leiningen project)
(do ;; bootclasspath workaround: http://dev.clojure.org/jira/browse/CLJ-673
(require '[clojure walk repl])
(require '[clojure.java io shell browse])
(when (:debug project)
(System/setProperty "clojure.debug" "true"))
;; need to at least pretend to return an exit code
(try (binding [*warn-on-reflection* (:warn-on-reflection project)
*ns* *ns*]
(eval (read-string (get-readable-form nil project form init))))
0
(catch Exception e
(.printStackTrace e)
1)))
(let [java (Java.)]
(.setProject java lancet/ant-project)
(add-system-property java :clojure.compile.path (:compile-path project))
(add-system-property java (format "%s.version" (:name project))
(:version project))
(when (:debug project)
(add-system-property java :clojure.debug true))
(when (.exists (file (:native-path project)))
(add-system-property java "java.library.path" (:native-path project)))
;; TODO: remove in 2.0?
(when (and (paths/legacy-native-path project)
(.exists (file (paths/legacy-native-path project))))
(add-system-property java "java.library.path"
(str (paths/legacy-native-path project))))
(.setClasspath java (apply make-path (get-classpath project)))
(.setFailonerror java true)
(.setFork java true)
(.setDir java (file (:root project)))
(doseq [arg (get-jvm-args project)]
(.setValue (.createJvmarg java) arg))
(.setClassname java "clojure.main")
;; to allow plugins and other tasks to customize
(when handler
(println "WARNING: eval-in-project's handler argument is deprecated.")
(handler java))
(.setValue (.createArg java) "-e")
(.setValue (.createArg java) (get-readable-form java project form init))
(.executeJava java))))
(let [form-string (get-readable-form nil project form init)]
(if (:eval-in-leiningen project)
(eval-in-leiningen project form-string)
(eval-in-subprocess project form-string))))
;; .class file cleanup
(defn- has-source-package?
"Test if the class file's package exists as a directory in :source-path."
@ -222,6 +219,8 @@
(blacklisted-class? project f))]
(.delete f))))
;; actual task
(defn- status [code msg]
(when-not *silently*
(binding [*out* (if (zero? code) *out* *err*)]

View file

@ -1,43 +1,14 @@
(ns leiningen.trampoline
(:refer-clojure :exclude [trampoline])
(:use [leiningen.core :only [apply-task task-not-found abort]]
[leiningen.compile :only [get-readable-form prep eval-in-project]]
[leiningen.classpath :only [get-classpath-string]])
(:require [clojure.string :as string]
[clojure.java.io :as io]
[clojure.java.shell :as shell]
[leiningen.util.paths :as paths]))
(def *trampoline?* false)
(defn get-jvm-opts [project]
(let [legacy-native (paths/legacy-native-path project)]
(filter identity [(if (not (empty? (System/getenv "JVM_OPTS")))
(System/getenv "JVM_OPTS"))
(if (:debug project)
"-Dclojure.debug=true")
(if (.exists (io/file (:native-path project)))
(str "-Djava.library.path=" (:native-path project))
(if (.exists (io/file legacy-native))
(str "-Djava.library.path=" legacy-native)))])))
(defn win-batch? []
(.endsWith (System/getProperty "leiningen.trampoline-file") ".bat"))
(defn escape [form-string]
(if (win-batch?)
form-string
(format "\"%s\"" (.replaceAll form-string "\"" "\\\\\""))))
(defn quote-path [string]
(format "\"%s\"" (if (win-batch?) string (.replace string "\\" "\\\\"))))
(defn command-string [project java-cmd jvm-opts [form init]]
(string/join " " [(quote-path java-cmd)
"-cp" (quote-path (get-classpath-string project))
(string/join " " (map quote-path jvm-opts))
"clojure.main" "-e"
(escape (get-readable-form (win-batch?) project form init))]))
(defn write-trampoline [command]
(spit (System/getProperty "leiningen.trampoline-file") command))
@ -53,16 +24,12 @@ issues. Not compatible with chaining.
ALPHA: subject to change without warning."
[project task-name & args]
(let [java-cmd (format "%s/bin/java" (System/getProperty "java.home"))
jvm-opts (get-jvm-opts project)
eval-args (atom nil)]
(let [command (atom nil)]
(when (:eval-in-leiningen project)
(println "Warning: trampoline has no effect with :eval-in-leiningen."))
(binding [*trampoline?* true
eval-in-project (fn [project form & [_ _ init]]
(prep project true)
(reset! eval-args [form init]) 0)]
shell/sh (fn [& c] (reset! command c) 0)]
(apply-task task-name project args task-not-found))
(if @eval-args
(write-trampoline (command-string project java-cmd jvm-opts @eval-args))
(if @command
(write-trampoline (string/join " " @command))
(abort task-name "is not trampolineable."))))

View file

@ -25,6 +25,7 @@
(deftest test-plugin
(is (zero? (eval-in-project (assoc sample-project
:eval-in-leiningen true
:skip-shutdown-agents true
:main nil)
'(do (require 'leiningen.compile)
:compiled)))))