Search code examples
javamavenclojure

Importing/using a resource from an external clojar


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?

  1. Access a resource file from an external clojar
  2. Import the resource file into the current project, so that I can access it using io/resource

Solution

  • 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.