From 546d42c33403dfc53e1f5795870be8cce4817aea Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 17 Sep 2017 00:45:18 +1200 Subject: [PATCH] Tidy up implementation. --- lib/build/dependency/chain.rb | 140 +++----------------------- lib/build/dependency/partial_chain.rb | 7 +- lib/build/dependency/provider.rb | 2 +- lib/build/dependency/resolver.rb | 133 ++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 134 deletions(-) create mode 100644 lib/build/dependency/resolver.rb diff --git a/lib/build/dependency/chain.rb b/lib/build/dependency/chain.rb index db4b870..7801e26 100644 --- a/lib/build/dependency/chain.rb +++ b/lib/build/dependency/chain.rb @@ -18,114 +18,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -require 'set' +require_relative 'resolver' module Build module Dependency - class UnresolvedDependencyError < StandardError - def initialize(chain) - super "Unresolved dependency chain: #{chain.unresolved.inspect}!" - - @chain = chain - end - - attr :chain - end - - TOP = Depends.new("").freeze - - class Resolver - def initialize - @resolved = {} - @ordered = [] - @provisions = [] - @unresolved = [] - @conflicts = {} - end - - attr :resolved - attr :ordered - attr :provisions - attr :unresolved - attr :conflicts - - def freeze - return unless frozen? - - @resolved.freeze - @ordered.freeze - @provisions.freeze - @unresolved.freeze - @conflicts.freeze - - super - end - - protected - - def expand_nested(dependencies, provider) - dependencies.each do |dependency| - expand(Depends[dependency], provider) - end - end - - def expand_provision(provision, dependency) - provider = provision.provider - - # If the provision was an Alias, make sure to resolve the alias first: - if provision.alias? - # puts "** Resolving alias #{provision} (#{provision.dependencies.inspect})" - expand_nested(provision.dependencies, provider) - end - - # puts "** Checking for #{provider.inspect} in #{resolved.inspect}" - unless @resolved.include?(provider) - # We are now satisfying the provider by expanding all its own dependencies: - @resolved[provider] = provision - - # Make sure we satisfy the provider's dependencies first: - expand_nested(provider.dependencies, provider) - - # puts "** Appending #{dependency} -> ordered" - - # Add the provider to the ordered list. - @ordered << Resolution.new(provision, dependency) - end - - # This goes here because we want to ensure 1/ that if - unless provision == nil or provision.alias? - # puts "** Appending #{dependency} -> provisions" - - # Add the provision to the set of required provisions. - @provisions << provision - end - - # For both @ordered and @provisions, we ensure that for [...xs..., x, ...], x is satisfied by ...xs.... - end - - def expand(dependency, parent) - # puts "** Expanding #{dependency.inspect} from #{parent.inspect} (private: #{dependency.private?})" - - if @resolved.include?(dependency) - # puts "** Already resolved dependency!" - - return nil - end - - # The find_provider method is abstract in this base class. - expand_dependency(dependency, parent) do |provision| - # We will now satisfy this dependency by satisfying any dependent dependencies, but we no longer need to revisit this one. - # puts "** Resolved #{dependency} (#{provision.inspect})" - @resolved[dependency] = provision - - expand_provision(provision, dependency) - end or begin - # puts "** Couldn't find_provider(#{dependency}, #{parent}) -> unresolved" - @unresolved << [dependency, parent] - end - end - end - class Chain < Resolver # An `UnresolvedDependencyError` will be thrown if there are any unresolved dependencies. def self.expand(*args) @@ -192,24 +88,18 @@ def filter_by_selection(viable_providers) return viable_providers.select{|provider| @selection.include? provider.name} end + def expand_wildcard(dependency, parent) + @providers.flat_map do |provider| + provider.filter(dependency).flat_map do |name, provision| + expand_dependency(Depends[name], parent) + end + end + end + # Resolve a dependency into one or more provisions: def expand_dependency(dependency, parent) if dependency.wildcard? - matched = false - - @providers.each do |provider| - provider.provisions.each do |name, provision| - if dependency.match?(name) - expand_dependency(Depends[provision.name], parent) do |provision| - matched = true - - yield provision - end - end - end - end - - return matched + return expand_wildcard(dependency, parent) end # Mostly, only one package will satisfy the dependency... @@ -222,9 +112,7 @@ def expand_dependency(dependency, parent) provision = provision_for(provider, dependency) # The best outcome, a specific provider was named: - yield provision - - return true + return [provision] elsif viable_providers.size > 1 # ... however in some cases (typically where aliases are being used) an explicit selection must be made for the build to work correctly. explicit_providers = filter_by_selection(viable_providers) @@ -246,16 +134,14 @@ def expand_dependency(dependency, parent) provision = provision_for(provider, dependency) # The best outcome, a specific provider was named: - yield provision - - return true + return [provision] else # Multiple providers were explicitly mentioned that satisfy the dependency. @conflicts[dependency] = explicit_providers end end - return false + return [] end def provision_for(provider, dependency) diff --git a/lib/build/dependency/partial_chain.rb b/lib/build/dependency/partial_chain.rb index 4f63ed1..141fc82 100644 --- a/lib/build/dependency/partial_chain.rb +++ b/lib/build/dependency/partial_chain.rb @@ -83,12 +83,7 @@ def expand(dependency, parent) end def expand_dependency(dependency, parent) - if provision = @chain.resolved[dependency] - yield provision - return true - end - - return false + @chain.resolved[dependency] end def provision_for(provider, dependency) diff --git a/lib/build/dependency/provider.rb b/lib/build/dependency/provider.rb index 00d50a2..065eff7 100644 --- a/lib/build/dependency/provider.rb +++ b/lib/build/dependency/provider.rb @@ -138,7 +138,7 @@ def dependencies end def filter(dependency) - provisions.select{|key,value| dependency.match?(key)} + provisions.select{|name, provision| dependency.match?(name)} end # Does this unit provide the named thing? diff --git a/lib/build/dependency/resolver.rb b/lib/build/dependency/resolver.rb new file mode 100644 index 0000000..46499e5 --- /dev/null +++ b/lib/build/dependency/resolver.rb @@ -0,0 +1,133 @@ +# Copyright, 2017, by Samuel G. D. Williams. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +require 'set' + +module Build + module Dependency + class UnresolvedDependencyError < StandardError + def initialize(chain) + super "Unresolved dependency chain: #{chain.unresolved.inspect}!" + + @chain = chain + end + + attr :chain + end + + TOP = Depends.new("").freeze + + class Resolver + def initialize + @resolved = {} + @ordered = [] + @provisions = [] + @unresolved = [] + @conflicts = {} + end + + attr :resolved + attr :ordered + attr :provisions + attr :unresolved + attr :conflicts + + def freeze + return unless frozen? + + @resolved.freeze + @ordered.freeze + @provisions.freeze + @unresolved.freeze + @conflicts.freeze + + super + end + + protected + + def expand_nested(dependencies, provider) + dependencies.each do |dependency| + expand(Depends[dependency], provider) + end + end + + def expand_provision(provision, dependency) + provider = provision.provider + + # If the provision was an Alias, make sure to resolve the alias first: + if provision.alias? + # puts "** Resolving alias #{provision} (#{provision.dependencies.inspect})" + expand_nested(provision.dependencies, provider) + end + + # puts "** Checking for #{provider.inspect} in #{resolved.inspect}" + unless @resolved.include?(provider) + # We are now satisfying the provider by expanding all its own dependencies: + @resolved[provider] = provision + + # Make sure we satisfy the provider's dependencies first: + expand_nested(provider.dependencies, provider) + + # puts "** Appending #{dependency} -> ordered" + + # Add the provider to the ordered list. + @ordered << Resolution.new(provision, dependency) + end + + # This goes here because we want to ensure 1/ that if + unless provision == nil or provision.alias? + # puts "** Appending #{dependency} -> provisions" + + # Add the provision to the set of required provisions. + @provisions << provision + end + + # For both @ordered and @provisions, we ensure that for [...xs..., x, ...], x is satisfied by ...xs.... + end + + def expand(dependency, parent) + # puts "** Expanding #{dependency.inspect} from #{parent.inspect} (private: #{dependency.private?})" + + if @resolved.include?(dependency) + # puts "** Already resolved dependency!" + + return nil + end + + # The find_provider method is abstract in this base class. + provisions = expand_dependency(dependency, parent) + + if provisions.empty? + # puts "** Couldn't resolve #{dependency}" + @unresolved << [dependency, parent] + else + # We will now satisfy this dependency by satisfying any dependent dependencies, but we no longer need to revisit this one. + # puts "** Resolved #{dependency}" + @resolved[dependency] = provisions + + provisions.each do |provision| + expand_provision(provision, dependency) + end + end + end + end + end +end