/
jar.clj
160 lines (138 loc) · 6.26 KB
/
jar.clj
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
(ns leiningen.jar
"Create a jar containing the compiled code and original source."
(:require [leiningen.compile :as compile])
(:use [leiningen.pom :only [make-pom make-pom-properties]]
[leiningen.deps :only [deps]]
[clojure.contrib.io :only [to-byte-array copy file slurp*]]
[clojure.contrib.string :only [join replace-re split]])
(:import [java.util.jar Manifest JarEntry JarOutputStream]
[java.util.regex Pattern]
[java.io BufferedOutputStream FileOutputStream
ByteArrayInputStream]))
(def bin-template (-> (.getContextClassLoader (Thread/currentThread))
(.getResourceAsStream "script-template")
(slurp*)))
(defn local-repo-path [{:keys [group name version]}]
(format "$HOME/.m2/repository/%s/%s/%s/%s-%s.jar"
group name version name version))
(defn- script-classpath-for [project deps-fileset]
(join ":" (conj (for [dep (-> deps-fileset
(.getDirectoryScanner lancet/ant-project)
(.getIncludedFiles))]
(format "$HOME/.m2/repository/%s" dep))
(local-repo-path project))))
(defn- shell-wrapper-name [project]
(or (:bin (:shell-wrapper project)
(format "bin/%s" (:name project)))))
(defn- shell-wrapper-contents [project bin-name main deps-fileset]
(if-let [is (-> (.getContextClassLoader (Thread/currentThread))
(.getResourceAsStream bin-name))]
(slurp* is)
(format bin-template
(script-classpath-for project deps-fileset) main)))
(defn- shell-wrapper-filespecs [project deps-fileset]
(when (:shell-wrapper project)
(let [main (or (:main (:shell-wrapper project)) (:main project))
bin-name (shell-wrapper-name project)
bin (shell-wrapper-contents project bin-name main deps-fileset)]
[{:type :bytes
:path bin-name
:bytes (.getBytes bin)}])))
(def default-manifest
{"Created-By" (str "Leiningen " (System/getProperty "leiningen.version"))
"Built-By" (System/getProperty "user.name")
"Build-Jdk" (System/getProperty "java.version")})
(defn make-manifest [project & [extra-entries]]
(Manifest.
(ByteArrayInputStream.
(to-byte-array
(reduce (fn [manifest [k v]]
(str manifest "\n" k ": " v))
"Manifest-Version: 1.0"
(merge default-manifest (:manifest project)
(when (:shell-wrapper project)
{"Leiningen-shell-wrapper" (shell-wrapper-name project)})
(when-let [main (:main project)]
{"Main-Class" (.replaceAll (str main) "-" "_")})))))))
(defn manifest-map [manifest]
(let [attrs (.getMainAttributes manifest)]
(zipmap (map str (keys attrs)) (vals attrs))))
(defn unix-path [path]
(.replaceAll path "\\\\" "/"))
(defn skip-file? [file]
(or (.isDirectory file)
(re-find #"^\.?#" (.getName file))
(re-find #"~$" (.getName file))))
(defmulti copy-to-jar (fn [project jar-os spec] (:type spec)))
(defn- trim-leading-str [s to-trim]
(replace-re (re-pattern (str "^" (Pattern/quote to-trim))) "" s))
(defmethod copy-to-jar :path [project jar-os spec]
(let [root (str (unix-path (:root project)) \/)
noroot #(trim-leading-str (unix-path %) root)
[resources classes src]
(map noroot (map project [:resources-path :compile-path :source-path]))]
(doseq [child (file-seq (file (:path spec)))]
(when-not (skip-file? child)
(let [path (reduce trim-leading-str (unix-path (str child))
[root resources classes src "/"])]
(.putNextEntry jar-os (doto (JarEntry. path)
(.setTime (.lastModified child))))
(copy child jar-os))))))
(defmethod copy-to-jar :bytes [project jar-os spec]
(.putNextEntry jar-os (JarEntry. (:path spec)))
(copy (ByteArrayInputStream. (:bytes spec)) jar-os))
;; TODO: hacky; needed for conditional :resources-path below
(defmethod copy-to-jar nil [project jar-os spec])
(defn write-jar [project out-filename filespecs]
(let [manifest (make-manifest project)]
(with-open [jar-os (-> out-filename
(FileOutputStream.)
(BufferedOutputStream.)
(JarOutputStream. manifest))]
(doseq [filespec filespecs]
(copy-to-jar project jar-os filespec)))))
(defn get-default-jar-name [project]
(or (:jar-name project)
(str (:name project) "-" (:version project) ".jar")))
(defn get-jar-filename
([project jar-name]
(let [jar-dir (:jar-dir project)]
(.mkdirs (file jar-dir))
(str jar-dir "/" jar-name)))
([project] (get-jar-filename project (get-default-jar-name project))))
(defn get-default-uberjar-name [project]
(or (:uberjar-name project)
(str (:name project) \- (:version project) "-standalone.jar")))
(defn- filespecs [project deps-fileset]
(concat
[{:type :bytes
:path (format "META-INF/maven/%s/%s/pom.xml"
(:group project)
(:name project))
:bytes (make-pom project)}
{:type :bytes
:path (format "META-INF/maven/%s/%s/pom.properties"
(:group project)
(:name project))
:bytes (make-pom-properties project)}
(when (and (:resources-path project)
(.exists (file (:resources-path project))))
{:type :path :path (:resources-path project)})
{:type :path :path (:compile-path project)}
{:type :path :path (str (:root project) "/project.clj")}]
(when-not (:omit-source project)
[{:type :path :path (:source-path project)}])
(shell-wrapper-filespecs project deps-fileset)))
(defn jar
"Create a $PROJECT-$VERSION.jar file containing the compiled .class files as
well as the source .clj files. If project.clj contains a :main symbol, it will
be used as the main-class for an executable jar."
([project jar-name]
(binding [compile/*silently* true]
(compile/compile project))
(let [jar-path (get-jar-filename project jar-name)
deps-fileset (deps project :skip-dev)]
(write-jar project jar-path (filespecs project deps-fileset))
(println "Created" jar-path)
jar-path))
([project] (jar project (get-default-jar-name project))))