Class: Ruber::ComponentManager::DepsSolver

Inherits:
Object
  • Object
show all
Defined in:
lib/ruber/component_manager.rb

Overview

Helper class which contains the methods needed to find all the plugins needed to satisfy the dependencies of a given set of plugins.

The difference between this class and PluginSorter is that the latter needs to know all the plugins which should be loaded, while this class has the job of finding out which ones need to be loaded.

Instance Method Summary (collapse)

Constructor Details

- (DepsSolver) initialize(to_load, availlable)

Creates a new DepsSolver.

to_load is an array containing the PluginSpecification corresponding describing the plugins to load, while availlable is an array containing the PluginSpecification which can be used to resolve dependencies.


201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/ruber/component_manager.rb', line 201

def initialize to_load, availlable
  @to_load = to_load.map{|i| [i.name, i]}.to_h
  @availlable = availlable.map{|i| [i.name, i]}.to_h
  @loaded_features = to_load.inject({}) do |res, i|
    i.features.each{|f| (res[f] ||= []) << i}
    res
  end
  @availlable_plugins = availlable.map{|i| [i.name, i]}.to_h
  @availlable_features = availlable.inject({}) do |res, i|
    i.features.each{|f| (res[f] ||= []) << i}
    res
  end
  # @res is an array containing the result, that is the list of names of
  # the plugins to load to satisfy all dependencies.
  # @deps is a hash containing the reasons for which a given plugin should
  # be loaded. Each key is the name of a plugin, while each value is an array
  # containing the list of plugins directly depending on the key. If the key
  # is in the list of plugins to load (that is in the first argument passed
  # to the constructor), the array contains nil.
  @res = []
  @deps = Hash.new{|h, k| h[k] = []}
end

Instance Method Details

- (Object) remove_unneeded_deps (private)

Attempts to remove from the list of needed dependencies all those dependencies which are there only to provide features already provided by other plugins.

For example, if the list of needed plugins includes both the plugin :a and the plugin :b, which provides the feature :a, then the plugin :a whould be removed from the list. If after removing plugins as described above, the list contains plugins which aren’t needed anymore, because they were there only to satisfy the dependencies of plugins which have already been removed, they’re also removed.


305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/ruber/component_manager.rb', line 305

def remove_unneeded_deps
  h = Hash.new{|hash, k| hash[k] = []}
  #A hash having the features as keys and the plugins providing them
  #as values
  deps_features = @res.inject(h) do |res, i|
    @availlable_plugins[i].features.each{|f| res[f] << i}
    res
  end
  to_delete = @res.find{|i| !@deps[i].include?(nil) and !deps_features[i].uniq.only? i}
  until to_delete.nil?
    @res.delete to_delete
    deps_features.each_value{|i| i.delete to_delete}
    new = deps_features[to_delete]
    @deps[new] += @deps[to_delete]
    @deps.delete to_delete
    @deps.each_value{|i| i.delete to_delete}
    to_delete = @res.find{|i| !@deps.include?(nil) and !deps_features[i].only? i}
    to_delete = @deps.find{|k, v| v.empty?}[0] rescue nil unless to_delete
  end
end

- (Object) solve

Tries to resolve the dependencies for the given plugins, returning an array containing the plugins needed to satisfy all the dependencies. When a plugin depends on a feature f, then f is included in the list of needed plugins, together with its dependencies, unless another required plugin already provides that feature.

If some dependencies can’t be satisfied, UnresolvedDep is raised. If there are circular dependencies, CircularDep is raised.


234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/ruber/component_manager.rb', line 234

def solve
  errors = {:missing => {}, :circular => []}
  @res = @to_load.values.inject([]) do |r, i| 
    @deps[i.name] << nil
    r + solve_for(i, errors, [])
  end
  if !errors[:missing].empty? then raise UnresolvedDep.new errors[:missing]
  elsif !errors[:circular].empty? then raise CircularDep.new errors[:circular]
  end
  remove_unneeded_deps
  @res
end

- (Object) solve_for(pl, errors, stack) (private)

Recursively finds all the dependencies for the plugin described by the PluginSpecification pl.

errors is a hash used to store missing dependencies and circular dependencies. It should have a :circular and a :missing key. The corresponding values should be an array and a hash. stack is an array containing the names of the plugins whose dependencies are being solved and is used to detect circular dependencies. For example, if stack is: [:a, :b, :c], it would mean that we’re resolving the dependencies of the plugin :c, which is a dependency of the plugin :b, which is a dependency of the plugin :a. If this array contains pl.name, then there’s a circular dependency. Note: this method doesn’t raise exceptions if there are circular or missing dependencies. Rather, it adds them to errors and goes on (this means that it skips both missing and circular dependencies).


268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/ruber/component_manager.rb', line 268

def solve_for pl, errors, stack
  deps = []
  if stack.include? pl.name
    errors[:circular] << [stack.at(stack.index(pl.name + 1)), pl.name]
    return deps
  end
  stack << pl.name
  unless pl.deps.empty?
    pl.deps.each do |dep|
      next if @loaded_features.include? dep
      new_pl = @availlable_plugins[dep]
      if new_pl
        deps << dep
        @deps[dep] << pl.name
        deps += solve_for new_pl, errors, stack
      else
        (errors[:missing][dep] ||= []) << pl.name
        return []
      end
    end
  end
  stack.pop
  deps
end