class Chain
A chain of dependencies that are resolved from a set of providers.
Definitions
def self.expand(*args)
An UnresolvedDependencyError will be thrown if there are any unresolved dependencies.
Implementation
def self.expand(*args)
chain = self.new(*args)
chain.freeze
if chain.unresolved.size > 0
raise UnresolvedDependencyError.new(chain)
end
return chain
end
def initialize(dependencies, providers, selection = [])
Initialize a dependency chain.
Signature
-
parameter
dependenciesArray<String, Depends> The dependencies to resolve.
-
parameter
providersArray<Provider> The providers to use for resolution.
-
parameter
selectionArray<String> Explicitly selected dependencies for resolving ambiguity.
Implementation
def initialize(dependencies, providers, selection = [])
super()
@selection = ::Set.new(selection)
@dependencies = dependencies.collect{|dependency| Depends[dependency]}
@providers = providers
expand_top
end
attr :selection
attr :dependencies
attr :providers
def freeze
Freeze the chain and all its dependencies.
Implementation
def freeze
return self if frozen?
@selection.freeze
@dependencies.freeze
@providers.freeze
super
end
def expand_wildcard(dependency, parent)
Given a dependency with wildcards, figure out all names that might match, and then expand them all individually.
Implementation
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
def expand_dependency(dependency, parent)
Resolve a dependency into one or more provisions.
Implementation
def expand_dependency(dependency, parent)
# The job of this function is to take a dependency and turn it into 0 or more provisions. The dependency could be a normal fully-qualified name or a wildcard. It's not clear at which point pattern matching should affect dependency resolution, but it seems logical since it depends on the available provisions that it's done here.
# Another benefit is that it introduces a fixed point of reference for expanding dependencies. When the resolver invokes this method, it can be assured that it will return the same result.
if dependency.wildcard?
return expand_wildcard(dependency, parent)
end
# Mostly, only one package will satisfy the dependency...
viable_providers = @providers.select{|provider| provider.provides? dependency}
# puts "** Found #{viable_providers.collect(&:name).join(', ')} viable providers."
if viable_providers.size == 1
provider = viable_providers.first
provision = provision_for(provider, dependency)
# The best outcome, a specific provider was named:
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)
# puts "** Filtering to #{explicit_providers.collect(&:name).join(', ')} explicit providers."
if explicit_providers.size != 1
# If we were unable to select a single package, we may use the priority to limit the number of possible options:
explicit_providers = viable_providers if explicit_providers.empty?
explicit_providers = filter_by_priority(explicit_providers)
end
if explicit_providers.size == 0
# No provider was explicitly specified, thus we require explicit conflict resolution:
@conflicts[dependency] = viable_providers
elsif explicit_providers.size == 1
provider = explicit_providers.first
provision = provision_for(provider, dependency)
# The best outcome, a specific provider was named:
return [provision]
else
# Multiple providers were explicitly mentioned that satisfy the dependency.
@conflicts[dependency] = explicit_providers
end
end
return []
end
def partial(provider)
Create a partial chain for a specific provider.
Signature
-
parameter
providerProvider The provider to create a partial chain for.
-
returns
PartialChain A partial chain containing only the provider's dependencies.
Implementation
def partial(provider)
PartialChain.expand(self, provider.dependencies)
end