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
@@ -5,6 +5,7 @@
5
5
  #++
6
6
 
7
7
  require 'rubygems/user_interaction'
8
+ require 'rubygems/remote_fetcher'
8
9
 
9
10
  require 'forwardable'
10
11
  require 'digest/sha2'
@@ -24,8 +25,13 @@ module Gem
24
25
  #
25
26
  class SourceIndex
26
27
  extend Forwardable
28
+
27
29
  include Enumerable
28
30
 
31
+ include Gem::UserInteraction
32
+
33
+ INCREMENTAL_THRESHHOLD = 50
34
+
29
35
  # Class Methods. -------------------------------------------------
30
36
  class << self
31
37
  include Gem::UserInteraction
@@ -57,7 +63,7 @@ module Gem
57
63
  # List of directory paths (all ending in "../specifications").
58
64
  #
59
65
  def installed_spec_directories
60
- Gem.path.collect { |dir| File.join(dir, "specifications") }
66
+ Gem.path.collect { |dir| File.join(dir, "specifications") }
61
67
  end
62
68
 
63
69
  # Factory method to construct a source index instance for a
@@ -72,7 +78,7 @@ module Gem
72
78
  # SourceIndex instance
73
79
  #
74
80
  def from_gems_in(*spec_dirs)
75
- self.new.load_gems_in(*spec_dirs)
81
+ self.new.load_gems_in(*spec_dirs)
76
82
  end
77
83
 
78
84
  # Load a specification from a file (eval'd Ruby code)
@@ -81,22 +87,22 @@ module Gem
81
87
  # return:: Specification instance or nil if an error occurs
82
88
  #
83
89
  def load_specification(file_name)
84
- begin
85
- spec_code = File.read(file_name).untaint
86
- gemspec = eval(spec_code)
87
- if gemspec.is_a?(Gem::Specification)
88
- gemspec.loaded_from = file_name
89
- return gemspec
90
- end
91
- alert_warning "File '#{file_name}' does not evaluate to a gem specification"
92
- rescue SyntaxError => e
93
- alert_warning e
94
- alert_warning spec_code
95
- rescue Exception => e
96
- alert_warning(e.inspect.to_s + "\n" + spec_code)
97
- alert_warning "Invalid .gemspec format in '#{file_name}'"
98
- end
99
- return nil
90
+ begin
91
+ spec_code = File.read(file_name).untaint
92
+ gemspec = eval(spec_code)
93
+ if gemspec.is_a?(Gem::Specification)
94
+ gemspec.loaded_from = file_name
95
+ return gemspec
96
+ end
97
+ alert_warning "File '#{file_name}' does not evaluate to a gem specification"
98
+ rescue SyntaxError => e
99
+ alert_warning e
100
+ alert_warning spec_code
101
+ rescue Exception => e
102
+ alert_warning(e.inspect.to_s + "\n" + spec_code)
103
+ alert_warning "Invalid .gemspec format in '#{file_name}'"
104
+ end
105
+ return nil
100
106
  end
101
107
 
102
108
  end
@@ -117,13 +123,31 @@ module Gem
117
123
  # directories.
118
124
  def load_gems_in(*spec_dirs)
119
125
  @gems.clear
120
- Dir.glob("{#{spec_dirs.join(',')}}/*.gemspec").each do |file_name|
126
+ specs = Dir.glob File.join("{#{spec_dirs.join(',')}}", "*.gemspec")
127
+ specs.each do |file_name|
121
128
  gemspec = self.class.load_specification(file_name.untaint)
122
- add_spec(gemspec) if gemspec
129
+ add_spec(gemspec) if gemspec
123
130
  end
124
131
  self
125
132
  end
126
133
 
134
+ # Returns a Hash of name => Specification of the latest versions of each
135
+ # gem in this index.
136
+ def latest_specs
137
+ thin = {}
138
+
139
+ each do |full_name, spec|
140
+ name = spec.name
141
+ if thin.has_key? name then
142
+ thin[name] = spec if spec.version > thin[name].version
143
+ else
144
+ thin[name] = spec
145
+ end
146
+ end
147
+
148
+ thin
149
+ end
150
+
127
151
  # Add a gem specification to the source index.
