Skip to content

Commit

Permalink
Tidy up implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Sep 16, 2017
1 parent 94beb2c commit 546d42c
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 134 deletions.
140 changes: 13 additions & 127 deletions lib/build/dependency/chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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("<top>").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)
Expand Down Expand Up @@ -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...
Expand All @@ -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)
Expand All @@ -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)
Expand Down
7 changes: 1 addition & 6 deletions lib/build/dependency/partial_chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lib/build/dependency/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
133 changes: 133 additions & 0 deletions lib/build/dependency/resolver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# 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("<top>").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

0 comments on commit 546d42c

Please sign in to comment.