How do you ignore user-installed gems?

Viewed 25

When I start Ruby, I see that it scans the home directory for user-installed gems:

strace -e file ruby30 -e nil |& fgrep $HOME

It's a long list, have a loook at it yourself. For me, it's roughly 300 system calls.

When I deploy Ruby scripts in my company, I want these Ruby scripts not to access the user home directory. All the gems that are necessary for my deployed Ruby scripts are installed using RPMs, alongside the interpreter.

Problems

It want to prevent these accesses to user home directory for multiple reasons.

Reproducible/correct behavior

The user might have installed a newer version of a gem. The newer version might behave differently from the installed gem. That's I want my deployed Ruby scripts to use the installed gems, and the installed gems only. Reproducible behavior is of paramount importance, because many of these Ruby scripts are used in builds that must be reproducible

Maintenance

Some users user-installed gems using prior versions of Ruby. My deployed scripts use a newer interpreter. Whenever one of these users starts one of my Ruby scripts, they get warnings like this one, at every single start:

Ignoring mysql2-0.4.8 because its extensions are not built. Try: gem pristine mysql2 --version 0.4.8

Performance

The user home directories are one a network share, mounted using NFS. Scanning the home directory via NFS makes Ruby startup much slow.

Approaches

--disable=gems

  • (good) ruby --disable=gems prevents Ruby from accessing to the user gems. (According to strace: no access to the user home directory at all.)
  • (good) It makes Ruby startup roughly 10 times faster (in my setup).
  • (good) Requires a simple change in the shebang line: #!/usr/bin/ruby30 --disable=gems
  • (bad) It prevents me from loading some gems (e.g.: nokogiri); I guess this is about a Gem being built-in or not being built-in.
  • (bad) --disable=gems is deprecated for Ruby >=3.1: https://ruby-doc.org/core-3.1.0/NEWS_md.html#label-Command+line+options

Setting $GEM_PATH and $GEM_SPEC_CACHE

  • (good) env GEM_PATH= GEM_SPEC_CACHE= ruby prevents Ruby from accessing the user gems. (According to strace: no access to the user home directory at all.)

  • (good) Ruby startup is much slower than ruby --disable=gems, but it is still much faster than the default behavior.

  • (good) Ruby is still able to load all gems that are installed using RPMs

  • (?) I have to create and deploy a wrapper:

    #!/bin/sh
    GEM_PATH= GEM_SPEC_CACHE= exec /usr/bin/ruby30 "$@"
    

    All my deployed scripts have to use this wrapper.

Setting Gem.paths

Inserting this snippet right at the start of the script:

#!/usr/bin/ruby
Gem.paths = {
  "GEM_HOME" => Gem.paths.home, 
  "GEM_PATH" => Gem.path.reject { |path| path.start_with? "/home/" }.join(":"), 
  "SPEC_CACHE_DIR" => "" 
}
[...]
  • (good) It prevents Ruby from loading the user gems.
  • (bad) Ruby accesses (and checks) all user gems before the start of the script.
  • (bad) Aesthetics

Comparing with Python

Isn't there a better solution? Something like python -s?

0 Answers
Related