问题
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:
- Idiomatic Clojure to copy resources from running jar to outside
- How to copy file inside jar to outside the jar?
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?
- Access a resource file from an external clojar
- Import the resource file into the current project, so that I can access it using
io/resource
回答1:
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
.
回答2:
(io/resource "fluid-r3.sf2")
is a url, not a file. You can slurp
it if you want it in memory all at once, or read it as a stream with the java.net.URL
api (and write it to a file as you read it if you want to).
Example:
user> (type (clojure.java.io/resource "text.txt"))
java.net.URL
user> (slurp (clojure.java.io/resource "text.txt"))
"this is a text file\n"
回答3:
"Access a resource file from an external clojar"
I found this solution from this article. Dmitri Sotnikov did this at his cryogen project.
source code is here. The load-plugins
functions will retrieve all the plugin.edn
from under every resources directory of external clojars.
(ns cryogen-core.plugins
(:require [clojure.edn :as edn]
[clojure.string :as string]
[text-decoration.core :refer :all]))
(defn load-plugin [^java.net.URL url]
(let [{:keys [description init]} (edn/read-string (slurp url))]
(println (green (str "loading module: " description)))
(-> init str (string/split #"/") first symbol require)
((resolve init))))
(defn load-plugins []
(let [plugins (.getResources (ClassLoader/getSystemClassLoader) "plugin.edn")]
(doseq [plugin (enumeration-seq plugins)]
(load-plugin (. ^java.net.URL plugin openStream)))))
If you do not like too much java interoperation, there is a resources
function in pomegranate library which can retrieve all the resources with given name in external clojars.
来源:https://stackoverflow.com/questions/30857749/importing-using-a-resource-from-an-external-clojar