Managing conflicting versions of ruby gems

2019-02-25 18:09发布

问题:

I am building a framework that loads user provided ruby code. It is basically a plugin mechanism. I want the user provide ruby code to be able to require gems of its own. I intend to have the "plugin" package include a vendor directory with the gems.

How can I load gems that are required by the plugin without having them conflict with my framework's gems? For example, if my framework uses treetop version 1.3.0, and a plugin uses treetop 1.4.2 I want each to work with their specified version.

Likewise, is there a way to prevent plugins from conflicting with each other?

I have looked at gem_plugin, _why's sandbox, and some other tools. But I don't see any library that specifically handles this case - I assume its been done before.

I have also looked at the internals of Bundler to see how it manages gem versions. I am prepared to do some pretty complex stuff if need be. But I am still uncertain of how to go about it.

I also have a lot of freedom in how I implement this. So if you think I am barking up the wrong tree, please say so.

Thanks for any advice.

SIDE NOTE: It occurred to me while writing this that I need something similar to the Classloaders in a Java servlet container. A WAR file can include jar files, and the web application's class loader will prefer those over the jars that are on the global classpath. Is there any way in ruby to segment the ruby "classpath" (i.e. load_path, require, etc)?

回答1:

To be blunt, you can't have two versions of the same gem loaded at the same time.

Bundler does a good (ish) job of looking through all of the required gems and finding a solution to the various overlapping dependencies, but even so it is limited to only loading one version of a gem at a time.

This leads to plugin developers constantly having to update to support any changes that are made in dependent gems in order to avoid just the situation you describe.

(Don't get me started on the screw up that results from the various competing JSON implementations and the pain you have to go through when you have several gem dependencies all requiring different ones.)



回答2:

Respectfully disagree with the answer above. Here is how I do it:

ruby -S gem list my_gem

`*** LOCAL GEMS ***
my_gem (1.0.1, 1.0.0, 0.0.2)
`

ruby -S gem lock my_gem-1.0.0 > locklist.rb

which generates list of dependencies for a specific version into locklist

require 'rubygems'
gem 'my_gem', '= 1.0.0'
gem 'gem_base', '= 1.0.0'
gem 'rest-client', '= 1.7.2'
gem 'savon', '= 1.1.0'
gem 'addressable', '= 2.3.6'
gem 'mime-types', '= 1.25.1'
gem 'netrc', '= 0.11.0'

now you can do load('locklist.rb') which will load a specific version of a gem along with its dependencies. Look ma, no Bundler.