Skip to content

Commit cd330e6

Browse files
committed
TBUILD-30 Apply exclusions and conflict handlers for local and git libs
1 parent e958224 commit cd330e6

3 files changed

Lines changed: 116 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Changelog
22
===========
33

4+
* next
5+
* uber - TBUILD-35 Fix error on exploding jar with / entry
6+
* uber - TBUILD-30 Apply exclusions and conflict handlers for local and git libs
47
* v0.9.0 8c93e0c on Dec 22, 2022
58
* Add clojure.tools.build.api/with-project-root macro
69
* java-command, compile-clj - TBUILD-34 - Use Clojure CLI logic in finding Java executable

src/main/clojure/clojure/tools/build/tasks/uber.clj

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
[java.io File InputStream FileInputStream BufferedInputStream
2020
OutputStream FileOutputStream BufferedOutputStream ByteArrayOutputStream]
2121
[java.nio.file Files]
22-
[java.nio.file.attribute FileAttribute]
22+
[java.nio.file.attribute FileAttribute FileTime]
2323
[java.util.jar JarEntry JarInputStream JarOutputStream Manifest]))
2424

2525
(set! *warn-on-reflection* true)
@@ -104,16 +104,16 @@
104104
(throw (ex-info (str "Conflicting path at " path " from " lib) {})))
105105

106106
(defn- handler-emit
107-
[entry buffer out-dir path write-spec]
107+
[^FileTime last-modified-time buffer out-dir path write-spec]
108108
(let [{:keys [string stream append] :or {append false}} write-spec
109109
out-file (jio/file out-dir path)]
110110
(if string
111111
(spit out-file string :append ^boolean append)
112112
(copy-stream! ^InputStream stream (BufferedOutputStream. (FileOutputStream. out-file ^boolean append)) buffer))
113-
(Files/setLastModifiedTime (.toPath out-file) (.getLastModifiedTime ^JarEntry entry))))
113+
(Files/setLastModifiedTime (.toPath out-file) last-modified-time)))
114114

115115
(defn- handle-conflict
116-
[handlers entry buffer out-dir {:keys [state path] :as handler-params}]
116+
[handlers last-modified-time buffer out-dir {:keys [state path] :as handler-params}]
117117
(let [use-handler (loop [[[re handler] & hs] (dissoc handlers :default)]
118118
(if re
119119
(if (re-matches re path)
@@ -124,7 +124,7 @@
124124
(let [{new-state :state, write :write} (use-handler handler-params)]
125125
(when write
126126
(doseq [[path write-spec] write]
127-
(handler-emit entry buffer out-dir path write-spec)))
127+
(handler-emit last-modified-time buffer out-dir path write-spec)))
128128
(or new-state state))
129129
(throw (ex-info (format "No handler found for conflict at %s" path) {})))))
130130

@@ -138,8 +138,34 @@
138138
true
139139
(throw (ex-info (str "Unable to create parent dirs for: " (.toString child)) {})))))
140140

141+
(defn- explode1
142+
"Given one entry/src file, copy to target pursuant to excludes and handlers.
143+
Returns possibly updated state for further exploding."
144+
[^InputStream is ^String path dir? ^FileTime last-modified-time
145+
^File out-file lib {:keys [out-dir buffer exclude handlers] :as context} state]
146+
(cond
147+
;; excluded or directory - do nothing
148+
(or (exclude-from-uber? exclude path) dir?)
149+
state
150+
151+
;; conflict, same file from multiple sources - handle
152+
(.exists out-file)
153+
(handle-conflict handlers last-modified-time buffer out-dir
154+
{:lib lib, :path path, :in is, :existing out-file, :state state})
155+
156+
;; write new file, parent dir exists for writing
157+
(ensure-dir (.getParentFile out-file) out-file)
158+
(do
159+
(copy-stream! ^InputStream is (BufferedOutputStream. (FileOutputStream. out-file)) buffer)
160+
(Files/setLastModifiedTime (.toPath out-file) last-modified-time)
161+
state)
162+
163+
:parent-dir-is-a-file
164+
(throw (ex-info (format "Cannot write %s from %s as parent dir is a file from another lib. One of them must be excluded."
165+
path lib) {}))))
166+
141167
(defn- explode
142-
[^File lib-file lib {:keys [out-dir buffer exclude handlers]} state]
168+
[^File lib-file lib {:keys [out-dir buffer exclude handlers] :as context} state]
143169
(cond
144170
(not (.exists lib-file))
145171
state
@@ -151,34 +177,24 @@
151177
(let [path (.getName entry)
152178
;; should rarely happen (except /), but chop to make relative:
153179
path (if (str/starts-with? path "/") (subs path 1) path)
154-
out-file (jio/file out-dir path)
155-
parent-file (.getParentFile out-file)]
156-
(cond
157-
;; excluded or directory - do nothing
158-
(or (exclude-from-uber? exclude path) (.isDirectory entry))
159-
(recur the-state)
160-
161-
;; conflict, same file from multiple sources - handle
162-
(.exists out-file)
163-
(recur (handle-conflict handlers entry buffer out-dir
164-
{:lib lib, :path path, :in jis, :existing out-file, :state the-state}))
165-
166-
;; write new file, parent dir exists for writing
167-
(ensure-dir parent-file out-file)
168-
(do
169-
(copy-stream! ^InputStream jis (BufferedOutputStream. (FileOutputStream. out-file)) buffer)
170-
(Files/setLastModifiedTime (.toPath out-file) (.getLastModifiedTime entry))
171-
(recur the-state))
172-
173-
:parent-dir-is-a-file
174-
(throw (ex-info (format "Cannot write %s from %s as parent dir is a file from another lib. One of them must be excluded."
175-
path lib) {}))))
180+
out-file (jio/file out-dir path)]
181+
(recur
182+
(explode1 jis path (.isDirectory entry) (.getLastModifiedTime ^JarEntry entry)
183+
out-file lib context the-state)))
176184
the-state)))
177185

