Traveling Ruby Documentation

repository·main·Indexed 24 days ago

https://github.com/phusion/traveling-ruby

Traveling Ruby provides self-contained, portable Ruby binaries for Linux, macOS, and Windows. It allows developers to distribute Ruby applications as single packages (tar.gz/zip) without requiring end-users to install Ruby or gems. The project includes build systems for Linux (using Docker and Rake) and macOS, support for native extensions on Unix-like systems, and guidance on reducing package size by removing unnecessary runtime files.

Tokens
8.6K
Snippets
20
Records
32
Agent score
74%

What's inside Traveling Ruby

  1. What is Traveling Ruby?

    main

    Traveling Ruby provides self-contained, portable Ruby binaries designed to run on any Linux distribution and any macOS machine. It also provides support for Windows.

    Developers use Traveling Ruby to bundle a precompiled Ruby interpreter and all necessary gems into a single tar.gz or zip package. This allows you to distribute Ruby applications to end users without requiring them to install Ruby or RubyGems themselves, avoiding version conflicts and installation errors.

  2. Understand Traveling Ruby caveats for Windows and Native Extensions

    main

    Before building, be aware of the following limitations:

    Native Extensions

    • Supported for Linux and macOS packages only.
    • Not supported for Windows packages.
    • Only a specific subset of popular native extension gems (and specific versions) are supported.

    Windows Support

    • Targeting Windows: You can create packages for Windows users.
    • Building on Windows: You cannot create packages on a Windows machine using the current documentation/tutorials, as they rely on standard Unix tools. You must use macOS or Linux to build Windows packages.
    • Ruby Version: Currently supports Ruby 2.4.10.
    • Native Extensions: Not supported for Windows.
  3. Understand the Traveling Ruby approach to portability

    main

    Traveling Ruby solves the problem of distributing portable Ruby binaries by providing a 'holy build box'—a tightly controlled build environment.

    Instead of using static linking (which is problematic for Ruby because it prevents the dynamic loading of Ruby extensions/shared libraries), Traveling Ruby uses a dynamic linking approach with specific safeguards:

    1. Old glibc symbols: The binaries are dynamically linked against the C library but use older symbol versions to ensure compatibility with older Linux systems.
    2. Bundled dependencies: It ships carefully-compiled versions of essential shared libraries (e.g., OpenSSL, ncurses, libedit) to prevent accidental linking to non-standard or version-mismatched libraries on the user's OS.

    This approach ensures that your application remains stable even if the user's OS version changes or if they have different versions of system libraries installed.

  4. Why bundle a Ruby interpreter instead of relying on system Ruby?

    main

    Even if a target platform (like macOS) ships with Ruby, you should bundle a Ruby interpreter with your application for the following reasons:

    • Version Consistency: Different OS versions ship different Ruby versions. Bundling ensures your app isn't broken by OS upgrades.
    • Language Syntax Compatibility: Significant changes in Ruby (such as keyword argument changes in Ruby 2.7+) can break code if the user's system Ruby is a different version than what you developed for.
    • Avoiding Installation Friction: Relying on system Ruby forces users to deal with complex installation methods (RVM, rbenv, apt-get, yum, etc.), PATH issues, and sudo permission conflicts with GEM_HOME.
    • Predictable Environment: Bundling avoids the 'command not found' errors and environment variable resets (like those caused by sudo or rvmsudo) that occur when users try to install dependencies via RubyGems on a system-wide Ruby.
  5. Build the Traveling Ruby builder Docker image

    main

    The build system uses a controlled environment provided by a Docker image (phusion/traveling-ruby-builder) to ensure compatibility across various Linux systems. This image uses a specific compiler toolchain and libraries based on Holy Build Box.

    You can build this Docker image locally by running rake image from within the linux/ directory.

    cd linux
    rake image
  6. Create a Windows wrapper script

    main

    Since Windows does not support Unix shell scripts (wrapper.sh), you must create a DOS batch file to act as the entry point for your application. This script sets the necessary Bundler environment variables and executes your application using the bundled Ruby interpreter.

    Create a file named packaging/wrapper.bat with the following content:

    @echo off
    
    :: Tell Bundler where the Gemfile and gems are.
    set "BUNDLE_GEMFILE=%~dp0\lib\vendor\Gemfile"
    set BUNDLE_IGNORE_CONFIG=
    
    :: Run the actual app using the bundled Ruby interpreter, with Bundler activated.
    @"%~dp0\lib\ruby\bin\ruby.bat" -rbundler/setup "%~dp0\lib\app\hello.rb"
  7. Manage conflicting paths during the build process

    main

    To prevent build environment pollution, certain paths must not exist during the build: ~/.bundle/config, /usr/local/include, and /usr/local/lib. Use the provided Rake tasks to temporarily rename these paths before building and restore them afterward.

    rake stash_conflicting_paths
    # ... run build ...
    rake unstash_conflicting_paths
  8. Create a wrapper script to execute bundled apps

    main

    A wrapper script is required to launch your application. It must perform three tasks:

    1. Determine its own location to resolve relative paths.
    2. Set the BUNDLE_GEMFILE environment variable to point to the Gemfile inside the package's lib/vendor/ directory.
    3. Execute the application using the bundled Ruby interpreter with bundler/setup required to activate the gem environment.

    Note: You must unset BUNDLE_IGNORE_CONFIG in the script if you previously set it in your environment to ensure the local .bundle/config is respected.

    #!/bin/bash
    set -e
    
    # Figure out where this script is located.
    SELFDIR="`dirname "$0"`"
    SELFDIR="`cd "$SELFDIR" && pwd`"
    
    # 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/hello.rb"
  9. Configure Rakefile for Windows packaging

    main

    To support Windows, you need to modify your Rakefile to handle .zip creation instead of .tar.gz and to download the correct Windows binaries.

    Follow these steps to update your Rakefile:

    1. Add a task to download Windows binaries: Add a file task that uses download_runtime("win32").

    2. Add the package:win32 task: Define a task within the package namespace that depends on :bundle_install and the downloaded runtime.

    3. Update create_package signature: Modify the create_package method to accept an os_type parameter (defaulting to :unix).

    4. Update wrapper copying logic: Use a conditional to copy wrapper.bat and rename it to hello.bat when os_type is not :unix.

    5. Update archive creation logic: Use a conditional to use the zip command instead of tar when targeting Windows.

    6. Update the main package task: Add 'package:win32' to the dependencies of the package task.

    # 1. Download task
    file "packaging/traveling-ruby-#{TRAVELING_RUBY_VERSION}-win32.tar.gz" do
      download_runtime("win32")
    end
    
    # 2. Package task
    namespace :package do
      desc "Package your app for Windows x86"
      task :win32 => [:bundle_install, "packaging/traveling-ruby-#{TRAVELING_RUBY_VERSION}-win32.tar.gz"] do
        create_package("win32", :windows)
      end
    end
    
    # 3. Updated method signature
    def create_package(target, os_type = :unix)
      # ... logic ...
    end
    
    # 4. Conditional wrapper copying
    if os_type == :unix
      sh "cp packaging/wrapper.sh #{package_dir}/hello"
    else
      sh "cp packaging/wrapper.bat #{package_dir}/hello.bat"
    end
    
    # 5. Conditional archiving
    if os_type == :unix
      sh "tar -czf #{package_dir}.tar.gz #{package_dir}"
    else
      sh "zip -9r #{package_dir}.zip #{package_dir}"
    end
    
    # 6. Update main task
    task :package => ['package:linux:x86', 'package:linux:x86_64', 'package:osx', 'package:win32']
  10. Build Traveling Ruby binaries

    main

    The Traveling Ruby project provides the binaries. Application developers typically only need to use the internal build systems if they are:

    • Contributing to the Traveling Ruby project.
    • Attempting to reproduce the provided binaries.
    • Wanting to customize the binaries.

    Build system documentation:

    • Linux: See linux/README.md
    • macOS: See osx/README.md