This repository has been archived by the owner on Sep 16, 2021. It is now read-only.
forked from healthfinch/depstar
-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
uberjar.clj
199 lines (172 loc) · 6.16 KB
/
uberjar.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
(ns hf.depstar.uberjar
(:require [clojure.edn :as edn]
[clojure.java.io :as jio])
(:import [java.io InputStream OutputStream PushbackReader]
[java.nio.file CopyOption LinkOption OpenOption
StandardCopyOption StandardOpenOption
FileSystem FileSystems Files
FileVisitResult FileVisitor
Path]
[java.nio.file.attribute BasicFileAttributes FileAttribute]
[java.util.jar JarInputStream JarOutputStream JarEntry]))
;; future:
;; other knobs?
;; clj -M options?
;; look into MANIFEST entries
(defonce ^FileSystem FS (FileSystems/getDefault))
(defn path
^Path [s]
(.getPath FS s (make-array String 0)))
(defn clash-strategy
[filename]
(cond
(= "data_readers.clj" filename)
:merge-edn
(re-find #"^META-INF/services/" filename)
:concat-lines
:else
:noop))
(defmulti clash (fn [filename in target]
(prn {:warning "clashing jar item" :path filename})
(clash-strategy filename)))
(defmethod clash
:merge-edn
[_ in target]
(let [er #(with-open [r (PushbackReader. %)] (edn/read r))
f1 (er (jio/reader in))
f2 (er (Files/newBufferedReader target))]
(with-open [w (Files/newBufferedWriter target (make-array OpenOption 0))]
(binding [*out* w]
(prn (merge f1 f2))))))
(defmethod clash
:concat-lines
[_ in target]
(let [f1 (line-seq (jio/reader in))
f2 (Files/readAllLines target)]
(with-open [w (Files/newBufferedWriter target (make-array OpenOption 0))]
(binding [*out* w]
(run! println (-> (vec f1)
(conj "\n")
(into f2)))))))
(defmethod clash
:default
[_ in target])
;; do nothing, first file wins
(defn excluded?
[filename]
(or (#{"project.clj"
"LICENSE"
"COPYRIGHT"} filename)
(re-matches #"(?i)META-INF/.*\.(?:MF|SF|RSA|DSA)" filename)
(re-matches #"(?i)META-INF/(?:INDEX\.LIST|DEPENDENCIES|NOTICE|LICENSE)(?:\.txt)?" filename)))
(defn copy!
;; filename drives strategy
[filename ^InputStream in ^Path target]
(when-not (excluded? filename)
(if (Files/exists target (make-array LinkOption 0))
(clash filename in target)
(Files/copy in target ^"[Ljava.nio.file.CopyOption;" (make-array CopyOption 0)))))
(defn consume-jar
[^Path path f]
(with-open [is (-> path
(Files/newInputStream (make-array OpenOption 0))
java.io.BufferedInputStream.
JarInputStream.)]
(loop []
(when-let [entry (try (.getNextJarEntry is) (catch Exception _))]
(f is entry)
(recur)))))
(defn classify
[entry]
(let [p (path entry)
symlink-opts (make-array LinkOption 0)]
(if (Files/exists p symlink-opts)
(cond
(Files/isDirectory p symlink-opts)
:directory
(and (Files/isRegularFile p symlink-opts)
(re-find #"\.jar$" (.toString p)))
:jar
:else :unknown)
:not-found)))
(defmulti copy-source*
(fn [src dest options]
(classify src)))
(defmethod copy-source*
:jar
[src dest options]
(when-not (= :thin (:jar options))
(consume-jar (path src)
(fn [inputstream ^JarEntry entry]
(let [name (.getName entry)
target (.resolve ^Path dest name)]
(if (.isDirectory entry)
(Files/createDirectories target (make-array FileAttribute 0))
(do (Files/createDirectories (.getParent target) (make-array FileAttribute 0))
(copy! name inputstream target))))))))
(defn copy-directory
[^Path src ^Path dest]
(let [copy-dir
(reify FileVisitor
(visitFile [_ p attrs]
(let [f (.relativize src p)]
(with-open [is (Files/newInputStream p (make-array OpenOption 0))]
(copy! (.toString f) is (.resolve dest f))))
FileVisitResult/CONTINUE)
(preVisitDirectory [_ p attrs]
(Files/createDirectories (.resolve dest (.relativize src p))
(make-array FileAttribute 0))
FileVisitResult/CONTINUE)
(postVisitDirectory [_ p ioexc]
(if ioexc (throw ioexc) FileVisitResult/CONTINUE))
(visitFileFailed [_ p ioexc] (throw (ex-info "Visit File Failed" {:p p} ioexc))))]
(Files/walkFileTree src copy-dir)
:ok))
(defmethod copy-source*
:directory
[src dest options]
(copy-directory (path src) dest))
(defmethod copy-source*
:not-found
[src _dest _options]
(prn {:warning "could not find classpath entry" :path src}))
(defn copy-source
[src dest options]
(copy-source* src dest options))
(defn write-jar
[^Path src ^Path target]
(with-open [os (-> target
(Files/newOutputStream (make-array OpenOption 0))
JarOutputStream.)]
(let [walker (reify FileVisitor
(visitFile [_ p attrs]
(.putNextEntry os (JarEntry. (.toString (.relativize src p))))
(Files/copy p os)
FileVisitResult/CONTINUE)
(preVisitDirectory [_ p attrs]
(when (not= src p) ;; don't insert "/" to zip
(.putNextEntry os (JarEntry. (str (.relativize src p) "/")))) ;; directories must end in /
FileVisitResult/CONTINUE)
(postVisitDirectory [_ p ioexc]
(if ioexc (throw ioexc) FileVisitResult/CONTINUE))
(visitFileFailed [_ p ioexc] (throw ioexc)))]
(Files/walkFileTree src walker)))
:ok)
(defn current-classpath
[]
(vec (.split ^String
(System/getProperty "java.class.path")
(System/getProperty "path.separator"))))
(defn depstar-itself?
[p]
(re-find #"depstar" p))
(defn run
[{:keys [dest] :as options}]
(let [tmp (Files/createTempDirectory "uberjar" (make-array FileAttribute 0))
cp (into [] (remove depstar-itself?) (current-classpath))]
(run! #(copy-source % tmp options) cp)
(println "Writing jar...")
(write-jar tmp (path dest))))
(defn -main
[destination]
(run {:dest destination}))