You can make a single executable from your gem or even a Rails application. I just tried ruby-packer and it works as promised.
One of the things that I missed when writing a command line tool in Ruby was making a binary that is easy to distribute. Since Ruby is an interpreter we cannot just make a binary.
However, there are ways how to package Ruby interpreter and all the required gems together with your program as one distribution. For one there was Traveling Ruby project. Another good option is to look at how Vagrant is packaged (I did that when we were bringing Vagrant natively for Fedora back in the day).
Neither approach felt quite right thought and the best way to distribute Ruby programs was to build them as RPM or DEB packages. That’s of course a lot of work and you end up with only one platform which is unfortunately not all that suitable for quick afternoon hacking.
But then ruby-packer appeared on my radar and it got my hopes high! Today I finally had some time and tried to make a single executable for InvoicePrinter. Here is how it went.
Installation
First we need to install the prerequisites. The main one is SquashFS, a compressed read-only file system for Linux, the core idea behind ruby-packer. On Fedora we install squashfs-tools
package:
$ sudo dnf install squashfs-tools
Then we need a C compiler, GNU Make and Ruby. If you are on Fedora like me and still don’t have those follow Fedora Developer instructions.
Once done I locally fetched ruby-packer as mentioned in docs:
$ curl -L http://enclose.io/rubyc/rubyc-linux-x64.gz | gunzip > rubyc
$ chmod +x rubyc
$ ./rubyc --help
Building the executable
Everything went smoothly so without further ado let’s build InvoicePrinter as single Ruby executable:
$ ./rubyc --gem=invoice_printer --gem-version=1.2.0 invoice_printer --output invoice_printer
We are calling rubyc
in a RubyGems mode by providing a gem name (--gem
) and version (--gem-version
), executable entry point and output.
After a while it produced invoice_printer
executable (a.out
in case the output name is not provided) and I was ready to give it a spin.
$ ./invoice_printer --help
Usage: invoice_printer [options]
Options:
-l, --labels labels as JSON
-d, --document document as JSON
-s, --stamp path to stamp
--logo path to logotype
--font path to font
--page-size letter or a4 (letter is the default)
-f, --filename output path
-r, --render directly render PDF stream (filename option will be ignored)
Works just as the gem version!
I am really pleasantly surprised. I now have 26.3 MB big working executable that I can distribute. The only small issue is that we have just one entry point, but InvoicePrinter comes with two (command line and server).
To that end we have to create a separate program for InvoicePrinter Server:
$ ./rubyc --gem=invoice_printer --gem-version=1.2.0 invoice_printer_server --output invoice_printer_server
Conclusion? I can offer executables for my gems to make it easy to use for folks not having a Ruby runtime around. And it all took me just 10 minutes. ruby-packer surely needs a closer look.