Skip to content

Hook mechanism for completion of Bundler.require #3216

@logiclrd

Description

@logiclrd

I'd like to add a mechanism to permit a Gem to register a block to be executed automatically once Bundler's Runtime has completed its require pass. I propose adding instance methods to Runtime and class methods to Bundler that would forward to the Bundler class's Runtime (Bundler.load) for this.

  1. Code in a Gem would use syntax such as:

    Bundler.when_require_complete do
      # final initialization code here
    end
    
  2. Runtime.when_require_complete would stash the provided block for later execution.

  3. Runtime.require would, upon completion of its loop through the list of dependencies, call each registered block.

Thoughts that occur to me:

  1. Does Bundler need to be threadsafe at all? What happens if two threads call require on the same Runtime object simultaneously? If that is a situation that currently works, then presumably I'll need to wrap accesses to the list of when_require_complete blocks in a Mutex.
  2. If Bundler is called multiple times, of course the second invocation's require calls, if pointed at the same Ruby files, won't actually run any code. So, if we assume that the finalization code might need to run again if the loadout of Gems changes, the callback handlers need to persist from one call to Runtime.require to the next.

If this sort of functionality is likely to be approved by Bundler's maintainers, then I'll go ahead and make a pull request for them. :-)

Rationale:

We have a suite of Gems that tie into a common configuration subsystem, which is defined in its own Gem. Each Gem needs to add its embedded configuration directory to a central list before the configuration subsystem initializes itself and pulls in all of the values, based on order of precedence. Right now, we have no easy way to ensure that the configuration subsystem always initializes after all of the other Gems have had a chance to add their directories to the list. One approach we've tried is to place the variable initialization into the root file for the Gem that handles the configuration, and then arranging things so that it is the last Gem that Bundler.require brings in, but this has hit a snag with a Gem that requires a specific version (or newer) of the Gem that handles the configuration. Because it declares it as a dependency, that guarantees that the configuration Gem gets pulled in before this other Gem that wants to add to the configuration. We want to avoid having to explicitly initialize the configuration mechanism in every file that uses it -- there are hundreds of locations maintained by dozens of different teams.

Therefore, having the ability to register a block with Bundler to be automatically executed when the Runtime completes its require pass is a convenient mechanism to allow each of the Gems specified in the Gemfile (and brought in via dependencies) to initialize, and then use the configuration state data those initializations have built up to fire up the configuration subsystem.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions