Mike Slinn
Mike Slinn

Debugging Rackup Webapps With Visual Studio Code

Published 2023-01-31.
Time to read: 3 minutes.

This page is part of the ruby collection, categorized under Ruby, Sinatra, e-commerce, nginx.

Rackup provides a command line interface for running rack-compatible web applications. Two well-known application servers that adhere to the rack standard are Ruby on Rails and Sinatra.

The rack gem is a dependency of the rackup gem.

In order to debug rack-compatible webapps in Visual Studio Code, you need to somehow launch the Ruby debugger on the rackup gem in such a way that all the other dependencies (also gems) are loaded.

Surely, reliable debugging must be an oxymoron,
😁
like awfully good, same difference, and original copy.

All the advice I could find on the Interwebs discussed a brittle approach, which would break every time a different version of the rackup gem was required. This blog post discusses how to use a manually created binstub for reliable debugging.

What is Rackup?

The rackup command is a short bash script that loads and executes a gem of the same name. The bash script is actually packaged within the gem, and is deployed when the gem is installed.

rackup
#!/usr/bin/env bash
set -e
[ -n "$RBENV_DEBUG" ] && set -x

program="${0##*/}"
if [ "$program" = "ruby" ]; then
  for arg; do
    case "$arg" in
    -e* | -- ) break ;;
    */* )
      if [ -f "$arg" ]; then
        export RBENV_DIR="${arg%/*}"
        break
      fi
      ;;
    esac
  done
fi

export RBENV_ROOT="/home/mslinn/.rbenv"
exec "/usr/lib/rbenv/libexec/rbenv" exec "$program" "$@"

This is the rackup help message:

Shell
$ rackup -h
Usage: rackup [ruby options] [rack options] [rackup config]
Ruby options: -e, --eval LINE evaluate a LINE of code -d, --debug set debugging flags (set $DEBUG to true) -w, --warn turn warnings on for your script -q, --quiet turn off logging -I, --include PATH specify $LOAD_PATH (may be used more than once) -r, --require LIBRARY require the library, before executing your script
Rack options: -b BUILDER_LINE, evaluate a BUILDER_LINE of code as a builder script --builder -s, --server SERVER serve using SERVER (thin/puma/webrick) -o, --host HOST listen on HOST (default: localhost) -p, --port PORT use PORT (default: 9292) -O NAME[=VALUE], pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '$HOME/.rbenv/versions/3.1.0/bin/rackup -s SERVER -h' to get a list of options for SERVER --option -E, --env ENVIRONMENT use ENVIRONMENT for defaults (default: development) -D, --daemonize run daemonized in the background -P, --pid FILE file to store PID
Profiling options: --heap HEAPFILE Build the application, then dump the heap to HEAPFILE --profile PROFILE Dump CPU or Memory profile to PROFILE (defaults to a tempfile) --profile-mode MODE Profile mode (cpu|wall|object)
Common options: -h, -?, --help Show this message --version Show version
Server-specific options for Rack::Handler::Puma: -O Threads=MIN:MAX min:max threads to use (default 0:16) -O Verbose Don’t report each request (default: false)

Gem Directory Tree

Gems are either installed system-wide or in user-specific locations. If you are using rbenv to work with Ruby (and you should), the directory that your gems are installed to is $HOME/.rbenv/versions/$RUBY_VERSION/​lib/​ruby/​gems/​$RUBY_VERSION/​$GEM_NAME-$GEM_VERSION. The various versions of each gem reside in sibling directories.

The project I was working on when I ran the following command used Ruby version 3.1.0, and the rackup version 1.0.0 gem used by that project was installed at:

Shell
$ bundle info rackup
/home/mslinn/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/rackup-1.0.0

The entry point is $HOME/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/rackup-1.0.0/lib/rackup.rb

The rackup command was installed to:

Shell
$ which rackup
/home/mslinn/.rbenv/shims/rackup

Following is the brittle Visual Studio Code launch configuration that you find on the Interwebs. It requires the rebornix.Ruby Visual Studio Code extension. I highlighted the brittle part:

.vscode/launch.json
{
  "cwd": "${workspaceRoot}",
  "name": "Debug rackup application",
  "pathToBundler": "${userHome}/.rbenv/shims/bundle",
  "pathToRDebugIDE": "${userHome}/.rbenv/shims/rdebug-ide",
  "program": "${userHome}/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/rackup-1.0.0/lib/rackup.rb",
  "request": "launch",
  "showDebuggerOutput": true,
  "type": "Ruby",
  "useBundler": true,
},

To amplify what I said before, if you change the Ruby version or the version of the rackup gem, the program path will also need to change, or the launch configuration will stop working.

VSCode Debugging With Binstubs

We saw earlier that the rackup command is a bash script, and it launches the gem of the same name. However, Ruby debuggers are unable to work with bash scripts.

Binstubs to the rescue! ... well, actually, manually created binstubs to the rescue.

I do not know why the following attempt to automagically create a binstub fails — as we have seen, rackup does indeed have an associated bash script.

Shell
$ bundle binstubs rackup
rackup has no executables, but you may want one from a gem it depends on.
  rack has: rackup 

However, there is a solution: manually creating a binstub for the rackup gem. I wrote a binstub for rackup; save it as bin/rackup in the directory of your Jekyll project.

./bin/rackup
#!/usr/bin/env ruby
# Prepares the $LOAD_PATH by adding to it lib directories of all gems in the # project's bundle: require 'bundler/setup'
load Gem.bin_path('rack', 'rackup')

The last line above could be modified to load other gems.

Following is a Visual Studio Code launch configuration for Rackup-compatible webapps using the manually created binstub. It assumes that rbenv was used to install Ruby. Because bundler is used to load rackup, all the dependencies mentioned in the Gemfile are also loaded prior to debugging.

.vscode/launch.json
{
  "cwd": "${workspaceRoot}",
  "name": "Debug rackup application",
  "pathToBundler": "${userHome}/.rbenv/shims/bundle",
  "pathToRDebugIDE": "${userHome}/.rbenv/shims/rdebug-ide",
  "program": "${workspaceRoot}/bin/rackup",
  "request": "launch",
  "showDebuggerOutput": true,
  "type": "Ruby",
  "useBundler": true,
},
😁

Easy!

About the Author

I, Mike Slinn, have been working with Ruby a long time now. Back in 2005 I was the product marketing manager at CodeGear (the company was formerly known as Borland) for their 3rd Rail IDE. 3rd Rail supported Ruby, and Ruby on Rails, at launch.

In 2006, I co-chaired the Silicon Valley Ruby Conference, on behalf of SD Forum in Silicon Valley. As you can see, I have the t-shirt. I was sole chairman of the 2007 Silicon Valley Ruby Conference.

Several court cases have come my way over the years in my capacity as a software expert witness. The court cases featured questions about IP misappropriation for Ruby on Rails programs. You can read about my experience as a software expert if that interests you.

I currently enjoy writing Jekyll plugins in Ruby for this website and others, and Ruby utilities.