I have written a program in Clojure and I'd like to execute it on the command-line without specifically invoking java on the command-line (such as java -jar). I want a single executable file, such as myprogram, that accepts any arguments and runs my program. Here are a couple things that might make this easier:
- It's OK to assume Java is installed and that
javais in the path. - Although a solution that works on Windows would be a great plus, you can assume that this is all being done on a UNIX-like operating system such as Mac OS X or Ubuntu.
- It's OK to invoke Java in a script of some kind.
- It's OK to use some other language, such as Ruby, Python or Perl, which the user may or may not have installed. All-bash would be cool because I can assume people have that.
- It's OK if I have to use some kind of tool to build a binary that will execute, but I am not looking for a .app or .exe file that expects to operate with a GUI interface (so for example, Oracle's appbundler is not what I'm looking for here).
I have gotten pretty far down this path with one approach, but I have to wonder if there's a better way.
FOR REFERENCE ONLY: What I've Tried Already
I'll describe my approach below, but any answers don't need to follow this approach at all.
What I have done is create a lein plugin called makescript that generates an uberjar, base64 encodes it, and places it inside a Ruby script in a so-called heredoc variable, like this:
# ...ruby script...
BASE64_JAR = <<-JAR_BOUNDARY
# [...base64 encoded file here...]
JAR_BOUNDARY
You should then be able to run the ruby script. It will take the BASE64_JAR variable, unencode it, place it in a temporary file, and execute it by invoking java -jar <filename>.
The problem I'm having with this approach is that Ruby's base64 library and Clojure's clojure.data.codec.base64 libraries seem to be producing different strings to represent the jar, and a string encoded by Clojure does not decode to the original file if I use Ruby. This may have something to do with the encoding of the strings themselves (UTF-8-related? maybe) between the two languages. Here are repl/irb sessions that illustrate the disconnect:
repl=> (def jar-contents (slurp "../target/myproj-0.1.0-SNAPSHOT-002-standalone.jar"))
repl=> (count jar-contents) ;; => 9433328
repl=> (def a-str (String. (clojure.data.codec.base64/encode (.getBytes jar-contents)) "UTF-8"))
repl=> (count a-str) ;; => 23265576
irb> f = File.open("target/pwgen-0.1.0-SNAPSHOT-002-standalone.jar", "r").read()
irb> p f.length # => 9657639
irb> b = Base64.encode64(f)
irb> p b.length # => 13564973
Note the raw sizes are close but not the same, but the encoded versions are much different.
Although this is puzzling and I'd like to know why this happens, I think I can bypass the problem by having makescript just generate the uberjar and pass the path to another Ruby script, which then will base64 encode (and later decode, also using Ruby) the standalone JAR. The question still stands: is there a better, simpler way? Am I missing something obvious, or is this really as hard as it seems?