What I'm trying to do is package a large file (a MIDI soundfont) in a standalone Maven repo/clojar, and then be able to pull it down programmatically and use it from a separate project. This seemingly simple task is proving to be more complicated than I expected.
What would be ideal is if there were a way to access these resources directly, or expose them as public vars, or something. This is the first thing I tried -- I did something like this:
(ns midi.soundfont.fluid-r3
(:require [clojure.java.io :as io]))
(def sf2
(io/file (io/resource "fluid-r3.sf2")))
However, the problem that I'm running into is that io/resource
only finds resource files on the current class path. As soon as I try to require this namespace from another project (or from the REPL), I get:
java.lang.IllegalArgumentException: Not a file: jar:file:/Users/dave/.m2/repository/midi/soundfont/fluid-r3/midi.soundfont.fluid-r3/0.1.0/midi.soundfont.fluid-r3-0.1.0.jar!/fluid-r3.sf2
If it's not possible to access the resource directly, I would be happy with a solution that involves copying the file to some path in the filesystem. I did try this as well, but I ran into the same problem when trying to run the "copy the file to the filesystem" method from a different project -- io/resource
still failed to locate the file because it's not on the current classpath.
I have found similar questions that have been asked on SO previously, such as:
However these solutions only seem to pertain to copying a file that is a resource in the current (running) project.
Is it possible to do one of these two things?
io/resource
As dbasch correctly explained, io/resource
returns a URL, not a file. But why you are being able to open that URL with
io/file
on the REPL or lein run
but not from the jar? That's because the URL in the first case points to the plain file
in the filesystem, while the URL when running with the jar points to the resource inside the jar, so it's not a proper file.
I made an example in this github repo. I'll copy the -main
code here for reference:
(defn -main [& args]
(let [r (io/resource "greet")]
(println r)
(println (slurp r))
(with-open [rdr (io/reader r)]
(println (clojure.string/join ", " (line-seq rdr))))
(println (io/file r))))
Running with lein run
shows:
› lein run
#<URL file:/home/nicolas/projects/clojure/resources/resources/greet>
hello
world
hello, world
#<File /home/nicolas/projects/clojure/resources/resources/greet>
Running the uberjar shows:
› java -jar target/resources-0.1.0-SNAPSHOT-standalone.jar
#<URL jar:file:/home/nicolas/projects/clojure/resources/target/resources-0.1.0-SNAPSHOT-standalone.jar!/greet>
hello
world
hello, world
Exception in thread "main" java.lang.IllegalArgumentException: Not a file: jar:file:/home/nicolas/projects/clojure/resources/target/resources-0.1.0-SNAPSHOT-standalone.jar!/greet
at clojure.java.io$fn__8588.invoke(io.clj:63)
at clojure.java.io$fn__8572$G__8556__8577.invoke(io.clj:35)
See the difference between #<URL file:/home/nico...
and #<URL jar:file:/home/nico...
, that explains why you can't call (io/file)
on it, but you can read it with slurp
or create a reader with io/reader
.