Ruby to EXE – Turn ruby scripts into portable executable apps

This method works with:

  1. Rails apps (your executable file will be a portable webserver);
  2. Scripts with gems;
  3. Multiple source file scripts;
  4. Simple, single-source file scripts;

The final app is a single executable file that can run on systems without a ruby interpreter installed.


UPDATE:

I have developed a gem that automates this process. Check it out:
https://github.com/loureirorg/rb2exe

Usage:


gem install rb2exe
(out)
echo "puts 'Hello world'" > test.rb
rb2exe test.rb
./test

Now, how this works, step-by-step

1. First, let’s create a simple Hello World app:


mkdir hello
cd hello
echo "puts 'Hello world'" > test.rb
ruby test.rb
(out)Hello World

The way this project is transformed into a self-contained executable file is by using Ruby Traveler. Ruby Traveler is just a folder with a standalone “ruby” executable. The idea is to add Ruby Traveler’s folder and binaries into your project and compress everything into a single self-extractable zip file. This self-extractable file extracts the compressed data into a temporary folder and executes the main ruby script (ex. “test.rb”).

As this is based on Ruby Traveler (by Phusion), which seems to be a stalled project, we are locked to the only versions it supports – the latest one is 2.2.2.

If you want to use a different Ruby version, other than 2.2.2, I recommend adapting this script to zw693’s Traveling Ruby, which supports ALL Ruby versions:
https://github.com/zw963/traveling-ruby


Part I: Standalone Ruby

2. Your ruby version should be 2.2.2, 64 bits:


echo $RUBY_VERSION
(out)ruby-2.2.2

If your version is different, please install the 2.2.2 (e.g. rvm use 2.2.2).
This will NOT work with 2.2.0, 2.2.3, 2.1, etc.

3. Duplicate the project folder, naming it as “app”:


cd ..
cp -r -pa hello app

4. In the project folder, create a “.package/payload/lib/ruby” sub-folder:


cd hello
mkdir -p .package/payload/lib/ruby

5. Move the “app” folder to “.package/payload/lib”:


mv ../app .package/payload/lib
tree -a
(out).
(out)├── .package
(out)│   └── payload
(out)│       └── lib
(out)│           ├── app
(out)│           │   └── test.rb
(out)│           └── ruby
(out)└── test.rb

6. Download ruby traveler 2.2.2 64 bits (5.6M), and unzip it on “.package/payload/lib/ruby”:


cd .package/payload/lib/ruby
wget http://d6r77u77i8pq3.cloudfront.net/releases/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz
tar -xf traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz
rm traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz

7. Go back to “.package/payload/”:


cd ../..
pwd
(out)/home/user/projects/hello/.package/payload

8. Create a wrapper script (name it as “installer”):


nano installer

#!/bin/bash
set -e

# Figure out where this script is located.
SELFDIR="`dirname \"$0\"`"
SELFDIR="`cd \"$SELFDIR\" && pwd`"

## GEMFILE
if [ -f "$SELFDIR/lib/vendor/Gemfile" ]
then
  # Tell Bundler where the Gemfile and gems are.
  export BUNDLE_GEMFILE="$SELFDIR/lib/vendor/Gemfile"
  unset BUNDLE_IGNORE_CONFIG

  # Run the actual app using the bundled Ruby interpreter, with Bundler activated.
  exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/test.rb"
else
  exec "$SELFDIR/lib/ruby/bin/ruby" "$SELFDIR/lib/app/test.rb"
fi

chmod +x installer

Replace the “exec” line with your actual command to start the application. Eg. for Rails apps, it should be:
RAILS_ENV=production exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/bin/rails" server

PS: The previous script is based on the “traveling ruby” tutorial.


Part II: Gemfile

If your project has a Gemfile, you need to follow these extra steps:

9. Create a “tmp” folder on “.package/payload/lib”:


cd lib
mkdir tmp
cd tmp

10. Copy the project Gemfile to tmp:


cp ../app/Gemfile* .

11. Download gems into the “lib/vendor” folder:


BUNDLE_IGNORE_CONFIG=1 bundle install --path ../vendor --without development

12. Delete tmp folder:


cd ..
rm -Rf tmp

13. [Optional] Delete gem’s cache in the vendor folder:


rm -f vendor/*/*/cache/*

14. Copy the Gemfile to the vendor folder:


cp app/Gemfile* vendor/

15. Create a bundler config


mkdir vendor/.bundle/
cd vendor/.bundle/
nano config

BUNDLE_PATH: .
BUNDLE_WITHOUT: development
BUNDLE_DISABLE_SHARED_GEMS: '1'

Part III: Pack everything as a single self-extract file

This part is based on Jeff Parent’s article.

17. Create a script to decompress everything


#!/bin/bash
export TMPDIR=`mktemp -d /tmp/selfextract.XXXXXX`

ARCHIVE=`awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' $0`

tail -n+$ARCHIVE $0 | tar -xz -C $TMPDIR

CDIR=`pwd`
cd $TMPDIR
./installer

cd $CDIR
rm -rf $TMPDIR

exit 0

__ARCHIVE_BELOW__

18. Package builder


#!/bin/bash
cd payload
tar cf ../payload.tar ./*
cd ..

if [ -e "payload.tar" ]; then
    gzip payload.tar

    if [ -e "payload.tar.gz" ]; then
        cat decompress payload.tar.gz > output
    else
        echo "payload.tar.gz does not exist"
        exit 1
    fi
else
    echo "payload.tar does not exist"
    exit 1
fi

chmod +x output
echo "Self-extract file created"
exit 0

chmod +x decompress
chmod +x build
./build
(out)Self-extract file created

And that’s it. You can now rename and distribute the generated “output” file :)

Close Menu