A common question in the Ruby world (especially from newbies) is to wonder how to produce a stand-alone executable that can be distributed. Ruby is inherently an interpreted language and currently there are a few ways to create an executable but none that are supported out of the box. One method to achieve this is to use JRuby and a tool called warbler to package your code into a Java Archive (JAR) file that can be run on any system where you have Java (i.e., a Java Virtual Machine set up). Let’s see how to do this for a Ruby project.
The ‘war’ in warbler comes from Web Archive and developers using JRuby for web deployments to a Java server will already be familiar with it. That said, it can also be used to package code into an executable JAR file that can be run on a desktop computer where you have Java available.
If you stumbled on this post before seeing other things, here is some background. JRuby is a Java implementation of Ruby that runs atop the Java Virtual Machine. We have previously covered how to install and run JRuby on Windows. We also saw the commands for running and installing gems, bundler, irb and so on. In the following post, we covered running scripts using JRuby. Take a look at either post again for the basics, if you need a refresher.
Now, let’s get started. In this post, we will look at the following:
- Install the warbler gem into your JRuby setup
- Organise your Ruby project – set up the directories correctly for packaging your code
- Call warbler to create the JAR
- Run the packaged JAR
- As always, notes and cautions
Open a command line terminal and switch to JRuby. If you’re using
pik, you will need to do something like
pik 921 to select the correct setup. The next thing to do is to call the
gem command to install
warbler. Remember that you start system commands as
jruby -S [COMMAND], so we do:
$ jruby -S gem install warbler Fetching warbler-2.0.5.gem Successfully installed warbler-2.0.5 Parsing documentation for warbler-2.0.5 Installing ri documentation for warbler-2.0.5 Done installing documentation for warbler after 7 seconds 1 gem installed
Note: you might see more things getting installed (such as jruby-jars) if this is the first time you install
warbler – that’s normal. Don’t worry about it.
To check if it’s installed, you can do
jruby -S warble -T and you should see a list of commands that it allows you to run. Note you run
warble (the executable) even though you installed
warbler (with an ‘r’).
$ jruby -S warble -T warble compiled # Feature: precompile all Ruby files warble config # Generate a configuration file to customize your archive warble executable # Feature: make an executable archive (runnable + an emb... warble gemjar # Feature: package gem repository inside a jar warble jar # Create the project jar file warble jar:clean # Remove the project jar file warble jar:debug # Dump diagnostic information warble pluginize # Install Warbler tasks in your Rails application warble runnable # Feature: make a runnable archive (e.g warble version # Display version of Warbler
Good! Things look fine here.
Organising your Ruby code
For smooth running of Warbler, it’s best if your project follows some basic conventions. It should be in a directory of its own with the following sub-directories minimally:
- lib – where all your Ruby code is
- bin – where you have a
.rbfile that acts as the entry point for your application
Note that Warbler will package all the directories that are in this directory – so, don’t have things you don’t need. I had a
tmp directory here with about 13MB of files which all got packaged into the first JAR I created! 🤦♂️
This setup is really important. Once you have a few files in
lib and an entry script in
bin, you are ready to run Warbler. We will come back to this a little bit again as we start running Warbler and see what warnings it shows.
For simplicity, let’s assume that you are set up this way:
- the project directory is called
- you have a Ruby file under
wa_parser.rb– it can be anything (I’m using a simple script that I wrote to parse a WhatsApp conversation export)
- you have an entry script in
main_file.rb– this accepts command line arguments and requires the parser and runs it
Your tree will probably look like this:
D:. ├───bin │ main_file.rb │ └───lib wa_parser.rb
Before you proceed, you should try to run your ruby script using
jruby bin\main_file so that you know it works!
Package using warbler
Once you have come this far, you are ready to run the command! The simple command is to produce a runnable JAR – an executable. You run this command at the base of the directory that you want to package (in our case, we run it from
lib are under this path).
$ jruby -S warble runnable jar No executable matching config.jar_name found, using bin/main_file.rb rm -f project1.jar Creating project1.jar $ dir *.jar 2021-03-13 10:13 AM 25,272,637 project1.jar
OK, that happened quickly! Let’s note a couple of things:
- The output file name of the JAR matches the name of the directory (project1.jar in our case)
- It’s 25MB large (we’ll see why in a bit)
- We saw a warning: No executable matching config.jar_name found, using bin/main_file.rb
Anyway, you should now be able to run it!
Running your Program
To run your compiled Java program, you now just need to do
java -jar project1.jar with any command line arguments that your script needs!
$ java -jar ..\project1\project1.jar "WhatsApp Chat.txt" ...
- Since you compiled it, you now run the program with
jruby– that’s why you can now send the JAR to anyone who has Java installed (and they don’t need to have JRuby)
- Also, the reason, they don’t need to have JRuby is because it’s packaged into the JAR you just created.
If you open the JAR file with an unzip utility (e.g. 7-zip), this is what you will see inside it:
As you can see, the
META-INF/lib path includes the JRuby-core and JRuby-Stdlib JAR files so that you are able to distribute without other files. Your own code is included inside
project1/bin paths. You can also see that the packager adds
init.rb to ensure that everything works.
Fine-tuning things a bit – Compiled Classes
Currently, your Ruby code is packaged as Ruby code in the
project1/lib directories. If you want, you can compile these files into JAVA classes ‘ahead of time’ by passing the
compiled task into the creation. You need to do this:
Note: It’s important that you pass all the commands (‘compiled’, ‘runnable’, ‘jar’) to the same invocation. If you don’t do this, it won’t work. The documentation on the Warbler page explains this.
$ jruby -S warble compiled runnable jar java -classpath "C:/jruby-220.127.116.11/lib/ruby/gems/shared/gems/jruby-jars-18.104.22.168/lib/jruby-core-22.214.171.124-complete.jar"; "C:/jruby-126.96.36.199/lib/ruby/gems/shared/gems/jruby-jars-188.8.131.52/lib/jruby-stdlib-184.108.40.206.jar" org.jruby.Main -S jrubyc "./bin/main_file.rb" "./lib/wa_parser.rb" No executable matching config.jar_name found, using bin/main_file.class rm -f project1.jar Creating project1.jar rm -f ./bin/main_file.class ./lib/wa_parser.class
We see a bit more happening now since it needs to compile before it packages the JAR. Take a look at the final packaged JAR now. You’ll notice that your Ruby files have become 35 byte stub code and there is an additional
.class file for each Ruby file that you had.
Fine-tuning things a bit – No Matching Jar Name
If you want to handle this warning that we saw:
No executable matching config.jar_name found, using bin/main_file.class the super simple way is to rename the entry point in the
bin folder to have the same name as the directory (and therefore the final JAR). If we rename our file to
project1.rb and then run the
warble command again, it doesn’t complain.
<coode>$ jruby -S warble runnable jar No executable matching config.jar_name found, using bin/main_file.rb rm -f project1.jar Creating project1.jar $ ren bin\main_file.rb project1.rb $ jruby -S warble runnable jar rm -f project1.jar Creating project1.jar
Nice! There is more we can do with configuration, but we are not handling that in this introductory post.
Final Notes and cautions
In the ways described here, you can package a simple Ruby script into a JAR file and distribute to places that have Java and not Ruby. Also, it allows you to compile the Ruby source code if needed.
There are a couple of things to be aware of:
- The size of the distribution is around 25MB because it includes the JRuby dependency
- The startup is slightly slow but for long running programs, that should not be a very big concern
- When it runs, it unzips and creates a folder and a file in the
TEMPdirectory. These are not removed automatically. The picture below shows you the files. I have asked on Twitter to find out more and will update this post with the information I find. If this is expected, just be sure that if you’re running a few jobs automatically every minute on a schedule, you will add approximately 25MB – 30MB to your TEMP directory each time… over time, you will need to maintain and clean up the directory.
warbler gives you flexibility to use
Jarfile and even gemspec. It also lets you have additional information through configuration. I will probably cover those in a separate post.
With that, we come to the end of this post. I hope you find it useful. If you have any other information to add, please do so below.