From: Evan Phoenix Date: 2010-04-16T03:08:27+09:00 Subject: [ruby-core:29537] Re: [Bug #3140] gem activation has changed between 1.8 and 1.9 --Apple-Mail-2-989924298 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=us-ascii On Apr 14, 2010, at 8:36 PM, Rich Kilmer wrote: > I wrote this original code in gem_prelude. >=20 > The intent of this was threefold: >=20 > 1) Make RubyGems packaged files accessible with require/load in 1.9 > without having to require 'rubygems' > 2) Minimize startup time (don't load the full rubygems library if its > not going to be used) > 3) Load rubygems proper if need be. >=20 > I would prefer C. I know we don't have a lot of time before 1.9.2 but > if we get our ducks in a row we can make this work...it does need to > be fixed. I've gone ahead and implemented an option D, which is very similar to = the original suggestion made for RubyGems back in 2007 = (blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/12792). This moves the RubyGem custom require into the prelude and triggers the = full loading of RubyGems itself if the normal require raises a = LoadError. It fixes the problem because the highest version gems are no = longer pushed on $LOAD_PATH, allowing RubyGems to activate gems and the = proper dependencies. This isn't as nice as Rich's suggestions, but it's possible to do easily = without changes load.c. I've attached the patch for comment. Thanks! - Evan --Apple-Mail-2-989924298 Content-Disposition: attachment; filename=gems-load.diff Content-Type: application/octet-stream; name="gems-load.diff" Content-Transfer-Encoding: 7bit Index: gem_prelude.rb =================================================================== --- gem_prelude.rb (revision 27351) +++ gem_prelude.rb (working copy) @@ -1,23 +1,15 @@ -# depends on: array.rb dir.rb env.rb file.rb hash.rb module.rb regexp.rb # vim: filetype=ruby +# WARN # NOTICE: Ruby is during initialization here. # * Encoding.default_external does not reflects -E. # * Should not expect Encoding.default_internal. # * Locale encoding is available. +# The implementation must define the Gem module before loading this prelude +# to indicate that it wants the prelude to introduce rubygems functionality. if defined?(Gem) then - # :stopdoc: - - module Kernel - - def gem(gem_name, *version_requirements) - Gem.push_gem_version_on_load_path(gem_name, *version_requirements) - end - private :gem - end - module Gem ConfigMap = { @@ -66,8 +58,8 @@ end def self.set_home(home) - home = home.dup.force_encoding(Encoding.find('filesystem')) - home.gsub!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR + home = home.dup.force_encoding Encoding.find('filesystem') + home.gsub! File::ALT_SEPARATOR, File::SEPARATOR if File::ALT_SEPARATOR @gem_home = home end @@ -88,11 +80,12 @@ end @gem_path.uniq! - @gem_path.map!{|x|x.force_encoding(Encoding.find('filesystem'))} + @gem_path.map! { |dir| dir.force_encoding Encoding.find('filesystem') } end def self.user_home - @user_home ||= File.expand_path("~").force_encoding(Encoding.find('filesystem')) + @user_home ||= File.expand_path("~").force_encoding \ + Encoding.find('filesystem') rescue if File::ALT_SEPARATOR then "C:/" @@ -106,7 +99,6 @@ require 'lib/rubygems/defaults.rb' # end rubygems/defaults - ## # Methods before this line will be removed when QuickLoader is replaced # with the real RubyGems @@ -135,6 +127,7 @@ module QuickLoader @loaded_full_rubygems_library = false + @loading_full_rubygems = false def self.load_full_rubygems_library return if @loaded_full_rubygems_library @@ -154,120 +147,52 @@ end $".delete path_to_full_rubygems_library + $".each do |path| - if /#{Regexp.escape File::SEPARATOR}rubygems\.rb\z/ =~ path + if /#{Regexp.escape File::SEPARATOR}rubygems\.rb\z/ =~ path then raise LoadError, "another rubygems is already loaded from #{path}" end end + + @loading_full_rubygems = true require 'rubygems' + @loading_full_rubygems = false end def self.fake_rubygems_as_loaded path = path_to_full_rubygems_library - $" << path unless $".include?(path) + $" << path unless $".include? path end def self.path_to_full_rubygems_library - installed_path = File.join(Gem::ConfigMap[:rubylibprefix], Gem::ConfigMap[:ruby_version]) - if $:.include?(installed_path) - return File.join(installed_path, 'rubygems.rb') + installed_path = File.join(Gem::ConfigMap[:rubylibprefix], + Gem::ConfigMap[:ruby_version]) + + if $:.include? installed_path then + File.join installed_path, 'rubygems.rb' else # e.g., on test-all $:.each do |dir| - if File.exist?( path = File.join(dir, 'rubygems.rb') ) - return path - end + path = File.join(dir, 'rubygems.rb') + return path if File.exist?(path) end + raise LoadError, 'rubygems.rb' end end - GemPaths = {} - GemVersions = {} - def push_gem_version_on_load_path(gem_name, *version_requirements) - if version_requirements.empty? - unless GemPaths.has_key?(gem_name) then - raise Gem::LoadError, "Could not find RubyGem #{gem_name} (>= 0)\n" - end + QuickLoader.load_full_rubygems_library + gem gem_name, *version_requirements + end - # highest version gems already active - return false - else - if version_requirements.length > 1 then - QuickLoader.load_full_rubygems_library - return gem(gem_name, *version_requirements) - end - - requirement, version = version_requirements[0].split - requirement.strip! - - if loaded_version = GemVersions[gem_name] then - case requirement - when ">", ">=" then - return false if - (loaded_version <=> Gem.integers_for(version)) >= 0 - when "~>" then - required_version = Gem.integers_for version - - return false if loaded_version.first == required_version.first - end - end - + def self.try_require(path) + unless @loaded_full_rubygems_library QuickLoader.load_full_rubygems_library - gem gem_name, *version_requirements end - end - def integers_for(gem_version) - numbers = gem_version.split(".").collect {|n| n.to_i} - numbers.pop while numbers.last == 0 - numbers << 0 if numbers.empty? - numbers - end + return false if @loading_full_rubygems - def push_all_highest_version_gems_on_load_path - Gem.path.each do |path| - gems_directory = File.join(path, "gems") - - if File.exist?(gems_directory) then - Dir.entries(gems_directory).each do |gem_directory_name| - next if gem_directory_name == "." || gem_directory_name == ".." - - next unless gem_name = gem_directory_name[/(.*)-(.*)/, 1] - new_version = integers_for($2) - current_version = GemVersions[gem_name] - - if !current_version or (current_version <=> new_version) < 0 then - GemVersions[gem_name] = new_version - GemPaths[gem_name] = File.join(gems_directory, gem_directory_name) - end - end - end - end - - require_paths = [] - - GemPaths.each_value do |path| - if File.exist?(file = File.join(path, ".require_paths")) then - paths = File.read(file).split.map do |require_path| - File.join path, require_path - end - - require_paths.concat paths - else - require_paths << file if File.exist?(file = File.join(path, "bin")) - require_paths << file if File.exist?(file = File.join(path, "lib")) - end - end - - # "tag" the first require_path inserted into the $LOAD_PATH to enable - # indexing correctly with rubygems proper when it inserts an explicitly - # gem version - unless require_paths.empty? then - require_paths.first.instance_variable_set(:@gem_prelude_index, true) - end - # gem directories must come after -I and ENV['RUBYLIB'] - $:[$:.index{|e|e.instance_variable_defined?(:@gem_prelude_index)}||-1,0] = require_paths + Gem.try_require(path) end def const_missing(constant) @@ -291,8 +216,53 @@ end + # :stopdoc: + + module Kernel + + def gem(gem_name, *version_requirements) + Gem.push_gem_version_on_load_path(gem_name, *version_requirements) + end + + private :gem + + ## + # The Kernel#require from before RubyGems was loaded. + + alias gem_original_require require + + ## + # 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. + + def require(path) # :doc: + gem_original_require path + rescue LoadError => load_error + if load_error.message =~ /#{Regexp.escape path}\z/ then + unless Gem::QuickLoader.try_require(path) + raise load_error + end + end + + raise load_error + end + + private :require + private :gem_original_require + + end + begin - Gem.push_all_highest_version_gems_on_load_path Gem::QuickLoader.fake_rubygems_as_loaded rescue Exception => e puts "Error loading gem paths on load path in gem_prelude" Index: lib/rubygems/custom_require.rb =================================================================== --- lib/rubygems/custom_require.rb (revision 27351) +++ lib/rubygems/custom_require.rb (working copy) @@ -6,6 +6,27 @@ require 'rubygems' +if defined? Gem::QuickLoader + +module Gem + + ## + # + # Called from the gem_prelude to attempt to activate +path+ + + def self.try_require(path) # :doc: + spec = Gem.searcher.find(path) + return false unless spec + + Gem.activate(spec.name, "= #{spec.version}") + gem_original_require path + + return true + end +end + +else + module Kernel ## @@ -44,3 +65,4 @@ end +end Index: lib/rubygems.rb =================================================================== --- lib/rubygems.rb (revision 27351) +++ lib/rubygems.rb (working copy) @@ -1063,6 +1063,7 @@ end require 'rubygems/config_file' +require 'rubygems/custom_require' Gem.clear_paths --Apple-Mail-2-989924298--