Repository navigation
Expand file tree
/
Copy pathcompile.clj
More file actions
172 lines (151 loc) · 7.23 KB
/
Copy pathcompile.clj
File metadata and controls
172 lines (151 loc) · 7.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
(ns leiningen.compile
"Compile Clojure source into .class files."
(:require [leiningen.core.eval :as eval]
[leiningen.core.main :as main]
[bultitude.core :as b]
[clojure.java.io :as io])
(:refer-clojure :exclude [compile])
(:import (java.io PushbackReader File)))
(defn regex? [str-or-re]
(instance? java.util.regex.Pattern str-or-re))
(defn- matching-nses [re-or-sym namespaces]
(if (regex? re-or-sym)
(filter #(re-find re-or-sym (name %)) namespaces)
[re-or-sym]))
(defn- find-namespaces-by-regex [project nses]
(let [avail-nses (->> (:source-paths project)
(map io/file)
(b/namespaces-on-classpath :classpath))]
(mapcat #(matching-nses % avail-nses) nses)))
(defn compilable-namespaces
"Returns a seq of the namespaces that are compilable, regardless of whether
their class files are present and up-to-date."
[{:keys [aot source-paths] :as project}]
(if (or (= :all aot) (= [:all] aot))
(b/namespaces-on-classpath :classpath (map io/file source-paths))
(find-namespaces-by-regex project aot)))
(defn stale-namespaces
"Return a seq of namespaces that are both compilable and that have missing or
out-of-date class files."
[project]
(for [namespace (compilable-namespaces project)
:let [[rel-source source]
(or (first (for [source-path (:source-paths project)
rel-source (map (partial b/path-for namespace) ["clj" "cljc"])
:let [file (io/file source-path rel-source)]
:when (.exists ^File file)]
[rel-source file]))
(let [rel-source (b/path-for namespace)]
;; always return a source file location (#1205)
[rel-source (io/file (first (:source-paths project)) rel-source)]))]
:when source
:let [rel-compiled (.replaceFirst rel-source "\\.cljc?$" "__init.class")
compiled (io/file (:compile-path project) rel-compiled)]
:when (>= (.lastModified source) (.lastModified compiled))]
namespace))
;; .class file cleanup
(defn- package-in-project?
"Tests if the package found in the compile path exists as a directory in the
source path."
[found-path compile-path source-path]
(.isDirectory (io/file (.replace found-path compile-path source-path))))
(defn- has-source-package?
"Test if the class file's package exists as a directory in source-paths."
[project f source-paths]
(and source-paths
(let [[[parent] [_ _ proxy-mod-parent]]
(->> f, (iterate #(.getParentFile %)),
(take-while identity), rest,
(split-with #(not (re-find #"^proxy\$" (.getName %)))))
found-path (.getPath (or proxy-mod-parent parent))
compile-path (:compile-path project)]
(some #(package-in-project? found-path compile-path %) source-paths))))
(defn- source-in-project?
"Tests if a file found in the compile path exists in the source path."
[parent compile-path source-path]
(let [path (.replace parent compile-path source-path)]
(or (.exists (io/file (str path ".clj")))
(.exists (io/file (str path ".cljc"))))))
(defn- class-in-project? [project f]
(or (has-source-package? project f (:source-paths project))
(has-source-package? project f (:java-source-paths project))
(let [parent (.getParent f)
compile-path (:compile-path project)]
(some #(source-in-project? parent compile-path %) (:source-paths project)))))
(defn- relative-path [project f]
(let [root-length (if (= \/ (last (:compile-path project)))
(count (:compile-path project))
(inc (count (:compile-path project))))]
(subs (.getAbsolutePath f) root-length)))
(defn- blacklisted-class? [project f]
;; true indicates all non-project classes are blacklisted
(or (true? (:clean-non-project-classes project))
(some #(re-find % (relative-path project f))
(:clean-non-project-classes project))))
(defn- whitelisted-class? [project f]
(or (class-in-project? project f)
(and (:class-file-whitelist project)
(re-find (:class-file-whitelist project)
(relative-path project f)))))
(defn clean-non-project-classes [project]
(when (:clean-non-project-classes project)
(doseq [f (file-seq (io/file (:compile-path project)))
:when (and (.isFile f)
(not (whitelisted-class? project f))
(blacklisted-class? project f))]
(.delete f))))
(defn compilation-specs [cli-args]
(if (contains? #{[:all] [":all"]} cli-args)
[:all]
(->> cli-args
(map #(if (string? %) (read-string %) %))
(sort-by (comp not regex?)))))
(def ^:private thread-factory-form
`(let [counter# (atom 0)]
(proxy [java.util.concurrent.ThreadFactory] []
(newThread [r#]
(let [thread-factory# (java.util.concurrent.Executors/defaultThreadFactory)]
(doto (.newThread thread-factory# r#)
(.setName (str "leiningen-send-off-pool-" (swap! counter# inc)))))))))
(def ^:private set-agent-threadpool-form
;; set-agent-send-off-executor! was introduced in Clojure 1.5
`(when-let [set-executor!# (resolve 'clojure.core/set-agent-send-off-executor!)]
(set-executor!#
(doto ^java.util.concurrent.ThreadPoolExecutor
(java.util.concurrent.Executors/newCachedThreadPool ~thread-factory-form)
(.setKeepAliveTime 100 java.util.concurrent.TimeUnit/MILLISECONDS)))))
(defn compile
"Compile Clojure source into .class files.
Uses the namespaces specified under :aot in project.clj or those given
as command-line arguments. Use :all argument to compile everything. Pass
#\"regular expressions\" to compile any matching namespaces. You may need
to escape punctuation for your shell.
This should automatically happen when required if it's configured correctly; it
shouldn't need to be manually invoked. See the javac task as well.
Compiling code loads the namespace, so keep side-effects out of the top level.
Code that should run on startup belongs in a -main defn."
([project]
(if-let [namespaces (seq (stale-namespaces project))]
(let [ns-sym (gensym "namespace")
form `(do
~set-agent-threadpool-form
(doseq [~ns-sym '~namespaces]
~(if main/*info*
`(binding [*out* *err*]
(println "Compiling" ~ns-sym)))
(try
(clojure.core/compile ~ns-sym)
(catch Throwable t#
(binding [*out* *err*]
(println (.getMessage t#)))
(throw t#)))))
project (update-in project [:prep-tasks]
(partial remove #{"compile"}))]
(try (binding [*print-dup* true]
(eval/eval-in-project project form))
(catch Exception e
(main/abort "Compilation failed:" (.getMessage e)))
(finally (clean-non-project-classes project))))
(main/debug "All namespaces already AOT compiled.")))
([project & args]
(compile (assoc project :aot (compilation-specs args)))))