128
152
  def add_spec(gem_spec)
129
153
  @gems[gem_spec.full_name] = gem_spec
@@ -178,7 +202,6 @@ module Gem
178
202
  # order. Empty if not found.
179
203
  #
180
204
  def search(gem_pattern, version_requirement=Version::Requirement.new(">= 0"))
181
- #FIXME - remove duplication between this and RemoteInstaller.search
182
205
  gem_pattern = /#{ gem_pattern }/i if String === gem_pattern
183
206
  version_requirement = Gem::Version::Requirement.create(version_requirement)
184
207
  result = []
@@ -197,7 +220,149 @@ module Gem
197
220
  def refresh!
198
221
  load_gems_in(self.class.installed_spec_directories)
199
222
  end
223
+
224
+ # Returns an Array of Gem::Specifications that are not up to date.
225
+ #
226
+ def outdated
227
+ remotes = Gem::SourceInfoCache.search(//)
228
+ outdateds = []
229
+
230
+ latest_specs.each do |_, local|
231
+ name = local.name
232
+ remote = remotes.select { |spec| spec.name == name }.
233
+ sort_by { |spec| spec.version }.
234
+ last
235
+ outdateds << name if remote and local.version < remote.version
236
+ end
237
+
238
+ outdateds
239
+ end
240
+
241
+ def update(source_uri)
242
+ use_incremental = false
243
+
244
+ begin
245
+ gem_names = fetch_quick_index source_uri
246
+ remove_extra gem_names
247
+ missing_gems = find_missing gem_names
248
+ use_incremental = missing_gems.size <= INCREMENTAL_THRESHHOLD
249
+ rescue Gem::OperationNotSupportedError => ex
250
+ use_incremental = false
251
+ end
252
+
253
+ if use_incremental then
254
+ update_with_missing source_uri, missing_gems
255
+ else
256
+ new_index = fetch_bulk_index source_uri
257
+ @gems.replace new_index.gems
258
+ end
259
+
260
+ self
261
+ end
200
262
 
263
+ protected
264
+
265
+ attr_reader :gems
266
+
267
+ private
268
+
269
+ # Convert the yamlized string spec into a real spec (actually, these are
270
+ # hashes of specs.).
271
+ def convert_specs(yaml_spec)
272
+ YAML.load(reduce_specs(yaml_spec)) or
273
+ raise "Didn't get a valid YAML document"
274
+ end
275
+
276
+ def fetcher
277
+ Gem::RemoteFetcher.fetcher
278
+ end
279
+
280
+ def fetch_bulk_index(source_uri)
281
+ say "Bulk updating Gem source index for: #{source_uri}"
282
+
283
+ begin
284
+ yaml_spec = fetcher.fetch_path source_uri + '/yaml.Z'
285
+ yaml_spec = unzip yaml_spec
286
+ rescue
287
+ begin
288
+ yaml_spec = fetcher.fetch_path source_uri + '/yaml'
289
+ rescue => e
290
+ raise Gem::RemoteSourceException,
291
+ "Error fetching remote gem cache: #{e}"
292
+ end
293
+ end
294
+
295
+ convert_specs yaml_spec
296
+ end
297
+
298
+ # Get the quick index needed for incremental updates.
299
+ def fetch_quick_index(source_uri)
300
+ zipped_index = fetcher.fetch_path source_uri + '/quick/index.rz'
301
+ unzip(zipped_index).split("\n")
302
+ rescue ::Exception => ex
303
+ raise Gem::OperationNotSupportedError,
304
+ "No quick index found: " + ex.message
305
+ end
306
+
307
+ # Make a list of full names for all the missing gemspecs.
308
+ def find_missing(spec_names)
309
+ spec_names.find_all { |full_name|
310
+ specification(full_name).nil?
311
+ }
312
+ end
313
+
314
+ # This reduces the source spec in size so that YAML bugs with large data
315
+ # sets will be dodged. Obviously this is a workaround, but it allows Gems
316
+ # to continue to work until the YAML bug is fixed.
317
+ def reduce_specs(yaml_spec)
318
+ result = ""
319
+ state = :copy
320
+ yaml_spec.each do |line|
321
+ if state == :copy && line =~ /^\s+files:\s*$/
322
+ state = :skip
323
+ result << line.sub(/$/, " []")
324
+ elsif state == :skip
325
+ if line !~ /^\s+-/
326
+ state = :copy
327
+ end
328
+ end
329
+ result << line if state == :copy
330
+ end
331
+ result
332
+ end
333
+
334
+ def remove_extra(spec_names)
335
+ dictionary = spec_names.inject({}) { |h, k| h[k] = true; h }
336
+ each do |name, spec|
337
+ remove_spec name unless dictionary.include? name
338
+ end
339
+ end
340
+
341
+ # Unzip the given string.
342
+ def unzip(string)
343
+ require 'zlib'
344
+ Zlib::Inflate.inflate(string)
345
+ end
346
+
347
+ # Update the cached source index with the missing names.
348
+ def update_with_missing(source_uri, missing_names)
349
+ progress = ui.progress_reporter(missing_names.size,
350
+ "Need to update #{missing_names.size} gems from #{source_uri}")
351
+ missing_names.each do |spec_name|
352
+ begin
353
+ spec_uri = source_uri + "/quick/#{spec_name}.gemspec.rz"
354
+ zipped_yaml = fetcher.fetch_path spec_uri
355
+ gemspec = YAML.load unzip(zipped_yaml)
356
+ add_spec gemspec
357
+ progress.updated spec_name
358
+ rescue RuntimeError => ex
359
+ ui.say "Failed to download spec for #{spec_name} from #{source_uri}"
360
+ end
361
+ end
362
+ progress.done
363
+ progress.count
364
+ end
365
+
201
366
  end
202
367
 
203
368
  # Cache is an alias for SourceIndex to allow older YAMLized source
@@ -205,3 +370,4 @@ module Gem
205
370
  Cache = SourceIndex
206
371
 
207
372
  end
373
+
@@ -0,0 +1,153 @@
1
+ require 'fileutils'
2
+ require 'rubygems'
3
+ require 'rubygems/remote_fetcher'
4
+ require 'rubygems/source_info_cache_entry'
5
+
6
+ require 'sources'
7
+
8
+ ##
9
+ # SourceInfoCache stores a copy of the gem index for each gem source.
10
+ #
11
+ # There are two possible cache locations, the system cache and the user cache:
12
+ # * The system cache is prefered if it is writable or can be created.
13
+ # * The user cache is used otherwise
14
+ #
15
+ # Once a cache is selected, it will be used for all operations.
16
+ # SourceInfoCache will not switch between cache files dynamically.
17
+ #
18
+ # Cache data is a Hash mapping a source URI to a SourceInfoCacheEntry.
19
+ #
20
+ #--
21
+ # To keep things straight, this is how the cache objects all fit together:
22
+ #
23
+ # Gem::SourceInfoCache
24
+ # @cache_data = {
25
+ # source_uri => Gem::SourceInfoCacheEntry
26
+ # @size => source index size
27
+ # @source_index => Gem::SourceIndex
28
+ # ...
29
+ # }
30
+ #
31
+ class Gem::SourceInfoCache
32
+
33
+ include Gem::UserInteraction
34
+
35
+ @cache = nil
36
+
37
+ def self.cache
38
+ return @cache if @cache
39
+ @cache = new
40
+ @cache.refresh
41
+ @cache
42
+ end
43
+
44
+ def self.cache_data
45
+ cache.cache_data
46
+ end
47
+
48
+ # Search all source indexes for +pattern+.
49
+ def self.search(pattern)
50
+ cache.search(pattern)
51
+ end
52
+
53
+ def initialize # :nodoc:
54
+ @cache_data = nil
55
+ @cache_file = nil
56
+ @dirty = false
57
+
58
+ @system_cache_file = nil
59
+ @user_cache_file = nil
60
+ end
61
+
62
+ # The most recent cache data.
63
+ def cache_data
64
+ return @cache_data if @cache_data
65
+ @dirty = false
66
+ cache_file # HACK writable check
67
+ # Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
68
+ @cache_data = Marshal.load(File.read(cache_file)) rescue {}
69
+ end
70
+
71
+ # The name of the cache file to be read
72
+ def cache_file
73
+ return @cache_file if @cache_file
74
+ @cache_file = (try_file(system_cache_file) or
75
+ try_file(user_cache_file) or
76
+ raise "unable to locate a writable cache file")
77
+ end
78
+
79
+ # Write the cache to a local file (if it is dirty).
80
+ def flush
81
+ write_cache if @dirty
82
+ @dirty = false
83
+ end
84
+
85
+ # Refreshes each source in the cache from its repository.
86
+ def refresh
87
+ Gem.sources.each do |source_uri|
88
+ cache_entry = cache_data[source_uri]
89
+ if cache_entry.nil? then
90
+ cache_entry = Gem::SourceInfoCacheEntry.new nil, 0
91
+ cache_data[source_uri] = cache_entry
92
+ end
93
+
94
+ cache_entry.refresh source_uri
95
+ end
96
+ update
97
+ flush
98
+ end
99
+
100
+ # Searches all source indexes for +pattern+.
101
+ def search(pattern)
102
+ cache_data.map do |source, sic_entry|
103
+ sic_entry.source_index.search pattern
104
+ end.flatten
105
+ end
106
+
107
+ # The name of the system cache file.
108
+ def system_cache_file
109
+ @system_cache_file ||= File.join(Gem.dir, "source_cache")
110
+ end
111
+
112
+ # Mark the cache as updated (i.e. dirty).
113
+ def update
114
+ @dirty = true
115
+ end
116
+
117
+ # The name of the user cache file.
118
+ def user_cache_file
119
+ @user_cache_file ||=
120
+ ENV['GEMCACHE'] || File.join(Gem.user_home, ".gem", "source_cache")
121
+ end
122
+
123
+ # Write data to the proper cache.
124
+ def write_cache
125
+ open cache_file, "wb" do |f|
126
+ f.write Marshal.dump(cache_data)
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ # Determine if +fn+ is a candidate for a cache file. Return fn if
133
+ # it is. Return nil if it is not.
134
+ def try_file(fn)
135
+ return fn if File.writable?(fn)
136
+ return nil if File.exist?(fn)
137
+ dir = File.dirname(fn)
138
+ if ! File.exist? dir
139
+ begin
140
+ FileUtils.mkdir_p(dir)
141
+ rescue RuntimeError
142
+ return nil
143
+ end
144
+ end
145
+ if File.writable?(dir)
146
+ FileUtils.touch fn
147
+ return fn
148
+ end
149
+ nil
150
+ end
151
+
152
+ end
153
+
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'rubygems/source_index'
3
+ require 'rubygems/remote_fetcher'
4
+
5
+ ##
6
+ # Entrys held by a SourceInfoCache.
7
+
8
+ class Gem::SourceInfoCacheEntry
9
+
10
+ # The source index for this cache entry.
11
+ attr_reader :source_index
12
+
13
+ # The size of the of the source entry. Used to determine if the
14
+ # source index has changed.
15
+ attr_reader :size
16
+
17
+ # Create a cache entry.
18
+ def initialize(si, size)
19
+ @source_index = si || Gem::SourceIndex.new({})
20
+ @size = size
21
+ end
22
+
23
+ def refresh(source_uri)
24
+ remote_size = Gem::RemoteFetcher.fetcher.fetch_size source_uri + '/yaml'
25
+ return if @size == remote_size
26
+ @source_index.update source_uri
27
+ @size = remote_size
28
+ end
29
+
30
+ end
31
+