模块 Kernel

私有实例方法

gem(gem_name, *requirements) 点击切换源代码

使用 Kernel#gem 来激活 gem_name 的特定版本。

requirements 是指定的 gem 必须匹配的版本要求列表,最常见的是 “= example.version.number”。有关如何指定版本要求,请参阅 Gem::Requirement

如果要激活 gem 的最新版本,则无需调用 Kernel#gemKernel#require 会为您做正确的事情。

如果 gem 已激活,Kernel#gem 返回 true,否则返回 false。如果找不到 gem,不符合版本要求,或者已激活了不同的版本,则会引发异常。

应该在任何 require 语句之前调用 Kernel#gem (否则 RubyGems 可能会加载冲突的库版本)。

Kernel#gem 仅在给出预发布版 requirements 时才加载预发布版本

gem 'rake', '>= 1.1.a', '< 2'

在较旧的 RubyGems 版本中,可以使用环境变量 GEM_SKIP 来跳过指定 gem 的激活,例如,测试尚未安装的更改。现在,RubyGems 会推迟到 -I 和 RUBYLIB 环境变量,以跳过 gem 的激活。

示例

GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb
# File rubygems/core_ext/kernel_gem.rb, line 35
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.is_a? 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
require(path) 点击切换源代码

当 require RubyGems 时,Kernel#require 会被我们自己的版本替换,该版本能够按需加载 gem。

当您调用 require 'x' 时,会发生以下情况

  • 如果可以从现有的 Ruby 加载路径加载该文件,则会加载。

  • 否则,将在已安装的 gem 中搜索匹配的文件。如果它在 gem 'y' 中找到,则会激活该 gem(添加到加载路径)。

如果该文件已被加载,则会保留返回 false 的正常 require 功能。

# File rubygems/core_ext/kernel_require.rb, line 36
def require(path) # :doc:
  return gem_original_require(path) unless Gem.discover_gems_on_require

  RUBYGEMS_ACTIVATION_MONITOR.synchronize do
    path = File.path(path)

    # If +path+ belongs to a default gem, we activate it and then go straight
    # to normal require

    if spec = Gem.find_default_spec(path)
      name = spec.name

      next if Gem.loaded_specs[name]

      # 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.find do |s|
          $LOAD_PATH[0...load_path_check_index].find do |lp|
            if File.symlink? lp # for backward compatibility
              next
            end

            full_path = File.expand_path(File.join(lp, "#{path}#{s}"))
            rp = full_path if File.file?(full_path)
          end
        end
        rp
      end

      Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless
        resolved_path

      next
    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?
      next
    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)
      next
    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(&:activate)

    # 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
        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
        raise le
      end

      valid.activate
    end
  end

  begin
    gem_original_require(path)
  rescue LoadError => load_error
    if load_error.path == path &&
       RUBYGEMS_ACTIVATION_MONITOR.synchronize { Gem.try_activate(path) }

      return gem_original_require(path)
    end

    raise load_error
  end
end