Let's prepare a stage for the experiments:
$ docker run --rm -it ruby:alpine sh
$ apk add build-base git
$ mkdir app
$ cd app
$ gem install bundler
$ gem list -e bundler
*** LOCAL GEMS ***
bundler (2.0.2, default: 1.17.2)
$ bundle --version
Bundler version 2.0.2
That is as expected, it uses the latest version unless told otherwise.
$ bundle _1.17.2_ --version
Bundler version 1.17.2
That's how you "tell otherwise." The following portion of the binstub basically constraints the version.
$ bundle init && bundle _1.17.2_ && bundle _2.0.2_ --version
Traceback (most recent call last):
2: from /usr/local/bundle/bin/bundle:25:in `<main>'
1: from /usr/local/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path'
/usr/local/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (1.17.2) required by your /app/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:1.17.2`
We asked for bundler-2.0.2, but the BUNDLED WITH portion of the Gemfile.lock file is equivalent to gem 'bundler', '1.17.2', so it bailed out. Do note the "required by your /app/Gemfile.lock" part.
Let's try to update bundler (change BUNDLED WITH constraint).
Gemfile:
source 'https://rubygems.org'
if ENV['LIFT_CONSTRAINT'].to_i > 0
gem 'byebug'
else
gem 'byebug', '~> 10'
endWe're going to use LIFT_CONSTRAINT variable to make it look like Gemfile* files were created a while ago, and now newer versions of gems are available.
$ rm Gemfile.lock; bundle _1.17.2_
Resolving dependencies...
Using bundler 1.17.2
Using byebug 10.0.2
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Bundled gems are installed into `/usr/local/bundle`
$ LIFT_CONSTRAINT=1 bundle update --bundler
Resolving dependencies...
Using bundler 2.0.2
Using byebug 10.0.2
Bundle updated!
It went as planned, only bundler was updated. Now, let's do it this way:
$ rm Gemfile.lock; bundle _1.17.2_
$ LIFT_CONSTRAINT=1 bundler update --bundler
Resolving dependencies...
Using bundler 1.17.2
Using byebug 10.0.2
Bundle updated!
Nothing changed. In the first case (bundle update --bundler) bundler-2.0.2 was executed, so it performed the update. In the second one (bundler update --bundler) bundler-1.17.2 took control, so it did nothing. On second thought, we had BUNDLED WITH 1.17.2 in Gemfile.lock. How come bundler-2.0.2 was executed? There's a little escape hatch in case of update, it takes effect when the command is like so: bundle update ... --bundler[=VERSION] .... But the check is pretty fragile, so bundler update won't do.
Let's take it a step further, update bundler with gems from GitHub:
Gemfile:
if ENV['LIFT_CONSTRAINT'].to_i > 0
gem 'rack', github: 'rack/rack'
else
gem 'rack', github: 'rack/rack', tag: '2.0.0'
end$ rm Gemfile.lock; bundle _1.17.2_
The git source `git://github.com/rack/rack.git` uses the `git` protocol, which transmits data without encryption. Disable this warning with `bundle config git.allow_insecure true`, or switch to the `https` protocol to keep your data secure.
Fetching git://github.com/rack/rack.git
Resolving dependencies...
Using bundler 1.17.2
Using rack 2.0.0 from git://github.com/rack/rack.git (at 2.0.0@035a99b)
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Bundled gems are installed into `/usr/local/bundle`
$ LIFT_CONSTRAINT=1 bundle update --bundler
Fetching https://github.com/rack/rack.git
Resolving dependencies...
Using bundler 2.0.2
Using rack 2.1.0 (was 2.0.0) from https://github.com/rack/rack.git (at master@529cd33)
Bundle updated!
Okay, not totally unexpected, but not like with ordinary gems. byebug wasn't updated in spite of the constraint having been lifted. rack was. Let's try it another way:
$ rm Gemfile.lock; bundle _1.17.2_
The git source `git://github.com/rack/rack.git` uses the `git` protocol, which transmits data without encryption. Disable this warning with `bundle config git.allow_insecure true`, or switch to the `https` protocol to keep your data secure.
Fetching git://github.com/rack/rack.git
Resolving dependencies...
Using bundler 1.17.2
Using rack 2.0.0 from git://github.com/rack/rack.git (at 2.0.0@035a99b)
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Bundled gems are installed into `/usr/local/bundle`
Now let's adjust Gemfile.lock to make it look like we didn't have the constraint in the first place:
--- Gemfile.lock.orig
+++ Gemfile.lock
@@ -1,7 +1,6 @@
GIT
remote: git://github.com/rack/rack.git
revision: 035a99b1bf8e450b79f3e8ae80329254746d7a8e
- tag: 2.0.0
specs:
rack (2.0.0)Check if bundler is okay with that:
$ LIFT_CONSTRAINT=1 bundle
The git source `git://github.com/rack/rack.git` uses the `git` protocol, which transmits data without
encryption. Disable this warning with `bundle config git.allow_insecure true`, or switch to the `https` protocol to keep your data secure.
Using bundler 1.17.2
Using rack 2.0.0 from git://github.com/rack/rack.git (at master@035a99b)
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Bundled gems are installed into `/usr/local/bundle`
It is. Now let's try to update bundler:
$ LIFT_CONSTRAINT=1 bundle update --bundler
Fetching https://github.com/rack/rack.git
Resolving dependencies...
Using bundler 2.0.2
Using rack 2.1.0 (was 2.0.0) from https://github.com/rack/rack.git (at master@529cd33)
Bundle updated!
There you go, it updated rack again. Have you noticed the change of protocols (git -> https)?
-Fetching git://github.com/rack/rack.git
+Fetching https://github.com/rack/rack.gitThat's the reason why bundler decided that the dependency had changed. Or its source, git source to be more precise. As such it updated the dependency. And for that it used information from Gemfile.
If that is true, in order to update only bundler we've got to change the protocols of git dependencies and do bundle update --bundler. Let's confirm it:
$ rm Gemfile.lock; bundle _1.17.2_
Remove the tag line, and change git to https in Gemfile.lock:
--- Gemfile.lock.orig
+++ Gemfile.lock
@@ -1,7 +1,6 @@
GIT
- remote: git://github.com/rack/rack.git
+ remote: https://github.com/rack/rack.git
revision: 035a99b1bf8e450b79f3e8ae80329254746d7a8e
- tag: 2.0.0
specs:
rack (2.0.0)Then:
$ LIFT_CONSTRAINT=1 bundle update --bundler
Resolving dependencies...
Using bundler 2.0.2
Using rack 2.0.0 from https://github.com/rack/rack.git (at master@035a99b)
Bundle updated!
Awesome. It wasn't that hard, was it? The solution was within our reach :) And no, you can't just change the BUNDLED WITH line, and be done with it. On next install it will update git dependencies because it "can't tolerate the git protocol." Which is not particularly bad, since you can always pin them with SHA-1 references. What is bad is that it will update all dependencies of git dependencies. Well, you can go as far as to pin all those dependencies as well, temporarily adding them to Gemfile where needed. But it might be easier to just change the protocols.