[ruby-talk:442992] How do I make Ruby ignore gems in the user's home directory?

Hi!

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/Safe Behavior
The user might have installed a newer or patched version of a gem in his home directory. 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: File: NEWS.md [Ruby 3.1.0]
    
### 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

## Feature Request?
Isn't there a better solution? Something like `python -s`? For me, it would be very useful.

Best regards
Johannes

You should be able to use this as the shebang line in your ruby script
file(s) rather than create a wrapper script:

    #!/usr/bin/env -S GEM_PATH= GEM_SPEC_CACHE= /usr/bin/ruby30

The -S option is key to making it work correctly. I tested this with a
trivial script to make sure it works on a Linux system, but I don't know
how portable that option is.

Have you identified why using this method is still slow for your needs? Is
it still trying to access paths in the user home directory? Since you know
where all of your rpm installed gems are located, maybe you can speed
things up more by adding them with Ruby's -I option in the shebang line so
that rubygems doesn't need to go searching.

-Jeremy

···

On Sun, Sep 18, 2022 at 12:55 PM Abt, Johannes <johannes.abt@advantest.com> wrote:

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

Hi Jeremy!

#!/usr/bin/env -S GEM_PATH= GEM_SPEC_CACHE= /usr/bin/ruby30

Thanks for the nice “env -S” trick, I did not know that one. It’s a pity the version of “env” on RHEL7 is too old for this feature.

Have you identified why using this method is still slow for your needs?

Sorry for the misunderstanding. It is not too slow for my needs. I just noticed that this method is somewhat slower than "--disable=gems" -- but it is more powerful (see "nokogiri").

Best regards
Johannes

> #!/usr/bin/env -S GEM_PATH= GEM_SPEC_CACHE= /usr/bin/ruby30

Thanks for the nice “env -S” trick, I did not know that one. It’s a pity
the version of “env” on RHEL7 is too old for this feature.

RedHat systems always burn me like that. I feel for you.

> Have you identified why using this method is still slow for your needs?

Sorry for the misunderstanding. It is not too slow for my needs. I just
noticed that this method is somewhat slower than "--disable=gems" -- but it
is more powerful (see "nokogiri").

I was really just asking if you did any further debugging to figure out why
the environment variable method was slower than disabling gems completely.
Rubygems adds some cost to require method calls that is likely unavoidable,
but you may be able to speed it up by ensuring that it's only looking in
paths you care about.

Have you looked into using the --standalone option to bundle install? It
claims to make "a bundle that can work without depending on Rubygems or
Bundler at runtime." If you use that, you would ship your script and all
dependencies as a single rpm. You would need to include a wrapper to make
starting your script work as expected, but I think this would let you
disable rubygems completely. As an added benefit, you could use different
versions of gems between your various scripts as necessary rather than
forcing a system wide upgrade for all of the gems and all of your scripts
whenever an update is needed. Think of it as similar to static linking
libraries into a C program.

-Jeremy

···

On Sun, Sep 18, 2022 at 4:04 PM Abt, Johannes <johannes.abt@advantest.com> wrote: