stickler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,76 @@
1
+ require 'uri'
2
+ require 'base64'
3
+ require 'progressbar'
4
+ require 'zlib'
5
+
6
+ module Stickler
7
+ #
8
+ # The representation of an upstream source from which stickler pulls gems.
9
+ # This wraps up the contents of the upstream specs.4.8 file found in a
10
+ # rubygems 1.2 or greater repository.
11
+ #
12
+ class Source
13
+ class Error < ::StandardError; end
14
+
15
+ # the uri of the source
16
+ attr_reader :uri
17
+
18
+ # the source_group this source belongs to
19
+ attr_accessor :source_group
20
+
21
+ def self.normalize_uri( uri )
22
+ return uri if uri.kind_of?( URI::Generic )
23
+ path_parts = uri.split( "/" )
24
+ uri = path_parts.join( "/" ) + "/"
25
+ uri = ::URI.parse( uri )
26
+ end
27
+
28
+ #
29
+ # Create a new Source for a source_group.
30
+ # Try and load the source from the cache if it can and if not,
31
+ # load it from the uri
32
+ #
33
+ def initialize( uri, source_group )
34
+
35
+ begin
36
+ @uri = Source.normalize_uri( uri )
37
+ @source_group = source_group
38
+ load_source_specs
39
+
40
+ rescue ::URI::Error => e
41
+ raise Error, "Unable to create source from uri #{uri} : #{e}"
42
+ end
43
+ end
44
+
45
+ #
46
+ # find all matching gems and return their SpecLite
47
+ #
48
+ def search( dependency )
49
+ found = source_specs.select do | spec |
50
+ dependency =~ Gem::Dependency.new( spec.name, spec.version )
51
+ end
52
+ end
53
+
54
+ #
55
+ # load the upstream or cached specs.marshalversion file for the source.
56
+ #
57
+ def source_specs
58
+ unless @source_specs
59
+ Console.info " * loading #{uri}"
60
+ @source_specs = []
61
+ ::Gem::SpecFetcher.fetcher.load_specs( uri, 'specs' ).each do |name, version, platform|
62
+ @source_specs << SpecLite.new( name, version, platform )
63
+ end
64
+ end
65
+ return @source_specs
66
+ end
67
+
68
+ #
69
+ # force a load of the source_specs
70
+ #
71
+ def load_source_specs
72
+ @source_specs = nil
73
+ return source_specs
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,365 @@
1
+ module Stickler
2
+ #
3
+ # A source group contains a set of Source objects, and runs common operations
4
+ # across all of them.
5
+ #
6
+ class SourceGroup
7
+
8
+ # the repository this group belongs to
9
+ attr_reader :repository
10
+
11
+ def initialize( repository )
12
+ @repository = repository
13
+ @sources = {}
14
+
15
+ @fetcher = ::Gem::RemoteFetcher.new( nil )
16
+ @spec_fetcher = ::Gem::SpecFetcher.fetcher
17
+ end
18
+
19
+ #
20
+ # The root directory of the repository
21
+ #
22
+ def root_dir
23
+ @root_dir || repository.directory
24
+ end
25
+
26
+ #
27
+ # The specification directory in the repository
28
+ #
29
+ def specification_dir
30
+ @specification_dir ||= repository.specification_dir
31
+ end
32
+
33
+ #
34
+ # The specification behavior
35
+ #
36
+ def requirement_satisfaction_behavior
37
+ @requirement_satisfaction_behavior ||= repository.requirement_satisfaction_behavior
38
+ end
39
+
40
+ #
41
+ # The directory housing the actual .gem files
42
+ #
43
+ def gems_dir
44
+ @gems_dir ||= repository.gems_dir
45
+ end
46
+
47
+ #
48
+ # logger for this class
49
+ #
50
+ def logger
51
+ @logger ||= ::Logging::Logger[self]
52
+ end
53
+
54
+ #
55
+ # Add a source to the source group
56
+ #
57
+ def add_source( source_uri )
58
+ s = Source.new( source_uri, self )
59
+ @sources[s.uri] = s
60
+ end
61
+
62
+ #
63
+ # accessor for the available Source objects in this group
64
+ #
65
+ def sources
66
+ @sources.values
67
+ end
68
+
69
+ #
70
+ # Access all the gems that are in this gemspec This is a Hash of all gems in
71
+ # the source group. The keys are spec.full_name and the values are
72
+ # Gem::Specification instances
73
+ #
74
+ def gems
75
+ unless @gems
76
+ @gems = {}
77
+ Dir.glob( File.join( specification_dir, "*.gemspec" ) ).each do |spec_file|
78
+ begin
79
+ logger.info "Loading spec file #{spec_file}"
80
+ spec = eval( IO.read( spec_file ) )
81
+ @gems[ spec.full_name ] = spec
82
+ rescue => e
83
+ logger.error "Failure loading specfile #{File.basename( spec_file )} : #{e}"
84
+ end
85
+ end
86
+ end
87
+ return @gems
88
+ end
89
+
90
+ #
91
+ # Force a reload of the gem from the existing specs
92
+ #
93
+ def reload_gems!
94
+ @gems = nil
95
+ gems
96
+ end
97
+
98
+ #
99
+ # Return a list of Gem::Specification instances corresponding to the
100
+ # existing gems for a particular source
101
+ #
102
+ def existing_specs_for_source_uri( uri )
103
+ unless @existing_specs_for_source_uri
104
+ logger.debug "Loading existing_specs_for_source_uri"
105
+ @existing_specs_for_source_uri = Hash.new{ |h,k| h[k] = Array.new }
106
+ gems.values.each do |spec|
107
+ @existing_specs_for_source_uri[ source_uri_for_spec( spec ) ] << spec
108
+ end
109
+ end
110
+ @existing_specs_for_source_uri[ Source.normalize_uri( uri ) ]
111
+ end
112
+
113
+ #
114
+ # Search through all sources for all gems that satisfy the given
115
+ # Gem::Dependency
116
+ #
117
+ def search( dependency )
118
+ results = []
119
+ @sources.each_pair do |uri, src|
120
+ results.concat( src.search( dependency ) )
121
+ end
122
+ return results
123
+ end
124
+
125
+ #
126
+ # Search through all the existing specs for gemes that match the given
127
+ # Gem::Dependency
128
+ #
129
+ def search_existing( dependency )
130
+ results = gems.values.find_all do |spec|
131
+ dependency =~ Gem::Dependency.new( spec.name, spec.version )
132
+ end
133
+ end
134
+
135
+ #
136
+ # Add the gem that satisfies the dependency based upon the current
137
+ # satisfication method
138
+ #
139
+ def add_from_dependency( dep )
140
+ Console.info "Resolving gem dependencies for #{dep.to_s} ..."
141
+ specs_satisfying_dependency( dep ).each do |s|
142
+ add( s )
143
+ end
144
+ end
145
+
146
+ #
147
+ # Add the gem given by the spec and all of its dependencies.
148
+ #
149
+ def add( spec )
150
+ top_spec = spec
151
+ unless spec.instance_of?( ::Gem::Specification )
152
+ source_uri = source_uri_for_spec( spec )
153
+ top_spec = @spec_fetcher.fetch_spec( spec.to_a, source_uri )
154
+ end
155
+
156
+ add_list = []
157
+
158
+ todo = []
159
+ todo.push top_spec
160
+ seen = {}
161
+
162
+ until todo.empty? do
163
+ spec = todo.pop
164
+ next if seen[ spec.full_name ] or gems[ spec.full_name ]
165
+
166
+ logger.info "Queueing #{spec.full_name} for download"
167
+ add_list << spec
168
+
169
+ seen[ spec.full_name ] = true
170
+
171
+ deps = spec.runtime_dependencies
172
+ deps |= spec.development_dependencies
173
+
174
+ deps.each do |dep|
175
+ specs_satisfying_dependency( dep ).each do |s|
176
+ todo.push s
177
+ end
178
+ end
179
+ end
180
+
181
+ add_gems_and_specs( add_list )
182
+ reload_gems!
183
+ end
184
+
185
+
186
+ #
187
+ # unisntall the gem given by the spec and all gems that depend on it.
188
+ #
189
+ def remove( spec_or_list )
190
+ Console.info "Resolving remove dependencies..."
191
+
192
+ todo = [ spec_or_list ].flatten
193
+ remove_list = []
194
+
195
+ until todo.empty? do
196
+ spec = todo.pop
197
+ next if remove_list.include?( spec )
198
+
199
+ logger.info "queueing #{spec.full_name} for removal"
200
+ remove_list << spec
201
+
202
+ sibling_gems_of( spec ).each do |sspec|
203
+ Console.debug "pushing #{sspec.full_name} onto todo list"
204
+ todo.push sspec
205
+ end
206
+
207
+ specs_depending_on( spec ).each do |dspec|
208
+ Console.debug "pushing #{dspec.full_name} onto todo list"
209
+ todo.push dspec
210
+ end
211
+ end
212
+
213
+ remove_gems_and_specs( remove_list )
214
+ reload_gems!
215
+ end
216
+
217
+ #
218
+ # Return the list of existing Specifications that have the same name as
219
+ # then given spec
220
+ #
221
+ def sibling_gems_of( spec )
222
+ sibs = []
223
+ gems.values.each do |gspec|
224
+ if spec.name == gspec.name and spec.full_name != gspec.full_name then
225
+ sibs << gspec
226
+ end
227
+ end
228
+ return sibs
229
+ end
230
+
231
+ #
232
+ # Get the list of existing Specifications that have the input spec as
233
+ # either a runtime or development dependency
234
+ #
235
+ def specs_depending_on( spec )
236
+ deps = []
237
+ gems.values.each do |gspec|
238
+ gspec.dependencies.each do |dep|
239
+ if spec.satisfies_requirement?( dep ) then
240
+ deps << gspec
241
+ break
242
+ end
243
+ end
244
+ end
245
+ return deps
246
+ end
247
+
248
+ #
249
+ # Get the list of Specifications that satisfy the dependency
250
+ # based upon the current requirement satisfaction method
251
+ #
252
+ def specs_satisfying_dependency( dep )
253
+ unsorted = search( dep )
254
+ sorted = unsorted.sort_by { |s| s.version }
255
+
256
+ sorted.reverse! if requirement_satisfaction_behavior == :minimum
257
+
258
+ satisfies = []
259
+ matching_from_each_platform( sorted ).each do |spec|
260
+ su = source_uri_for_spec( spec )
261
+ satisfies << @spec_fetcher.fetch_spec( spec.to_a, su )
262
+ end
263
+
264
+ return satisfies
265
+ end
266
+
267
+ #
268
+ # collect the highest version from each distinct platform in the results and
269
+ # return that list
270
+ #
271
+ def matching_from_each_platform( results )
272
+ by_platform = {}
273
+ until results.empty?
274
+ spec = results.pop
275
+ if not by_platform.has_key?( spec.platform.to_s ) then
276
+ by_platform[ spec.platform.to_s ] = spec
277
+ end
278
+ end
279
+ return by_platform.values
280
+ end
281
+
282
+ #
283
+ # return the URI of the Source object that houses the upstream gem
284
+ #
285
+ def source_uri_for_spec( key_spec )
286
+ unless @source_uri_for_spec
287
+ logger.debug "Loading source_uri_to_spec"
288
+ @source_uri_for_spec = {}
289
+ @sources.each_pair do |uri, src|
290
+ src.source_specs.each do | s |
291
+ @source_uri_for_spec[ s.name_version ] = uri
292
+ end
293
+ end
294
+ end
295
+ @source_uri_for_spec[ key_spec.name_version ]
296
+ end
297
+
298
+
299
+ #
300
+ # remove the source from the source group
301
+ #
302
+ def remove_source( source_uri )
303
+ ulist = existing_specs_for_source_uri( source_uri )
304
+ remove( ulist )
305
+ @sources.delete( source_uri )
306
+ logger.info "removed #{source_uri}"
307
+ end
308
+
309
+ #
310
+ # Remove a list of gems from specifications
311
+ #
312
+ def remove_gems_and_specs( remove_list )
313
+ while spec = remove_list.pop do
314
+ Console.info "Removing #{ spec.full_name }"
315
+ delete_gem_files( spec )
316
+ end
317
+ end
318
+
319
+ #
320
+ # Remove all files from the repository related to this specification
321
+ #
322
+ def delete_gem_files( spec )
323
+ FileUtils.rm_f( File.join( gems_dir, "#{spec.full_name}.gem" ) )
324
+ FileUtils.rm_f( File.join( specification_dir, "#{spec.full_name}.gemspec" ))
325
+ end
326
+
327
+ #
328
+ # Add the gem represented by the spec
329
+ #
330
+ def add_gem( spec )
331
+
332
+ local_fetch_path = @fetcher.download( spec, source_uri_for_spec( spec ).to_s, root_dir )
333
+ dest_gem_path = File.join( gems_dir, File.basename( local_fetch_path ) )
334
+ logger.info "copying #{local_fetch_path} to #{dest_gem_path}"
335
+ FileUtils.cp local_fetch_path, dest_gem_path
336
+
337
+ return dest_gem_path
338
+ end
339
+
340
+
341
+ #
342
+ # Add the specification
343
+ #
344
+ def add_spec( spec )
345
+ rubycode = spec.to_ruby
346
+ file_name = File.join( specification_dir, "#{spec.full_name}.gemspec" )
347
+ logger.info "writing #{file_name}"
348
+ File.open( file_name, "wb" ) do |file|
349
+ file.puts rubycode
350
+ end
351
+ end
352
+
353
+
354
+ #
355
+ # Add all the gems and specifications in the add list
356
+ #
357
+ def add_gems_and_specs( add_list )
358
+ while spec = add_list.pop do
359
+ Console.info "Adding #{ spec.full_name }"
360
+ add_gem( spec )
361
+ add_spec( spec )
362
+ end
363
+ end
364
+ end
365
+ end
@@ -0,0 +1,48 @@
1
+ module Stickler
2
+ #
3
+ # A lightweight version of a gemspec that only responds to name, version,
4
+ # platform and full_name. Many of the items in the rubygems world
5
+ # deal with the triplet [ name, verison, platform ] and this class
6
+ # encapsulates that.
7
+ #
8
+ class SpecLite
9
+ attr_reader :name
10
+ attr_reader :version
11
+ attr_reader :platform
12
+
13
+ def initialize( name, version, platform )
14
+ @name = name
15
+ @version = version
16
+ @new_platform = Gem::Platform.new( platform )
17
+ @platform = platform
18
+ end
19
+
20
+ def full_name
21
+ if platform == Gem::Platform::RUBY or platform.nil? then
22
+ "#{name}-#{version}"
23
+ else
24
+ "#{name}-#{version}-#{platform}"
25
+ end
26
+ end
27
+
28
+ def name_version
29
+ "#{name}-#{version}"
30
+ end
31
+
32
+ def to_a
33
+ [ name, version, platform.to_s ]
34
+ end
35
+
36
+ def to_s
37
+ full_name
38
+ end
39
+ end
40
+ end
41
+
42
+ module Gem
43
+ class Specification
44
+ def name_version
45
+ "#{name}-#{version}"
46
+ end
47
+ end
48
+ end