1# frozen_string_literal: true 2#-- 3# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. 4# All rights reserved. 5# See LICENSE.txt for permissions. 6#++ 7 8require 'monitor' 9 10module Kernel 11 12 RUBYGEMS_ACTIVATION_MONITOR = Monitor.new # :nodoc: 13 14 # Make sure we have a reference to Ruby's original Kernel#require 15 unless defined?(gem_original_require) 16 alias gem_original_require require 17 private :gem_original_require 18 end 19 20 ## 21 # When RubyGems is required, Kernel#require is replaced with our own which 22 # is capable of loading gems on demand. 23 # 24 # When you call <tt>require 'x'</tt>, this is what happens: 25 # * If the file can be loaded from the existing Ruby loadpath, it 26 # is. 27 # * Otherwise, installed gems are searched for a file that matches. 28 # If it's found in gem 'y', that gem is activated (added to the 29 # loadpath). 30 # 31 # The normal <tt>require</tt> functionality of returning false if 32 # that file has already been loaded is preserved. 33 34 def require(path) 35 RUBYGEMS_ACTIVATION_MONITOR.enter 36 37 path = path.to_path if path.respond_to? :to_path 38 39 if spec = Gem.find_unresolved_default_spec(path) 40 Gem.remove_unresolved_default_spec(spec) 41 begin 42 Kernel.send(:gem, spec.name) 43 rescue Exception 44 RUBYGEMS_ACTIVATION_MONITOR.exit 45 raise 46 end 47 end 48 49 # If there are no unresolved deps, then we can use just try 50 # normal require handle loading a gem from the rescue below. 51 52 if Gem::Specification.unresolved_deps.empty? 53 RUBYGEMS_ACTIVATION_MONITOR.exit 54 return gem_original_require(path) 55 end 56 57 # If +path+ is for a gem that has already been loaded, don't 58 # bother trying to find it in an unresolved gem, just go straight 59 # to normal require. 60 #-- 61 # TODO request access to the C implementation of this to speed up RubyGems 62 63 if Gem::Specification.find_active_stub_by_path(path) 64 RUBYGEMS_ACTIVATION_MONITOR.exit 65 return gem_original_require(path) 66 end 67 68 # Attempt to find +path+ in any unresolved gems... 69 70 found_specs = Gem::Specification.find_in_unresolved path 71 72 # If there are no directly unresolved gems, then try and find +path+ 73 # in any gems that are available via the currently unresolved gems. 74 # For example, given: 75 # 76 # a => b => c => d 77 # 78 # If a and b are currently active with c being unresolved and d.rb is 79 # requested, then find_in_unresolved_tree will find d.rb in d because 80 # it's a dependency of c. 81 # 82 if found_specs.empty? 83 found_specs = Gem::Specification.find_in_unresolved_tree path 84 85 found_specs.each do |found_spec| 86 found_spec.activate 87 end 88 89 # We found +path+ directly in an unresolved gem. Now we figure out, of 90 # the possible found specs, which one we should activate. 91 else 92 93 # Check that all the found specs are just different 94 # versions of the same gem 95 names = found_specs.map(&:name).uniq 96 97 if names.size > 1 98 RUBYGEMS_ACTIVATION_MONITOR.exit 99 raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}" 100 end 101 102 # Ok, now find a gem that has no conflicts, starting 103 # at the highest version. 104 valid = found_specs.find { |s| !s.has_conflicts? } 105 106 unless valid 107 le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" 108 le.name = names.first 109 RUBYGEMS_ACTIVATION_MONITOR.exit 110 raise le 111 end 112 113 valid.activate 114 end 115 116 RUBYGEMS_ACTIVATION_MONITOR.exit 117 return gem_original_require(path) 118 rescue LoadError => load_error 119 RUBYGEMS_ACTIVATION_MONITOR.enter 120 121 begin 122 if load_error.message.start_with?("Could not find") or 123 (load_error.message.end_with?(path) and Gem.try_activate(path)) 124 require_again = true 125 end 126 ensure 127 RUBYGEMS_ACTIVATION_MONITOR.exit 128 end 129 130 return gem_original_require(path) if require_again 131 132 raise load_error 133 end 134 135 private :require 136 137end 138