178186
(.isDirectory lib-file)
179-
(do
180-
(file/copy-contents lib-file out-dir)
181-
state)
187+
(let [source-dir (.getAbsoluteFile lib-file)
188+
source-path (.toPath source-dir)
189+
fs (file/collect-files source-dir :dirs true)]
190+
(loop [[^File f & restf] fs, the-state state]
191+
(if f
192+
(let [is (when (.isFile f) (jio/input-stream f))
193+
path (.toString (.relativize source-path (.toPath f)))
194+
source-time (FileTime/fromMillis (.lastModified f))
195+
out-file (jio/file out-dir path)]
196+
(recur restf (explode1 is path (.isDirectory f) source-time out-file lib context the-state)))
197+
the-state)))
182198

183199
:else
184200
(throw (ex-info (format "Unexpected lib file: %s" (.toString lib-file)) {}))))

src/test/clojure/clojure/tools/build/tasks/test_uber.clj

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,71 @@
136136
(slurp (project-path "j2/META-INF/LICENSE.txt")))
137137
(slurp (project-path "target/unzip/META-INF/LICENSE.txt"))))))
138138

139+
(deftest test-conflicts-but-files
140+
(with-test-dir "test-data/uber-conflict"
141+
(api/set-project-root! (.getAbsolutePath *test-dir*))
142+
143+
;; make dirs
144+
(doseq [j ["j1" "j2" "j3"]]
145+
(let [classes (format "target/%s/classes" j)]
146+
(api/copy-dir {:target-dir classes :src-dirs [j]})
147+
(spit (project-path (format "target/%s/classes/deps.edn" j)) "{:paths [\".\"]}")))
148+
149+
;; uber including j1, j2, j3 as local dirs
150+
(api/uber {:class-dir "target/classes"
151+
:basis (api/create-basis {:root nil
152+
:project {:deps {'dummy/j1 {:local/root "target/j1/classes"}
153+
'dummy/j2 {:local/root "target/j2/classes"}
154+
'dummy/j3 {:local/root "target/j3/classes"}}}})
155+
:uber-file "target/conflict.jar"
156+
:conflict-handlers {"ignore.txt" :ignore
157+
"overwrite.txt" :overwrite
158+
"append.txt" :append}})
159+
160+
;; unzip
161+
(api/unzip {:zip-file "target/conflict.jar" :target-dir "target/unzip"})
162+
163+
;; non-conflicting files are combined, conflicting files are reconciled
164+
(let [fs (map :name (zip/list-zip (project-path "target/conflict.jar")))]
165+
(is
166+
(set/subset?
167+
#{"META-INF/LICENSE.txt" "META-INF/MANIFEST.MF"
168+
"data_readers.clj"
169+
"my/j1.txt" "my/j2.txt"
170+
"ignore.txt" "overwrite.txt" "append.txt"}
171+
(set fs))))
172+
173+
;; data_readers.clj merge
174+
(is (= '{j1a my.foo/j1a-reader, j1b my.bar/j1b-reader,
175+
j2a my.foo/j2a-reader, j2b my.bar/j2b-reader}
176+
(read-string (slurp (project-path "target/unzip/data_readers.clj")))))
177+
178+
;; data_readers.cljc merge
179+
(is (= {'j1a (reader-conditional '(:cljs my.cljs.foo/j1a-reader :clj my.clj.foo/j1a-reader) false)
180+
'j1b (reader-conditional '(:cljs my.cljs.foo/j1b-reader :clj my.clj.foo/j1b-reader) false)
181+
'j2a (reader-conditional '(:cljs my.cljs.foo/j2a-reader :clj my.clj.foo/j2a-reader) false)
182+
'j2b (reader-conditional '(:cljs my.cljs.foo/j2b-reader :clj my.clj.foo/j2b-reader) false)}
183+
(read-string {:read-cond :preserve :features #{:clj}}
184+
(slurp (project-path "target/unzip/data_readers.cljc")))))
185+
186+
;; ignore files ignore, so first one wins
187+
(is (= (slurp (project-path "j1/ignore.txt"))
188+
(slurp (project-path "target/unzip/ignore.txt"))))
189+
190+
;; overwrite files overwrite, so last wins
191+
(is (= (slurp (project-path "j2/overwrite.txt"))
192+
(slurp (project-path "target/unzip/overwrite.txt"))))
193+
194+
;; append files append
195+
(is (= (str (slurp (project-path "j1/append.txt")) "\n"
196+
(slurp (project-path "j2/append.txt")))
197+
(slurp (project-path "target/unzip/append.txt"))))
198+
199+
;; LICENSE files append but no dupes - include j1 and j2, but not j3 (dupe of j1)
200+
(is (= (str (slurp (project-path "j1/META-INF/LICENSE.txt")) "\n"
201+
(slurp (project-path "j2/META-INF/LICENSE.txt")))
202+
(slurp (project-path "target/unzip/META-INF/LICENSE.txt"))))))
203+
139204
(deftest test-case-sensitive-dir-file-collision
140205
(with-test-dir "test-data/case-sensitive-collision"
141206
(api/set-project-root! (.getAbsolutePath *test-dir*))
@@ -175,6 +240,7 @@
175240

176241
(comment
177242
(test-conflicts)
243+
(test-conflicts-but-files)
178244
(test-case-sensitive-dir-file-collision)
179245
(run-tests)
180246
)

0 commit comments

Comments
 (0)