Fix up Rubinius specific library specs.
[rbx.git] / lib / rubygems / dependency_installer.rb
blob8636599fbc20d8b15e1dd7820270c85cf36314ba
1 require 'rubygems'
2 require 'rubygems/dependency_list'
3 require 'rubygems/installer'
4 require 'rubygems/spec_fetcher'
5 require 'rubygems/user_interaction'
7 ##
8 # Installs a gem along with all its dependencies from local and remote gems.
10 class Gem::DependencyInstaller
12   include Gem::UserInteraction
14   attr_reader :gems_to_install
15   attr_reader :installed_gems
17   DEFAULT_OPTIONS = {
18     :env_shebang => false,
19     :domain => :both, # HACK dup
20     :force => false,
21     :format_executable => false, # HACK dup
22     :ignore_dependencies => false,
23     :security_policy => nil, # HACK NoSecurity requires OpenSSL.  AlmostNo? Low?
24     :wrappers => true
25   }
27   ##
28   # Creates a new installer instance.
29   #
30   # Options are:
31   # :cache_dir:: Alternate repository path to store .gem files in.
32   # :domain:: :local, :remote, or :both.  :local only searches gems in the
33   #           current directory.  :remote searches only gems in Gem::sources.
34   #           :both searches both.
35   # :env_shebang:: See Gem::Installer::new.
36   # :force:: See Gem::Installer#install.
37   # :format_executable:: See Gem::Installer#initialize.
38   # :ignore_dependencies:: Don't install any dependencies.
39   # :install_dir:: See Gem::Installer#install.
40   # :security_policy:: See Gem::Installer::new and Gem::Security.
41   # :wrappers:: See Gem::Installer::new
43   def initialize(options = {})
44     options = DEFAULT_OPTIONS.merge options
46     @bin_dir = options[:bin_dir]
47     @development = options[:development]
48     @domain = options[:domain]
49     @env_shebang = options[:env_shebang]
50     @force = options[:force]
51     @format_executable = options[:format_executable]
52     @ignore_dependencies = options[:ignore_dependencies]
53     @security_policy = options[:security_policy]
54     @wrappers = options[:wrappers]
56     @installed_gems = []
58     @install_dir = options[:install_dir] || Gem.dir
59     @cache_dir = options[:cache_dir] || @install_dir
61     if options[:install_dir] then
62       spec_dir = File.join @install_dir, 'specifications'
63       @source_index = Gem::SourceIndex.from_gems_in spec_dir
64     else
65       @source_index = Gem.source_index
66     end
67   end
69   ##
70   # Returns a list of pairs of gemspecs and source_uris that match
71   # Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources)
72   # sources.  Gems are sorted with newer gems prefered over older gems, and
73   # local gems preferred over remote gems.
75   def find_gems_with_sources(dep)
76     gems_and_sources = []
78     if @domain == :both or @domain == :local then
79       Dir[File.join(Dir.pwd, "#{dep.name}-[0-9]*.gem")].each do |gem_file|
80         spec = Gem::Format.from_file_by_path(gem_file).spec
81         gems_and_sources << [spec, gem_file] if spec.name == dep.name
82       end
83     end
85     if @domain == :both or @domain == :remote then
86       begin
87         requirements = dep.version_requirements.requirements.map do |req, ver|
88           req
89         end
91         all = requirements.length > 1 ||
92                 (requirements.first != ">=" and requirements.first != ">")
94         found = Gem::SpecFetcher.fetcher.fetch dep, all
95         gems_and_sources.push(*found)
97       rescue Gem::RemoteFetcher::FetchError => e
98         if Gem.configuration.really_verbose then
99           say "Error fetching remote data:\t\t#{e.message}"
100           say "Falling back to local-only install"
101         end
102         @domain = :local
103       end
104     end
106     gems_and_sources.sort_by do |gem, source|
107       [gem, source =~ /^http:\/\// ? 0 : 1] # local gems win
108     end
109   end
111   ##
112   # Gathers all dependencies necessary for the installation from local and
113   # remote sources unless the ignore_dependencies was given.
115   def gather_dependencies
116     specs = @specs_and_sources.map { |spec,_| spec }
118     dependency_list = Gem::DependencyList.new
119     dependency_list.add(*specs)
121     unless @ignore_dependencies then
122       to_do = specs.dup
123       seen = {}
125       until to_do.empty? do
126         spec = to_do.shift
127         next if spec.nil? or seen[spec.name]
128         seen[spec.name] = true
130         deps = spec.runtime_dependencies
131         deps |= spec.development_dependencies if @development
133         deps.each do |dep|
134           results = find_gems_with_sources(dep).reverse
136           results.reject! do
137             @source_index.any? do |_, installed_spec|
138               dep.name == installed_spec.name and
139                 dep.version_requirements.satisfied_by? installed_spec.version
140             end
141           end
143           results.each do |dep_spec, source_uri|
144             next if seen[dep_spec.name]
145             @specs_and_sources << [dep_spec, source_uri]
146             dependency_list.add dep_spec
147             to_do.push dep_spec
148           end
149         end
150       end
151     end
153     @gems_to_install = dependency_list.dependency_order.reverse
154   end
156   ##
157   # Finds a spec and the source_uri it came from for gem +gem_name+ and
158   # +version+.  Returns an Array of specs and sources required for
159   # installation of the gem.
161   def find_spec_by_name_and_version gem_name, version = Gem::Requirement.default
162     spec_and_source = nil
164     glob = if File::ALT_SEPARATOR then
165              gem_name.gsub File::ALT_SEPARATOR, File::SEPARATOR
166            else
167              gem_name
168            end
170     local_gems = Dir["#{glob}*"].sort.reverse
172     unless local_gems.empty? then
173       local_gems.each do |gem_file|
174         next unless gem_file =~ /gem$/
175         begin
176           spec = Gem::Format.from_file_by_path(gem_file).spec
177           spec_and_source = [spec, gem_file]
178           break
179         rescue SystemCallError, Gem::Package::FormatError
180         end
181       end
182     end
184     if spec_and_source.nil? then
185       dep = Gem::Dependency.new gem_name, version
186       spec_and_sources = find_gems_with_sources(dep).reverse
188       spec_and_source = spec_and_sources.find { |spec, source|
189         Gem::Platform.match spec.platform
190       }
191     end
193     if spec_and_source.nil? then
194       raise Gem::GemNotFoundException,
195         "could not find gem #{gem_name} locally or in a repository"
196     end
198     @specs_and_sources = [spec_and_source]
199   end
201   ##
202   # Installs the gem and all its dependencies.  Returns an Array of installed
203   # gems specifications.
205   def install dep_or_name, version = Gem::Requirement.default
206     if String === dep_or_name then
207       find_spec_by_name_and_version dep_or_name, version
208     else
209       @specs_and_sources = [find_gems_with_sources(dep_or_name).last]
210     end
212     @installed_gems = []
214     gather_dependencies
216     @gems_to_install.each do |spec|
217       last = spec == @gems_to_install.last
218       # HACK is this test for full_name acceptable?
219       next if @source_index.any? { |n,_| n == spec.full_name } and not last
221       # TODO: make this sorta_verbose so other users can benefit from it
222       say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose
224       _, source_uri = @specs_and_sources.assoc spec
225       begin
226         local_gem_path = Gem::RemoteFetcher.fetcher.download spec, source_uri,
227                                                              @cache_dir
228       rescue Gem::RemoteFetcher::FetchError
229         next if @force
230         raise
231       end
233       inst = Gem::Installer.new local_gem_path,
234                                 :env_shebang => @env_shebang,
235                                 :force => @force,
236                                 :format_executable => @format_executable,
237                                 :ignore_dependencies => @ignore_dependencies,
238                                 :install_dir => @install_dir,
239                                 :security_policy => @security_policy,
240                                 :wrappers => @wrappers,
241                                 :bin_dir => @bin_dir,
242                                 :development => @development
244       spec = inst.install
246       @installed_gems << spec
247     end
249     @installed_gems
250   end