Ruby to EXE – Turn ruby scripts into portable executable apps

This method works with:

  1. Rails apps (your portable app will be a 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 developed a gem that automates this process. Check it out:
https://github.com/loureirorg/rb2exe

Example:

gem install rb2exe

echo "puts 'Hello world'" > test.rb
rb2exe test.rb
./test

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

$ mkdir hello
$ cd hello
$ echo "puts 'Hello world'" > test.rb
$ ruby test.rb
Hello World

Part I: Standalone Ruby

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

$ echo $RUBY_VERSION
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
.
├── .package
│   └── payload
│       └── lib
│           ├── app
│           │   └── test.rb
│           └── ruby
└── 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
/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

The above 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 this 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
Self-extract file created

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

Close Menu