-
Couldn't load subscription status.
- Fork 32
Description
Summary
This is request to allow bundler to be able to support optional dependencies with an opinionated syntax and behavior.
Use case
A creator of a gem might want to declare that their package has some additional functionality if the client wants to include an additional dependency. For example, a string formatter gem might want to optionally add string coloring functionality if the client is okay with including the rainbow gem.
Another use case is sorbet, a type-checker for ruby. More info on my specific use case here.
What we'd like is for a gem to include sorbet-runtime (method type annotations for sorbet). However, we do not want to burden the client with this gem if they do not want to install it. Instead, we'd like to fall back to a separate gem, which is a shim of this runtime.
Desired syntax
This is the desired syntax for this feature. Very open to other options here.
Take no action of the primary dependency is unavailable
spec.add_optional_dependency 'sorbet-runtime', '~> 0.5.5585'Sub in a separate dependency if the primary dependency is unavailable
spec.add_optional_dependency 'sorbet-runtime', '~> 0.5.5585', else_add_dependency: 'sorbet-runtime-stub', '~> 0.14'Desired behavior
Case 1: The user does not include the optional dependency
Behavior: If the gem uses else_add, it will include the given argument (sorbet-runtime-stub above) and reconcile the fall back against the client's other dependencies, and include it in Gemfile.lock of the client (i.e. the fallback is required and behaves as if its any other required gem).
Case 2: The user does include the optional dependency
Behavior: The fallback is ignored, and the optional dependency is required, and the versions are reconciled against the client's version, and the standard reconciliation process applies (i.e. bundler errors if there are irreconcilable dependencies).
Optional additions
Allow the client of the gem to specify the optionality inline.
For example, the client might have:
gem 'my_special_gem', include_optional_dependencies: 'sorbet'This would allow the client to explicitly tell the gem to use that dependency. This is essentially syntactical sugar over:
gem 'sorbet'
gem 'my_special_gem'It makes it explicit that the dependency is specifically to satisfy the optionality of the sorbet dependency.
If the client uses include_optional_dependencies, it uses the version specified by the gem. If the client wants to specify the version of sorbet, they use the second approach (i.e. simply declare the two separate gems with the desired version numbers).
Allow the gem to declare a fallback require
spec.add_optional_dependency 'sorbet-runtime', '~> 0.5.5585', else_require: '../lib/sorbet-runtime-stub'This would allow us to explicitly handle the case where the client did not include the optional dependency.
Other options
This behavior is today able to be emulated, but the emulation is fraught with concerns.
Here is my best attempt at emulating this behavior.
There are many issues with this:
- The client's
Gemfile.lockwill not reflect that the optional dependency is actually a dependency of the gem. - Bundler will not reconcile versions at the time the user executes
bundle install. Instead errors come only during runtime, which conflicts with the overarching philosophy of bundler of being able to reconcile dependencies statically in a separate process (i.e. before runtime). - This relies on possibly unstable API (specific errors raised by the
gemcommand and the specific messages in those errors). - Depending on the load order, it might be possible that the fallback overrides the optional dependency in an unexpected way.
- In general, we are moving dependency management away from
bundlerand into the client. This can potentially have a lot of undesirable downstream effects. One simple example is if you are constructing a dependency tree of your application, it would not reflect these optional dependencies using this workaround.