This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-12-01
Channels
- # adventofcode (170)
- # announcements (3)
- # babashka (1)
- # beginners (25)
- # cherry (1)
- # cider (3)
- # clj-kondo (5)
- # cljsrn (9)
- # clojure (27)
- # clojure-art (2)
- # clojure-europe (11)
- # clojure-nl (1)
- # clojure-norway (26)
- # clojure-sweden (2)
- # clojure-uk (5)
- # code-reviews (12)
- # component (8)
- # conjure (1)
- # data-science (2)
- # hyperfiddle (6)
- # malli (5)
- # off-topic (65)
- # overtone (34)
- # polylith (3)
- # re-frame (2)
- # reagent (2)
- # releases (3)
- # rum (1)
- # shadow-cljs (2)
- # slack-help (8)
- # sql (8)
- # squint (100)
- # thejaloniki (3)
- # tools-build (16)
- # vim (7)
- # yamlscript (1)
I have a pretty simple wrapper over clojure.tools.build.api/uber
which serves to build an uberjar in my project.
The trouble I'm facing is in the presence of file conflicts, it fails with java.io.IOException: Stream closed
I've found (I think) a similar issue here: https://github.com/healthfinch/depstar/issues/4
However, not sure what to make out of it.
Sean said:
> In my fork, I wrapped the .getNextJarEntry
call in (try ... (catch Exception _))
and everything seems to work just fine...
That doesn't seem right since it I think that would interrupt the process in the middle of uberjar packaging and could skip a lot of files.
This is my code
(let [{:keys [basis target-dir]} (compile/compile-clj options)
uber-file (str project-prefix "target/" uberjar-name)]
(printf "Creating uberjar '%s' with target dir '%s'.\n" uber-file target-dir)
(b/uber {:basis basis
:class-dir target-dir
:conflict-handlers {"^duct_hierarchy\\.edn$"
(fn [{:keys [path in existing] :as _params}]
(printf "Found conflicting file path '%s' with existing file '%s'. Merging...\n" path existing)
(let [existing-content (edn/read-string (slurp existing))
new-content (edn/read-string (slurp in))]
{:write {path {:string (with-out-str (pprint/pprint (merge existing-content
new-content)))}}}))}
:main main
:uber-file uber-file}))
It seems that the merge is done but almost immediately after that I get the error
java.io.IOException: Stream closed
at java.base/java.util.zip.ZipInputStream.ensureOpen(ZipInputStream.java:70)
at java.base/java.util.zip.ZipInputStream.getNextEntry(ZipInputStream.java:119)
at java.base/java.util.jar.JarInputStream.getNextEntry(JarInputStream.java:129)
at java.base/java.util.jar.JarInputStream.getNextJarEntry(JarInputStream.java:166)
at clojure.tools.build.tasks.uber$explode.invokeStatic(uber.clj:178)
at clojure.tools.build.tasks.uber$explode.invoke(uber.clj:169)
at clojure.tools.build.tasks.uber$uber$fn__32890$fn__32894.invoke(uber.clj:286)
at clojure.lang.PersistentVector.reduce(PersistentVector.java:343)
at clojure.core$reduce.invokeStatic(core.clj:6885)
at clojure.core$reduce.invoke(core.clj:6868)
at clojure.tools.build.tasks.uber$uber$fn__32890.invoke(uber.clj:285)
at clojure.core.protocols$iter_reduce.invokeStatic(protocols.clj:49)
at clojure.core.protocols$fn__8230.invokeStatic(protocols.clj:75)
at clojure.core.protocols$fn__8230.invoke(protocols.clj:75)
at clojure.core.protocols$fn__8178$G__8173__8191.invoke(protocols.clj:13)
at clojure.core$reduce.invokeStatic(core.clj:6886)
at clojure.core$reduce.invoke(core.clj:6868)
at clojure.tools.build.tasks.uber$uber.invokeStatic(uber.clj:283)
at clojure.tools.build.tasks.uber$uber.invoke(uber.clj:270)
at clojure.lang.Var.invoke(Var.java:384)
at clojure.tools.build.api$uber.invokeStatic(api.clj:502)
at clojure.tools.build.api$uber.invoke(api.clj:430)
...
Found conflicting file path 'duct_hierarchy.edn' with existing file '/var/folders/hn/tgwyrdmj1tb5pmmbdkd1g_qc0000gn/T/uber5850277775875523123/duct_hierarchy.edn'. Merging...%
In fact, as you can see, my message is printed only after the stacktrace.
Is there maybe some laziness in place?Maybe it doesn't have to do anything with my conflict handler? I now try to build the uberjar again and getting just the exception and not my logging message "Found conflicting file..."
I found the problem.
It's the usage of (slurp in)
in my conflict handler.
That closes the input stream.
I ended up copying these two functions and using stream->string
instead in my conflict handler:
(defn- copy-stream!
"Copy input stream to output stream using buffer.
Caller is responsible for passing buffered streams and closing streams."
[^InputStream is ^OutputStream os ^bytes buffer]
(loop []
(let [size (.read is buffer)]
(if (pos? size)
(do
(.write os buffer 0 size)
(recur))
(.close os)))))
(defn- stream->string
[^InputStream is]
(let [baos (ByteArrayOutputStream. 4096)
_ (copy-stream! is baos (byte-array 4096))]
(.toString baos "UTF-8")))
the exception means that the stream has been closed
yeah, you got it
Yes 🙂
I guess my problem was that I didn't really understood what's in
.
And I'm not sure I still get it.
I thought it's just the conflicting file, not the JAR itself.
https://clojure.github.io/tools.build/clojure.tools.build.api.html#var-uber defines this
Conflict handler signature (fn [params]) => effect-map:
params:
:path - String, path in uber jar, matched by regex
:in - InputStream to incoming file (see stream->string if needed)
:existing - File, existing File at path
:lib - symbol, lib source for incoming conflict
:state - map, available for retaining state during uberjar process
it's an InputStream into the incoming file (here from a jar entry)
if it would be helpful, could probably make those helpers public
And why this wasn't obvious to me is because it's "incoming file" but from a JAR entry (as you said). That it's a JAR is crucial, because then it explains why it cannot be closed Also I should have read the javadoc (https://docs.oracle.com/javase/8/docs/api/java/util/jar/JarInputStream.html#getNextJarEntry--) to understand why stream->string (or slurp) actually only returns the content of the file and not the whole JAR file
I've logged it for the next time I do a pass on tbuild https://clojure.atlassian.net/browse/CLJ-2814
oops that was in the wrong project