rubygems-update 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubygems-update might be problematic. Click here for more details.

Files changed (105) hide show
  1. data/.document +4 -0
  2. data/ChangeLog +458 -0
  3. data/GPL.txt +340 -0
  4. data/LICENSE.txt +53 -0
  5. data/Rakefile +26 -10
  6. data/TODO +0 -1
  7. data/bin/gem_server +2 -434
  8. data/bin/gemlock +1 -1
  9. data/bin/index_gem_repository.rb +34 -12
  10. data/examples/application/an-app.gemspec +4 -2
  11. data/examples/application/ext/Makefile +139 -0
  12. data/examples/application/ext/extconf.rb +3 -0
  13. data/examples/application/ext/foo.c +1 -0
  14. data/lib/gemconfigure.rb +2 -2
  15. data/lib/rubygems.rb +85 -46
  16. data/lib/rubygems/cmd_manager.rb +14 -11
  17. data/lib/rubygems/command.rb +18 -9
  18. data/lib/rubygems/config_file.rb +17 -16
  19. data/lib/rubygems/custom_require.rb +7 -12
  20. data/lib/rubygems/dependency_list.rb +38 -38
  21. data/lib/rubygems/gem_commands.rb +471 -242
  22. data/lib/rubygems/gem_openssl.rb +1 -1
  23. data/lib/rubygems/gem_runner.rb +2 -1
  24. data/lib/rubygems/installer.rb +189 -143
  25. data/lib/rubygems/package.rb +625 -621
  26. data/lib/rubygems/remote_fetcher.rb +142 -0
  27. data/lib/rubygems/remote_installer.rb +85 -465
  28. data/lib/rubygems/rubygems_version.rb +1 -1
  29. data/lib/rubygems/security.rb +54 -11
  30. data/lib/rubygems/server.rb +486 -0
  31. data/lib/rubygems/source_index.rb +187 -21
  32. data/lib/rubygems/source_info_cache.rb +153 -0
  33. data/lib/rubygems/source_info_cache_entry.rb +31 -0
  34. data/lib/rubygems/specification.rb +71 -30
  35. data/lib/rubygems/user_interaction.rb +22 -20
  36. data/lib/rubygems/validator.rb +8 -7
  37. data/lib/rubygems/version.rb +32 -17
  38. data/pkgs/sources/sources-0.0.1.gem +0 -0
  39. data/post-install.rb +42 -3
  40. data/scripts/gemdoc.rb +3 -3
  41. data/scripts/runtest.rb +1 -1
  42. data/scripts/upload_gemdoc.rb +11 -11
  43. data/setup.rb +14 -7
  44. data/test/brokenbuildgem.rb +35 -0
  45. data/test/data/a-0.0.1.gem +0 -0
  46. data/test/data/a-0.0.2.gem +0 -0
  47. data/test/data/b-0.0.2.gem +0 -0
  48. data/test/data/broken-1.0.0.gem +0 -0
  49. data/test/data/broken_build/broken-build.gemspec +20 -0
  50. data/test/data/broken_build/ext/extconf.rb +3 -0
  51. data/test/data/broken_build/ext/foo.c +1 -0
  52. data/test/data/c-1.2.gem +0 -0
  53. data/test/data/gemhome/cache/a-0.0.1.gem +0 -0
  54. data/test/data/gemhome/cache/a-0.0.2.gem +0 -0
  55. data/test/data/gemhome/cache/b-0.0.2.gem +0 -0
  56. data/test/data/gemhome/cache/c-1.2.gem +0 -0
  57. data/test/data/gemhome/gems/a-0.0.1/lib/code.rb +0 -6
  58. data/test/data/gemhome/gems/a-0.0.2/lib/code.rb +0 -6
  59. data/test/data/gemhome/gems/b-0.0.2/lib/code.rb +0 -6
  60. data/test/data/gemhome/gems/c-1.2/lib/code.rb +0 -6
  61. data/test/data/gemhome/specifications/a-0.0.1.gemspec +1 -1
  62. data/test/data/gemhome/specifications/a-0.0.2.gemspec +1 -1
  63. data/test/data/gemhome/specifications/b-0.0.2.gemspec +1 -1
  64. data/test/data/gemhome/specifications/c-1.2.gemspec +1 -1
  65. data/test/data/lib/code.rb +0 -6
  66. data/test/data/one/one-0.0.1.gem +0 -0
  67. data/test/functional.rb +24 -21
  68. data/test/functional_extension_gems.rb +48 -0
  69. data/test/functional_generate_yaml_index.rb +6 -3
  70. data/test/gemenvironment.rb +27 -22
  71. data/test/gemutilities.rb +95 -5
  72. data/test/insure_session.rb +2 -2
  73. data/test/io_capture.rb +33 -0
  74. data/test/test_check_command.rb +2 -2
  75. data/test/test_command.rb +16 -13
  76. data/test/test_dependency_list.rb +20 -20
  77. data/test/test_file_list.rb +10 -11
  78. data/test/test_format.rb +19 -46
  79. data/test/test_gem.rb +25 -0
  80. data/test/test_gem_ext_configure_builder.rb +88 -0
  81. data/test/test_gem_ext_ext_conf_builder.rb +122 -0
  82. data/test/test_gem_ext_rake_builder.rb +61 -0
  83. data/test/test_gem_outdated_command.rb +37 -0
  84. data/test/test_gem_source_info_cache.rb +196 -0
  85. data/test/test_gem_source_info_cache_entry.rb +44 -0
  86. data/test/test_gem_sources_command.rb +130 -0
  87. data/test/test_installer.rb +137 -9
  88. data/test/test_package.rb +521 -531
  89. data/test/test_parse_commands.rb +7 -7
  90. data/test/test_process_commands.rb +1 -1
  91. data/test/test_remote_fetcher.rb +187 -45
  92. data/test/test_remote_installer.rb +35 -52
  93. data/test/test_require_gem.rb +12 -12
  94. data/test/test_source_index.rb +194 -48
  95. data/test/test_specification.rb +154 -0
  96. data/test/test_user_interaction.rb +48 -0
  97. data/test/test_validator.rb +1 -1
  98. data/test/test_version_comparison.rb +116 -5
  99. metadata +33 -10
  100. data/lib/rubygems/incremental_fetcher.rb +0 -136
  101. data/lib/rubygems/loadpath_manager.rb +0 -114
  102. data/lib/rubygems/open-uri.rb +0 -756
  103. data/test/test_cached_fetcher.rb +0 -64
  104. data/test/test_incremental_fetcher.rb +0 -175
  105. data/test/test_local_cache.rb +0 -106
