Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a deterministic way of checking whether bundle exec is necessary? #486

Open
mculp opened this issue Jan 5, 2023 · 5 comments
Open

Comments

@mculp
Copy link

mculp commented Jan 5, 2023

Sort of related to #433, I've recently found out that bundle exec adds a significant amount of time to commands as opposed to the naked command when the binstub doesn't exist. This is especially important when running linters as a pre-commit hook.

I posted on lefthook's discussion board about this, but our team mostly uses chruby in local environments because it's in our onboarding guide, and y'all seem to more likely to know the answer to this: is there a deterministic or even near-deterministic way of checking whether bundle exec is necessary?

I came up with this pretty naive exec-wrapper script:

#!/usr/bin/env sh

if test -n "$RUBY_VERSION"
then
  if test -x "$0"
  then
    exec "$@"
  elif test -x chruby-exec
  then
    chruby-exec "$RUBY_VERSION" -- "$@"
  fi
else
  bundle exec "$@"
fi
Command Mean [ms] Min [ms] Max [ms] Relative
rubocop --server ... 190.9 ± 5.9 183.4 199.9 1.00
bin/exec-wrapper rubocop --server ... 200.2 ± 8.4 184.1 212.1 1.05 ± 0.05
chruby-exec 3.1.2 -- rubocop --server ... 210.5 ± 7.8 195.2 222.7 1.10 ± 0.05
bundle exec rubocop ... 709.4 ± 5.2 702.7 716.7 3.72 ± 0.12
Benchmark 1: bin/exec-wrapper rubocop --server ...
  Time (mean ± σ):     197.1 ms ±   4.9 ms    [User: 93.1 ms, System: 30.3 ms]
  Range (min … max):   186.4 ms … 202.3 ms    14 runs

Benchmark 2: rubocop --server ...
  Time (mean ± σ):     189.4 ms ±   7.3 ms    [User: 92.3 ms, System: 28.8 ms]
  Range (min … max):   173.7 ms … 200.5 ms    15 runs

Benchmark 3: bundle exec rubocop --server ...
  Time (mean ± σ):     706.4 ms ±   5.3 ms    [User: 567.1 ms, System: 64.8 ms]
  Range (min … max):   699.2 ms … 715.6 ms    10 runs

Benchmark 4: chruby-exec 3.1.2 -- rubocop ...
  Time (mean ± σ):     212.0 ms ±   5.2 ms    [User: 98.5 ms, System: 34.4 ms]
  Range (min … max):   203.9 ms … 219.4 ms    14 runs

Summary
  'rubocop --server ...
    1.04 ± 0.05 times faster than 'bin/exec-wrapper rubocop --server ...'
    1.12 ± 0.05 times faster than 'chruby-exec 3.1.2 -- rubocop --server ...'
    3.73 ± 0.15 times faster than 'bundle exec rubocop --server ...'

I would like to commit this pretty significant quality of life change, but I don't want to break anyone's machine by missing something in this script.

@eregon
Copy link
Contributor

eregon commented Jan 5, 2023

All except bundle exec rubocop will pick the latest version of rubocop installed and not the one specified in the Gemfile for the project, which can lead to failures, etc.

That big an overhead for bundle exec seems too much though, could you open an issue at https://github.com/rubygems/rubygems/issues ?

@mculp
Copy link
Author

mculp commented Jan 5, 2023

I see, that's exactly the answer I was looking for. What if we were using postmodern's gem_home gem or some other "gemset" tool? Is that still a thing? gem_home itself looks pretty out of date.

Sure, I'll file an issue there. I first noticed this when someone else mentioned it happening with them as well in an attempt to improve rubocop boot time.

@eregon
Copy link
Contributor

eregon commented Jan 5, 2023

IMHO gemset tools are pretty much useless nowadays. Having a dedicated gem_home for an app to avoid bundle exec would only work if you wipe out the entire gem home every time you update a gem in Gemfile/Gemfile.lock and then reinstall everything, and only if there are no extra gems installed in the default gem home. Basically, very messy and won't be practical I think. It would also not work if the a default/bundled gems of that Ruby is more recent than what you use (would pick that version instead).

@postmodern
Copy link
Owner

postmodern commented Jan 6, 2023

It might be possible to check if vendor/bundle exists and add the $ruby/$ruby_api_version/bin directory to PATH. However, like you pointed out, you might not want all executables to automatically use the bundled version. Also, installing gems into vendor/bundle per-project might consume extra disk space.

I've been using this bash alias to selectively make certain common executables "bundle aware".

It would be nice if rubygems handled this automatically in it's own binstubs which are generated when you install a gem with executables.

@mculp
Copy link
Author

mculp commented Jan 9, 2023

yeah, I'm trying to avoid bundle exec for linting because it's so much faster without bundle exec (and runs fine w/ chruby)

I went from greater than 2s running rubocop on 1 staged file to 190ms (with some other tweaks), but bundle exec is a huge chunk of that -- at least 500ms and sometimes up to 1000ms on my machine.

Maybe instead of trying to genericize for all commands, I'll write a specific wrapper for Rubocop. If the Rubocop server is running, then the files have been loaded already and there shouldn't be a need for bundle exec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants