Bundler 2.1.4 fixes dependency loading outside an application
Bundler 2.1.4 repairs inline gem loading and removes an optional internal require that could activate an application dependency too early.
A Ruby script with an inline dependency declaration should not need a separate Gemfile somewhere above its working directory. That is the purpose of keeping the declaration inside the script. Bundler 2.1.4, published on January 5, repairs a regression in that workflow. The other fix in this release also concerns dependency loading, where a library used internally by Bundler could activate a gem before the application had established its bundle.
The versioned changelog identifies both corrections. They are small changes in code, but they affect a useful boundary. Bundler must load its own implementation while also controlling how application dependencies become available. Work done for the first purpose can accidentally change the second.
I came to Ruby from Perl and find Ruby's blocks approachable for expressing a small operation with its surrounding context. An inline Gemfile fits that style. The dependency declaration stays beside the program that needs it, provided the package manager does not introduce an unexpected dependence on the directory containing the script.
Inline declarations establish their own bundle
The 2.1.4 implementation of bundler/inline accepts a block containing Gemfile declarations. It builds a definition from that block and sets up the runtime from the resulting definition. Its documentation says declared gems are required automatically unless the declaration supplies require: false.
A script can consequently describe the dependency and use its normal entry point. This example leaves requiring the library explicit:
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "json", require: false
end
require "json"
puts JSON.generate("message" => "hello")
The example does not supply a separate Gemfile path. Bundler evaluates the block as its declaration. The implementation also prevents writing a normal lockfile through this inline definition, so this workflow should not be mistaken for a conventional application directory containing a maintained Gemfile and lockfile pair.
The code temporarily changes Bundler's root behavior and BUNDLE_GEMFILE while evaluating and setting up the definition. An ensure block restores the earlier root method and handles the environment variable afterward. That cleanup runs even when an operation raises, which matters because environment state affects later library loading in the process.
The implementation temporarily disables frozen settings while constructing the inline definition. That choice fits a workflow without a maintained lockfile. It also explains why an inline script should be assessed according to its own dependency behavior instead of inheriting every expectation of a deployed application bundle.
An empty variable and an absent variable differ
Pull request 7537 changes the cleanup when BUNDLE_GEMFILE was not previously set. Instead of deleting the variable, the implementation leaves it present with an empty string. If an earlier value existed, that value is restored.
The difference is observable. Removing a variable and assigning an empty string do not represent identical process environments. Code performing subsequent bundle discovery can react differently to those states. The changelog connects this distinction to inline gems becoming unavailable when no Gemfile exists in the directory hierarchy.
The added regression test exercises an inline declaration after clearing the original variable. It checks that the resulting value is empty and then launches another Ruby process, checking its exit status. That gives the fix a concrete environment-inheritance case, beyond simply loading a gem once in the parent process.
This is a useful example of cleanup preserving a semantic boundary. Restoring an earlier nonempty path is appropriate when a caller had supplied one. With no earlier path, leaving the empty marker avoids returning the inline workflow to a discovery state that requires a surrounding application file.
Optional requires can activate dependencies too early
Pull request 7529 addresses the other regression. Bundler vendors net-http-persistent, whose code attempted to require net/http/pipeline if it happened to be installed. The optional require rescued LoadError, so the internal library could run without that gem. Its presence on the system nevertheless changed what Bundler loaded.
The patch removes that optional loading and the associated pipeline method from the vendored implementation. The changelog describes the resulting application problem as net-http-pipeline no longer being allowed in a Gemfile when the system already had it installed. Loading it for Bundler's internal HTTP implementation happened too early for the application's dependency setup.
The regression test installs the gem in the system environment, declares it in an application Gemfile and configures a separate bundle path. Installation and bundle checking must then succeed. The case tests coexistence between system installation and application resolution, rather than testing whether the gem's own functionality works.
I prefer dependency behavior that follows the declaration over behavior that varies with incidental installed packages. Ruby's convenient loading model makes optional integration easy to express, but package-management code has to account for when that integration runs. Here, removing an internal optional require lets the application establish the dependency environment it actually requested.