@@ -0,0 +1,142 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'yaml'
4
+ require 'zlib'
5
+
6
+ require 'rubygems'
7
+ require 'rubygems/user_interaction'
8
+
9
+ ##
10
+ # Represents an error communicating via HTTP.
11
+
12
+ class Gem::RemoteSourceException < Gem::Exception; end
13
+
14
+ ##
15
+ # RemoteFetcher handles the details of fetching gems and gem information from
16
+ # a remote source.
17
+
18
+ class Gem::RemoteFetcher
19
+
20
+ class FetchError < Gem::Exception; end
21
+
22
+ @fetcher = nil
23
+
24
+ # Cached RemoteFetcher instance.
25
+ def self.fetcher
26
+ @fetcher ||= new Gem.configuration[:http_proxy]
27
+ end
28
+
29
+ # Initialize a remote fetcher using the source URI and possible proxy
30
+ # information.
31
+ #
32
+ # +proxy+
33
+ # * [String]: explicit specification of proxy; overrides any environment
34
+ # variable setting
35
+ # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER,
36
+ # HTTP_PROXY_PASS)
37
+ # * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy
38
+ def initialize(proxy)
39
+ @proxy_uri =
40
+ case proxy
41
+ when :no_proxy then nil
42
+ when nil then get_proxy_from_env
43
+ else URI.parse(proxy.to_str)
44
+ end
45
+ end
46
+
47
+ # Downloads +uri+.
48
+ def fetch_path(uri)
49
+ open_uri_or_path(uri) do |input|
50
+ input.read
51
+ end
52
+ rescue Timeout::Error
53
+ raise FetchError, "timed out fetching #{uri}"
54
+ rescue IOError, SocketError, SystemCallError => e
55
+ raise FetchError, "#{e.class} reading #{uri}"
56
+ rescue
57
+ old_uri = uri
58
+ uri = uri.downcase
59
+ retry if old_uri != uri
60
+ raise
61
+ end
62
+
63
+ # Returns the size of +uri+ in bytes.
64
+ def fetch_size(uri)
65
+ return File.size(get_file_uri_path(uri)) if file_uri?(uri)
66
+ require 'net/http'
67
+ require 'uri'
68
+ u = URI.parse(uri)
69
+ raise ArgumentError, 'uri is not an HTTP URI' unless URI::HTTP === u
70
+ http = connect_to(u.host, u.port)
71
+ resp = http.head(u.request_uri)
72
+ raise Gem::RemoteSourceException, "HTTP Response #{resp.code}" if resp.code !~ /^2/
73
+ resp['content-length'].to_i
74
+ rescue SocketError, SystemCallError, Timeout::Error => e
75
+ raise FetchError, "#{e.message}(#{e.class})"
76
+ end
77
+
78
+ private
79
+
80
+ def escape(str)
81
+ return unless str
82
+ URI.escape(str)
83
+ end
84
+
85
+ def unescape(str)
86
+ return unless str
87
+ URI.unescape(str)
88
+ end
89
+
90
+ # Returns an HTTP proxy URI if one is set in the environment variables.
91
+ def get_proxy_from_env
92
+ env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
93
+ uri = env_proxy ? URI.parse(env_proxy) : nil
94
+ if uri and uri.user.nil? and uri.password.nil? then
95
+ # Probably we have http_proxy_* variables?
96
+ uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER'])
97
+ uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS'])
98
+ end
99
+ uri
100
+ end
101
+
102
+ # Normalize the URI by adding "http://" if it is missing.
103
+ def normalize_uri(uri)
104
+ (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}"
105
+ end
106
+
107
+ # Connect to the source host/port, using a proxy if needed.
108
+ def connect_to(host, port)
109
+ if @proxy_uri
110
+ Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, unescape(@proxy_uri.user), unescape(@proxy_uri.password)).new(host, port)
111
+ else
112
+ Net::HTTP.new(host, port)
113
+ end
114
+ end
115
+
116
+ # Read the data from the (source based) URI, but if it is a file:// URI,
117
+ # read from the filesystem instead.
118
+ def open_uri_or_path(uri, &block)
119
+ require 'open-uri'
120
+ if file_uri?(uri)
121
+ open(get_file_uri_path(uri), &block)
122
+ else
123
+ connection_options = {
124
+ "User-Agent" => "RubyGems/#{Gem::RubyGemsVersion}",
125
+ :proxy => @proxy_uri,
126
+ }
127
+ open(uri, connection_options, &block)
128
+ end
129
+ end
130
+
131
+ # Checks if the provided string is a file:// URI.
132
+ def file_uri?(uri)
133
+ uri =~ %r{\Afile://}
134
+ end
135
+
136
+ # Given a file:// URI, returns its local path.
137
+ def get_file_uri_path(uri)
138
+ uri.sub(%r{\Afile://}, '')
139
+ end
140
+
141
+ end
142
+
@@ -4,402 +4,23 @@
4
4
  # See LICENSE.txt for permissions.
5
5
  #++
6
6
 
7
- require 'rubygems'
8
- require 'socket'
9
7
  require 'fileutils'
8
+ require 'yaml'
9
+
10
+ require 'rubygems'
11
+ require 'rubygems/installer'
12
+ require 'rubygems/source_info_cache'
13
+
14
+ require 'sources'
10
15
 
11
16
  module Gem
12
17
  class DependencyError < Gem::Exception; end
13
- class RemoteSourceException < Gem::Exception; end
14
18
  class GemNotFoundException < Gem::Exception; end
15
19
  class RemoteInstallationCancelled < Gem::Exception; end
16
-
17
- ####################################################################
18
- # RemoteSourceFetcher handles the details of fetching gems and gem
19
- # information from a remote source.
20
- class RemoteSourceFetcher
21
- include UserInteraction
22
-
23
- # Initialize a remote fetcher using the source URI (and possible
24
- # proxy information).
25
- # +proxy+
26
- # * [String]: explicit specification of proxy; overrides any
27
- # environment variable setting
28
- # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, HTTP_PROXY_PASS)
29
- # * <tt>:no_proxy</tt>: ignore environment variables and _don't_
30
- # use a proxy
31
- def initialize(source_uri, proxy)
32
- @uri = normalize_uri(source_uri)
33
- @proxy_uri =
34
- case proxy
35
- when :no_proxy
36
- nil
37
- when nil
38
- env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
39
- uri = env_proxy ? URI.parse(env_proxy) : nil
40
- if uri and uri.user.nil? and uri.password.nil?
41
- #Probably we have http_proxy_* variables?
42
- uri.user = ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
43
- uri.password = ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
44
- end
45
- uri
46
- else
47
- URI.parse(proxy.to_str)
48
- end
49
- end
50
-
51
- # The uncompressed +size+ of the source's directory (e.g. source
52
- # info).
53
- def size
54
- @size ||= get_size("/yaml")
55
- end
56
-
57
- # Fetch the data from the source at the given path.
58
- def fetch_path(path="")
59
- read_data(@uri + path)
60
- end
61
-
62
- # Get the source index from the gem source. The source index is a
63
- # directory of the gems available on the source, formatted as a
64
- # Gem::Cache object. The cache object allows easy searching for
65
- # gems by name and version requirement.
66
- #
67
- # Notice that the gem specs in the cache are adequate for searches
68
- # and queries, but may have some information elided (hence
69
- # "abbreviated").
70
- def source_index
71
- say "Bulk updating Gem source index for: #{@uri}"
72
- begin
73
- require 'zlib'
74
- yaml_spec = fetch_path("/yaml.Z")
75
- yaml_spec = Zlib::Inflate.inflate(yaml_spec)
76
- rescue
77
- yaml_spec = nil
78
- end
79
- begin
80
- yaml_spec = fetch_path("/yaml") unless yaml_spec
81
- convert_spec(yaml_spec)
82
- rescue SocketError => e
83
- raise RemoteSourceException.new("Error fetching remote gem cache: #{e.to_s}")
84
- end
85
- end
86
-
87
- private
88
-
89
- # Normalize the URI by adding "http://" if it is missing.
90
- def normalize_uri(uri)
91
- (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}"
92
- end
93
-
94
- # Connect to the source host/port, using a proxy if needed.
95
- def connect_to(host, port)
96
- if @proxy_uri
97
- Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password).new(host, port)
98
- else
99
- Net::HTTP.new(host, port)
100
- end
101
- end
102
-
103
- # Get the size of the (non-compressed) data from the source at the
104
- # given path.
105
- def get_size(path)
106
- read_size(@uri + path)
107
- end
108
-
109
- # Read the size of the (source based) URI using an HTTP HEAD
110
- # command.
111
- def read_size(uri)
112
- return File.size(get_file_uri_path(uri)) if is_file_uri(uri)
113
- require 'net/http'
114
- require 'uri'
115
- u = URI.parse(uri)
116
- http = connect_to(u.host, u.port)
117
- path = (u.path == "") ? "/" : u.path
118
- resp = http.head(path)
119
- fail RemoteSourceException, "HTTP Response #{resp.code}" if resp.code !~ /^2/
120
- resp['content-length'].to_i
121
- end
122
-
123
- # Read the data from the (source based) URI.
124
- def read_data(uri)
125
- begin
126
- open_uri_or_path(uri) do |input|
127
- input.read
128
- end
129
- rescue
130
- old_uri = uri
131
- uri = uri.downcase
132
- retry if old_uri != uri
133
- raise
134
- end
135
- end
136
-
137
- # Read the data from the (source based) URI, but if it is a
138
- # file:// URI, read from the filesystem instead.
139
- def open_uri_or_path(uri, &block)
140
- require 'rubygems/open-uri'
141
- if is_file_uri(uri)
142
- open(get_file_uri_path(uri), &block)
143
- else
144
- connection_options = {"User-Agent" => "RubyGems/#{Gem::RubyGemsVersion}"}
145
- if @proxy_uri
146
- http_proxy_url = "#{@proxy_uri.scheme}://#{@proxy_uri.host}:#{@proxy_uri.port}"
147
- connection_options[:proxy_http_basic_authentication] = [http_proxy_url, @proxy_uri.user||'', @proxy_uri.password||'']
148
- end
149
-
150
- open(uri, connection_options, &block)
151
- end
152
- end
153
-
154
- # Checks if the provided string is a file:// URI.
155
- def is_file_uri(uri)
156
- uri =~ %r{\Afile://}
157
- end
158
-
159
- # Given a file:// URI, returns its local path.
160
- def get_file_uri_path(uri)
161
- uri.sub(%r{\Afile://}, '')
162
- end
163
-
164
- # Convert the yamlized string spec into a real spec (actually,
165
- # these are hashes of specs.).
166
- def convert_spec(yaml_spec)
167
- YAML.load(reduce_spec(yaml_spec)) or
168
- fail "Didn't get a valid YAML document"
169
- end
170
-
171
- # This reduces the source spec in size so that YAML bugs with
172
- # large data sets will be dodged. Obviously this is a workaround,
173
- # but it allows Gems to continue to work until the YAML bug is
174
- # fixed.
175
- def reduce_spec(yaml_spec)
176
- result = ""
177
- state = :copy
178
- yaml_spec.each do |line|
179
- if state == :copy && line =~ /^\s+files:\s*$/
180
- state = :skip
181
- result << line.sub(/$/, " []")
182
- elsif state == :skip
183
- if line !~ /^\s+-/
184
- state = :copy
185
- end
186
- end
187
- result << line if state == :copy
188
- end
189
- result
190
- end
191
-
192
- class << self
193
- # Sent by the client when it is done with all the sources,
194
- # allowing any cleanup activity to take place.
195
- def finish
196
- # Nothing to do
197
- end
198
- end
199
- end
200
-
201
- ####################################################################
202
- # Entrys held by a SourceInfoCache.
203
- class SourceInfoCacheEntry
204
- # The source index for this cache entry.
205
- attr_reader :source_index
206
-
207
- # The size of the of the source entry. Used to determine if the
208
- # source index has changed.
209
- attr_reader :size
210
-
211
- # Create a cache entry.
212
- def initialize(si, size)
213
- replace_source_index(si, size)
214
- end
215
-
216
- # Replace the source index and the index size with given values.
217
- def replace_source_index(si, size)
218
- @source_index = si || SourceIndex.new({})
219
- @size = size
220
- end
221
- end
222
-
223
- ####################################################################
224
- # SourceInfoCache implements the cache management policy on where
225
- # the source info is stored on local file system. There are two
226
- # possible cache locations: (1) the system wide cache, and (2) the
227
- # user specific cache.
228
- #
229
- # * The system cache is prefered if it is writable (or can be
230
- # created).
231
- # * The user cache is used if the system cache is not writable (or
232
- # can not be created).
233
- #
234
- # Once a cache is selected, it will be used for all operations. It
235
- # will not switch between cache files dynamically.
236
- #
237
- # Cache data is a simple hash indexed by the source URI. Retrieving
238
- # and entry from the cache data will return a SourceInfoCacheEntry.
239
- #
240
- class SourceInfoCache
241
-
242
- # The most recent cache data.
243
- def cache_data
244
- @dirty = false
245
- @cache_data ||= read_cache
246
- end
247
-
248
- # Write data to the proper cache.
249
- def write_cache
250
- data = cache_data
251
- open(writable_file, "wb") do |f|
252
- f.write Marshal.dump(data)
253
- end
254
- end
255
-
256
- # The name of the system cache file.
257
- def system_cache_file
258
- @sysetm_cache ||= File.join(Gem.dir, "source_cache")
259
- end
260
-
261
- # The name of the user cache file.
262
- def user_cache_file
263
- @user_cache ||=
264
- ENV['GEMCACHE'] || File.join(Gem.user_home, ".gem/source_cache")
265
- end
266
-
267
- # Mark the cache as updated (i.e. dirty).
268
- def update
269
- @dirty = true
270
- end
271
-
272
- # Write the cache to a local file (if it is dirty).
273
- def flush
274
- write_cache if @dirty
275
- @dirty = false
276
- end
277
-
278
- private
279
-
280
- # Find a writable cache file.
281
- def writable_file
282
- @cache_file
283
- end
284
-
285
- # Read the most current cache data.
286
- def read_cache
287
- @cache_file = select_cache_file
288
- begin
289
- open(@cache_file, "rb") { |f| load_local_cache(f) } || {}
290
- rescue StandardError => ex
291
- {}
292
- end
293
- end
294
-
295
- def load_local_cache(f)
296
- Marshal.load(f)
297
- end
298
-
299
- # Select a writable cache file
300
- def select_cache_file
301
- try_file(system_cache_file) or
302
- try_file(user_cache_file) or
303
- fail "Unable to locate a writable cache file."
304
- end
305
-
306
- # Determine if +fn+ is a candidate for a cache file. Return fn if
307
- # it is. Return nil if it is not.
308
- def try_file(fn)
309
- return fn if File.writable?(fn)
310
- return nil if File.exist?(fn)
311
- dir = File.dirname(fn)
312
- if ! File.exist? dir
313
- begin
314
- FileUtils.mkdir_p(dir)
315
- rescue RuntimeError
316
- return nil
317
- end
318
- end
319
- if File.writable?(dir)
320
- FileUtils.touch fn
321
- return fn
322
- end
323
- nil
324
- end
325
- end
326
-
327
- ####################################################################
328
- # CachedFetcher is a decorator that adds local file caching to
329
- # RemoteSourceFetcher objects.
330
- class CachedFetcher
331
-
332
- # Create a cached fetcher (based on a RemoteSourceFetcher) for the
333
- # source at +source_uri+ (through the proxy +proxy+).
334
- def initialize(source_uri, proxy)
335
- require 'rubygems/incremental_fetcher'
336
- @source_uri = source_uri
337
- rsf = RemoteSourceFetcher.new(source_uri, proxy)
338
- @fetcher = IncrementalFetcher.new(source_uri, rsf, manager)
339
- end
340
-
341
- # The uncompressed +size+ of the source's directory (e.g. source
342
- # info).
343
- def size
344
- @fetcher.size
345
- end
346
-
347
- # Fetch the data from the source at the given path.
348
- def fetch_path(path="")
349
- @fetcher.fetch_path(path)
350
- end
351
-
352
- # Get the source index from the gem source. The source index is a
353
- # directory of the gems available on the source, formatted as a
354
- # Gem::Cache object. The cache object allows easy searching for
355
- # gems by name and version requirement.
356
- #
357
- # Notice that the gem specs in the cache are adequate for searches
358
- # and queries, but may have some information elided (hence
359
- # "abbreviated").
360
- def source_index
361
- cache = manager.cache_data[@source_uri]
362
- if cache && cache.size == @fetcher.size
363
- cache.source_index
364
- else
365
- result = @fetcher.source_index
366
- manager.cache_data[@source_uri] = SourceInfoCacheEntry.new(result, @fetcher.size)
367
- manager.update
368
- result
369
- end
370
- end
371
-
372
- # Flush the cache to a local file, if needed.
373
- def flush
374
- manager.flush
375
- end
376
-
377
- private
378
-
379
- # The cache manager for this cached source.
380
- def manager
381
- self.class.manager
382
- end
383
-
384
- # The cache is shared between all caching fetchers, so the cache
385
- # is put in the class object.
386
- class << self
387
-
388
- # The Cache manager for all instances of this class.
389
- def manager
390
- @manager ||= SourceInfoCache.new
391
- end
392
-
393
- # Sent by the client when it is done with all the sources,
394
- # allowing any cleanup activity to take place.
395
- def finish
396
- manager.flush
397
- end
398
- end
399
-
400
- end
20
+ class RemoteInstallationSkipped < Gem::Exception; end
401
21
 
402
22
  class RemoteInstaller
23
+
403
24
  include UserInteraction
404
25
 
405
26
  # <tt>options[:http_proxy]</tt>::
@@ -409,12 +30,10 @@ module Gem
409
30
  # * <tt>:no_proxy</tt>: ignore environment variables and _don't_
410
31
  # use a proxy
411
32
  #
33
+ # * <tt>:cache_dir</tt>: override where downloaded gems are cached.
412
34
  def initialize(options={})
413
- require 'uri'
414
-
415
- # Ensure http_proxy env vars are used if no proxy explicitly supplied.
416
35
  @options = options
417
- @fetcher_class = CachedFetcher
36
+ @source_index_hash = nil
418
37
  end
419
38
 
420
39
  # This method will install package_name onto the local system.
@@ -428,109 +47,110 @@ module Gem
428
47
  # Returns::
429
48
  # an array of Gem::Specification objects, one for each gem installed.
430
49
  #
431
- def install(gem_name, version_requirement = "> 0.0.0", force=false, install_dir=Gem.dir, install_stub=true)
50
+ def install(gem_name, version_requirement = "> 0.0.0", force=false,
51
+ install_dir=Gem.dir, install_stub=true)
432
52
  unless version_requirement.respond_to?(:satisfied_by?)
433
- version_requirement = Version::Requirement.new(version_requirement)
53
+ version_requirement = Version::Requirement.new [version_requirement]
434
54
  end
435
55
  installed_gems = []
436
- caches = source_index_hash
437
- spec, source = find_gem_to_install(gem_name, version_requirement, caches)
438
- dependencies = find_dependencies_not_installed(spec.dependencies)
439
- installed_gems << install_dependencies(dependencies, force, install_dir)
440
- cache_dir = File.join(install_dir, "cache")
441
- destination_file = File.join(cache_dir, spec.full_name + ".gem")
442
- download_gem(destination_file, source, spec)
443
- installer = new_installer(destination_file)
444
- installed_gems.unshift installer.install(force, install_dir, install_stub)
445
- installed_gems.flatten
446
- end
56
+ begin
57
+ spec, source = find_gem_to_install(gem_name, version_requirement)
58
+ dependencies = find_dependencies_not_installed(spec.dependencies)
447
59
 
448
- # Search Gem repository for a gem by specifying all or part of
449
- # the Gem's name
450
- def search(pattern_to_match)
451
- results = []
452
- caches = source_index_hash
453
- caches.each do |cache|
454
- results << cache[1].search(pattern_to_match)
455
- end
456
- results
457
- end
60
+ installed_gems << install_dependencies(dependencies, force, install_dir)
61
+
62
+ cache_dir = @options[:cache_dir] || File.join(install_dir, "cache")
63
+ destination_file = File.join(cache_dir, spec.full_name + ".gem")
64
+
65
+ download_gem(destination_file, source, spec)
458
66
 
459
- # Return a list of the sources that we can download gems from
460
- def sources
461
- unless @sources
462
- require 'sources'
463
- @sources = Gem.sources
67
+ installer = new_installer(destination_file)
68
+ installed_gems.unshift installer.install(force, install_dir, install_stub)
69
+ rescue RemoteInstallationSkipped => e
70
+ puts e.message
464
71
  end
465
- @sources
72
+ installed_gems.flatten
466
73
  end
467
-
74
+
468
75
  # Return a hash mapping the available source names to the source
469
76
  # index of that source.
470
77
  def source_index_hash
471
- result = {}
472
- sources.each do |source|
473
- result[source] = fetch_source(source)
78
+ return @source_index_hash if @source_index_hash
79
+ @source_index_hash = {}
80
+ Gem::SourceInfoCache.cache_data.each do |source_uri, sic_entry|
81
+ @source_index_hash[source_uri] = sic_entry.source_index
474
82
  end
475
- @fetcher_class.finish
476
- result
83
+ @source_index_hash
477
84
  end
478
85
 
479
- # Return the source info for the given source. The
480
- def fetch_source(source)
481
- rsf = @fetcher_class.new(source, @options[:http_proxy])
482
- rsf.source_index
483
- end
484
-
485
- # Find a gem to be installed by interacting with the user.
486
- def find_gem_to_install(gem_name, version_requirement, caches)
86
+ # Finds the Gem::Specification objects and the corresponding source URI
87
+ # for gems matching +gem_name+ and +version_requirement+
88
+ def specs_n_sources_matching(gem_name, version_requirement)
487
89
  specs_n_sources = []
488
90
 
489
- caches.each do |source, cache|
490
- cache.each do |name, spec|
491
- if /^#{gem_name}$/i === spec.name &&
492
- version_requirement.satisfied_by?(spec.version) then
493
- specs_n_sources << [spec, source]
494
- end
91
+ source_index_hash.each do |source_uri, source_index|
92
+ specs = source_index.search(/^#{Regexp.escape gem_name}$/i,
93
+ version_requirement)
94
+ # TODO move to SourceIndex#search?
95
+ ruby_version = Gem::Version.new RUBY_VERSION
96
+ specs = specs.select do |spec|
97
+ spec.required_ruby_version.nil? or
98
+ spec.required_ruby_version.satisfied_by? ruby_version
495
99
  end
100
+ specs.each { |spec| specs_n_sources << [spec, source_uri] }
496
101
  end
497
102
 
498
103
  if specs_n_sources.empty? then
499
- raise GemNotFoundException.new("Could not find #{gem_name} (#{version_requirement}) in the repository")
104
+ raise GemNotFoundException, "Could not find #{gem_name} (#{version_requirement}) in any repository"
500
105
  end
501
106
 
502
107
  specs_n_sources = specs_n_sources.sort_by { |gs,| gs.version }.reverse
503
108
 
504
- non_binary_gems = specs_n_sources.reject { |item|
109
+ specs_n_sources
110
+ end
111
+
112
+ # Find a gem to be installed by interacting with the user.
113
+ def find_gem_to_install(gem_name, version_requirement)
114
+ specs_n_sources = specs_n_sources_matching gem_name, version_requirement
115
+
116
+ top_3_versions = specs_n_sources.map{|gs| gs.first.version}.uniq[0..3]
117
+ specs_n_sources.reject!{|gs| !top_3_versions.include?(gs.first.version)}
118
+
119
+ binary_gems = specs_n_sources.reject { |item|
505
120
  item[0].platform.nil? || item[0].platform==Platform::RUBY
506
121
  }
507
122
 
508
123
  # only non-binary gems...return latest
509
- return specs_n_sources.first if non_binary_gems.empty?
124
+ return specs_n_sources.first if binary_gems.empty?
510
125
 
511
- list = specs_n_sources.collect { |item|
512
- "#{item[0].name} #{item[0].version} (#{item[0].platform.to_s})"
126
+ list = specs_n_sources.collect { |spec, source_uri|
127
+ "#{spec.name} #{spec.version} (#{spec.platform})"
513
128
  }
514
129
 
130
+ list << "Skip this gem"
515
131
  list << "Cancel installation"
516
132
 
517
133
  string, index = choose_from_list(
518
- "Select which gem to install for your platform (#{RUBY_PLATFORM})",
519
- list)
134
+ "Select which gem to install for your platform (#{RUBY_PLATFORM})",
135
+ list)
520
136
 
521
- if index == (list.size - 1) then
137
+ if index.nil? or index == (list.size - 1) then
522
138
  raise RemoteInstallationCancelled, "Installation of #{gem_name} cancelled."
523
139
  end
524
140
 
141
+ if index == (list.size - 2) then
142
+ raise RemoteInstallationSkipped, "Installation of #{gem_name} skipped."
143
+ end
144
+
525
145
  specs_n_sources[index]
526
146
  end
527
147
 
528
148
  def find_dependencies_not_installed(dependencies)
529
149
  to_install = []
530
150
  dependencies.each do |dependency|
531
- srcindex = Gem::SourceIndex.from_installed_gems
532
- matches = srcindex.find_name(dependency.name, dependency.requirement_list)
533
- to_install.push dependency if matches.empty?
151
+ srcindex = Gem::SourceIndex.from_installed_gems
152
+ matches = srcindex.find_name(dependency.name, dependency.requirement_list)
153
+ to_install.push dependency if matches.empty?
534
154
  end
535
155
  to_install
536
156
  end
@@ -544,27 +164,27 @@ module Gem
544
164
  def install_dependencies(dependencies, force, install_dir)
545
165
  return if @options[:ignore_dependencies]
546
166
  installed_gems = []
547
- dependencies.each do |dependency|
167
+ dependencies.each do |dep|
548
168
  if @options[:include_dependencies] ||
549
- ask_yes_no("Install required dependency #{dependency.name}?", true)
550
- remote_installer = RemoteInstaller.new(@options)
551
- installed_gems << remote_installer.install(
552
- dependency.name,
553
- dependency.version_requirements,
554
- force,
555
- install_dir)
169
+ ask_yes_no("Install required dependency #{dep.name}?", true)
170
+ remote_installer = RemoteInstaller.new @options
171
+ installed_gems << remote_installer.install(dep.name,
172
+ dep.version_requirements,
173
+ force, install_dir)
174
+ elsif force then
175
+ # ignore
556
176
  else
557
- raise DependencyError.new("Required dependency #{dependency.name} not installed")
177
+ raise DependencyError, "Required dependency #{dep.name} not installed"
558
178
  end
559
179
  end
560
180
  installed_gems
561
181
  end
562
182
 
563
183
  def download_gem(destination_file, source, spec)
564
- rsf = @fetcher_class.new(source, @proxy_uri)
565
- path = "/gems/#{spec.full_name}.gem"
566
- response = rsf.fetch_path(path)
567
- write_gem_to_file(response, destination_file)
184
+ return if File.exist? destination_file
185
+ uri = source + "/gems/#{spec.full_name}.gem"
186
+ response = Gem::RemoteFetcher.fetcher.fetch_path uri
187
+ write_gem_to_file response, destination_file
568
188
  end
569
189
 
570
190
  def write_gem_to_file(body, destination_file)