module Kernel
RubyGems adds the gem
method to allow activation of specific gem versions and overrides the require
method on Kernel
to make gems appear as if they live on the $LOAD_PATH
. See the documentation of these methods for further detail.
Public Instance Methods
Private Instance Methods
Use Kernel#gem
to activate a specific version of gem_name
.
requirements
is a list of version requirements that the specified gem must match, most commonly “= example.version.number”. See Gem::Requirement
for how to specify a version requirement.
If you will be activating the latest version of a gem, there is no need to call Kernel#gem
, Kernel#require
will do the right thing for you.
Kernel#gem
returns true if the gem was activated, otherwise false. If the gem could not be found, didn’t match the version requirements, or a different version was already activated, an exception will be raised.
Kernel#gem
should be called before any require statements (otherwise RubyGems may load a conflicting library version).
Kernel#gem
only loads prerelease versions when prerelease requirements
are given:
gem 'rake', '>= 1.1.a', '< 2'
In older RubyGems versions, the environment variable GEM_SKIP could be used to skip activation of specified gems, for example to test out changes that haven’t been installed yet. Now RubyGems defers to -I and the RUBYLIB environment variable to skip activation of a gem.
Example:
GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb
# File rubygems/core_ext/kernel_gem.rb, line 41 def gem(gem_name, *requirements) # :doc: skip_list = (ENV['GEM_SKIP'] || "").split(/:/) raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name if gem_name.kind_of? Gem::Dependency unless Gem::Deprecate.skip warn "#{Gem.location_of_caller.join ':'}:Warning: Kernel.gem no longer "\ "accepts a Gem::Dependency object, please pass the name "\ "and requirements directly" end requirements = gem_name.requirement gem_name = gem_name.name end dep = Gem::Dependency.new(gem_name, *requirements) loaded = Gem.loaded_specs[gem_name] return false if loaded && dep.matches_spec?(loaded) spec = dep.to_spec if spec if Gem::LOADED_SPECS_MUTEX.owned? spec.activate else Gem::LOADED_SPECS_MUTEX.synchronize { spec.activate } end end end
When RubyGems is required, Kernel#require
is replaced with our own which is capable of loading gems on demand.
When you call require 'x'
, this is what happens:
-
If the file can be loaded from the existing Ruby loadpath, it is.
-
Otherwise, installed gems are searched for a file that matches. If it’s found in gem ‘y’, that gem is activated (added to the loadpath).
The normal require
functionality of returning false if that file has already been loaded is preserved.
# File rubygems/core_ext/kernel_require.rb, line 34 def require(path) if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?) monitor_owned = RUBYGEMS_ACTIVATION_MONITOR.mon_owned? end RUBYGEMS_ACTIVATION_MONITOR.enter path = path.to_path if path.respond_to? :to_path if spec = Gem.find_unresolved_default_spec(path) # Ensure -I beats a default gem resolved_path = begin rp = nil load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths Gem.suffixes.each do |s| $LOAD_PATH[0...load_path_check_index].each do |lp| safe_lp = lp.dup.tap(&Gem::UNTAINT) begin if File.symlink? safe_lp # for backward compatibility next end rescue SecurityError RUBYGEMS_ACTIVATION_MONITOR.exit raise end full_path = File.expand_path(File.join(safe_lp, "#{path}#{s}")) if File.file?(full_path) rp = full_path break end end break if rp end rp end begin Kernel.send(:gem, spec.name, Gem::Requirement.default_prerelease) rescue Exception RUBYGEMS_ACTIVATION_MONITOR.exit raise end unless resolved_path end # If there are no unresolved deps, then we can use just try # normal require handle loading a gem from the rescue below. if Gem::Specification.unresolved_deps.empty? RUBYGEMS_ACTIVATION_MONITOR.exit return gem_original_require(path) end # If +path+ is for a gem that has already been loaded, don't # bother trying to find it in an unresolved gem, just go straight # to normal require. #-- # TODO request access to the C implementation of this to speed up RubyGems if Gem::Specification.find_active_stub_by_path(path) RUBYGEMS_ACTIVATION_MONITOR.exit return gem_original_require(path) end # Attempt to find +path+ in any unresolved gems... found_specs = Gem::Specification.find_in_unresolved path # If there are no directly unresolved gems, then try and find +path+ # in any gems that are available via the currently unresolved gems. # For example, given: # # a => b => c => d # # If a and b are currently active with c being unresolved and d.rb is # requested, then find_in_unresolved_tree will find d.rb in d because # it's a dependency of c. # if found_specs.empty? found_specs = Gem::Specification.find_in_unresolved_tree path found_specs.each do |found_spec| found_spec.activate end # We found +path+ directly in an unresolved gem. Now we figure out, of # the possible found specs, which one we should activate. else # Check that all the found specs are just different # versions of the same gem names = found_specs.map(&:name).uniq if names.size > 1 RUBYGEMS_ACTIVATION_MONITOR.exit raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}" end # Ok, now find a gem that has no conflicts, starting # at the highest version. valid = found_specs.find { |s| !s.has_conflicts? } unless valid le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" le.name = names.first RUBYGEMS_ACTIVATION_MONITOR.exit raise le end valid.activate end RUBYGEMS_ACTIVATION_MONITOR.exit return gem_original_require(path) rescue LoadError => load_error RUBYGEMS_ACTIVATION_MONITOR.enter begin if load_error.message.end_with?(path) and Gem.try_activate(path) require_again = true end ensure RUBYGEMS_ACTIVATION_MONITOR.exit end return gem_original_require(path) if require_again raise load_error ensure if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?) if monitor_owned != (ow = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?) STDERR.puts [$$, Thread.current, $!, $!.backtrace].inspect if $! raise "CRITICAL: RUBYGEMS_ACTIVATION_MONITOR.owned?: before #{monitor_owned} -> after #{ow}" end end end