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:
java
is in the path.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?
If you really want to do it, you can uuencode the jar file (or any other binary file) in a bash executable, see this for an example: Embed a Executable Binary in a shell script
It sounds like you're just trying to bootstrap your app from a script using cleaner syntax. This might be most easily done like so:
create a new bash script called myprogram
:
#!/usr/bin/bash
# pass whatever command line args you have down through the script
java -jar myjar.jar
give it execute permissions
chmod +x myprogram
run it
./myprogram (with whatever params)
If you want to get rid of the ./
you'll need to move things around so the script gets picked up by your PATH
.
Keep in mind that you're not creating a platform specific binary executable. Doing so would pretty much defeat the purpose of using the jvm in the first place. You'd just be invoking it through an extra layer of indirection.
Now that GraalVM has matured a little bit, you may want to consider compiling your application to a binary executable.
There's a good tutorial of how to accomplish that here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With