123456789_123456789_123456789_123456789_123456789_

Module: Kernel

Overview

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.

Constant Summary

Instance Method Summary

Instance Method Details

gem(gem_name, *requirements) (private)

Use #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 #gem, #require will do the right thing for you.

#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.

#gem should be called before any require statements (otherwise RubyGems may load a conflicting library version).

#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

Raises:

[ GitHub ]

  
# File 'lib/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

gem_original_require (private)

[ GitHub ]

  
# File 'lib/rubygems/core_ext/kernel_require.rb', line 16

alias gem_original_require require

require(path) (private)

When RubyGems is required, #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.

[ GitHub ]

  
# File 'lib/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