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
Install warbler
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
.rb
file 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
project1
- you have a Ruby file under
lib
calledwa_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
bin
calledmain_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 project1
since bin
and 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"
...
Note:
- Since you compiled it, you now run the program with
java
notjruby
– 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/lib
and project1/bin
paths. You can also see that the packager adds main.rb
and init.rb
to ensure that everything works.
Good! Progress!
Fine-tuning things a bit – Compiled Classes
Currently, your Ruby code is packaged as Ruby code in the project1/bin
and 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-9.2.13.0/lib/ruby/gems/shared/gems/jruby-jars-9.2.16.0/lib/jruby-core-9.2.16.0-complete.jar";
"C:/jruby-9.2.13.0/lib/ruby/gems/shared/gems/jruby-jars-9.2.16.0/lib/jruby-stdlib-9.2.16.0.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
TEMP
directory. 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.
Finally, warbler
gives you flexibility to use Gemfile
or 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.