Class: Inspec::Resolver

Inherits:
Object
  • Object
show all
Includes:
Molinillo::UI
Defined in:
lib/inspec/dependencies.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(vendor_index, opts = {}) ⇒ Resolver

Returns a new instance of Resolver.



21
22
23
24
25
26
27
28
# File 'lib/inspec/dependencies.rb', line 21

def initialize(vendor_index, opts = {})
  @logger = opts[:logger] || Logger.new(nil)
  @debug_mode = false # TODO: hardcoded for now, grab from options

  @vendor_index = vendor_index
  @resolver = Molinillo::Resolver.new(self, self)
  @search_cache = {}
end

Class Method Details

.resolve(requirements, vendor_index, cwd, opts = {}) ⇒ Object



12
13
14
15
16
17
18
19
# File 'lib/inspec/dependencies.rb', line 12

def self.resolve(requirements, vendor_index, cwd, opts = {})
  reqs = requirements.map do |req|
    Requirement.(req, cwd: cwd) ||
      fail("Cannot initialize dependency: #{req}")
  end

  new(vendor_index, opts).resolve(reqs)
end

Instance Method Details

#allow_missing?(_dependency) ⇒ Boolean

Returns whether this dependency, which has no possible matching specifications, can safely be ignored.

Parameters:

  • dependency (Object)

Returns:

  • (Boolean)

    whether this dependency can safely be skipped.



160
161
162
163
# File 'lib/inspec/dependencies.rb', line 160

def allow_missing?(_dependency)
  # TODO
  false
end

#dependencies_for(specification) ⇒ Array<Object>

Note:

This method should be ‘pure’, i.e. the return value should depend only on the ‘specification` parameter.

Returns the dependencies of ‘specification`.

Parameters:

  • specification (Object)

Returns:

  • (Array<Object>)

    the dependencies that are required by the given ‘specification`.



89
90
91
# File 'lib/inspec/dependencies.rb', line 89

def dependencies_for(specification)
  specification.profile..dependencies
end

#name_for(dependency) ⇒ String

Note:

This method should be ‘pure’, i.e. the return value should depend only on the ‘dependency` parameter.

Returns the name for the given ‘dependency`.

Parameters:

  • dependency (Object)

Returns:

  • (String)

    the name for the given ‘dependency`.



112
113
114
115
116
117
# File 'lib/inspec/dependencies.rb', line 112

def name_for(dependency)
  unless dependency.is_a?(Inspec::Requirement)
    fail 'Internal error: Dependency resolver requires an Inspec::Requirement object for #name_for(dependency)'
  end
  dependency.name
end

#name_for_explicit_dependency_sourceString

Returns the name of the source of explicit dependencies, i.e. those passed to #resolve directly.

Returns:

  • (String)

    the name of the source of explicit dependencies, i.e. those passed to #resolve directly.



121
122
123
# File 'lib/inspec/dependencies.rb', line 121

def name_for_explicit_dependency_source
  'inspec.yml'
end

#name_for_locking_dependency_sourceString

Returns the name of the source of ‘locked’ dependencies, i.e. those passed to #resolve directly as the ‘base`.

Returns:

  • (String)

    the name of the source of ‘locked’ dependencies, i.e. those passed to #resolve directly as the ‘base`



127
128
129
# File 'lib/inspec/dependencies.rb', line 127

def name_for_locking_dependency_source
  'inspec.lock'
end

#outputIO

The IO object that should be used to print output. ‘STDOUT`, by default.

Returns:

  • (IO)


173
174
175
# File 'lib/inspec/dependencies.rb', line 173

def output
  self
end


177
178
179
# File 'lib/inspec/dependencies.rb', line 177

def print(what = '')
  @logger.info(what)
end

#requirement_satisfied_by?(requirement, _activated, spec) ⇒ Boolean

Determines whether the given ‘requirement` is satisfied by the given `spec`, in the context of the current `activated` dependency graph.

Parameters:

  • requirement (Object)
  • activated (DependencyGraph)

    the current dependency graph in the resolution process.

  • spec (Object)

Returns:

  • (Boolean)

    whether ‘requirement` is satisfied by `spec` in the context of the current `activated` dependency graph.



102
103
104
# File 'lib/inspec/dependencies.rb', line 102

def requirement_satisfied_by?(requirement, _activated, spec)
  requirement.matches_spec?(spec) || spec.is_a?(Inspec::Profile)
end

#resolve(requirements) ⇒ Array(String)

Resolve requirements.

Parameters:

  • requirements (Array(Inspec::requirement))

    Array of requirements

Returns:

  • (Array(String))

    list of resolved dependency paths



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/inspec/dependencies.rb', line 34

def resolve(requirements)
  requirements.each(&:pull)
  @base_dep_graph = Molinillo::DependencyGraph.new
  @dep_graph = @resolver.resolve(requirements, @base_dep_graph)
  arr = @dep_graph.map(&:payload)
  Hash[arr.map { |e| [e.name, e] }]
rescue Molinillo::VersionConflict => e
  raise VersionConflict.new(e.conflicts.keys.uniq, e.message)
rescue Molinillo::CircularDependencyError => e
  names = e.dependencies.sort_by(&:name).map { |d| "profile '#{d.name}'" }
  raise CyclicDependencyError,
        'Your profile has requirements that depend on each other, creating '\
        "an infinite loop. Please remove #{names.count > 1 ? 'either ' : ''} "\
        "#{names.join(' or ')} and try again."
end

#search_for(dep) ⇒ Array<Object>

Note:

This method should be ‘pure’, i.e. the return value should depend only on the ‘dependency` parameter.

Search for the specifications that match the given dependency. The specifications in the returned array will be considered in reverse order, so the latest version ought to be last.

Parameters:

  • dependency (Object)

Returns:

  • (Array<Object>)

    the specifications that satisfy the given ‘dependency`.



62
63
64
65
66
67
# File 'lib/inspec/dependencies.rb', line 62

def search_for(dep)
  unless dep.is_a?(Inspec::Requirement)
    fail 'Internal error: Dependency resolver requires an Inspec::Requirement object for #search_for(dependency)'
  end
  @search_cache[dep] ||= uncached_search_for(dep)
end

#sort_dependencies(dependencies, activated, conflicts) ⇒ Array<Object>

Sort dependencies so that the ones that are easiest to resolve are first. Easiest to resolve is (usually) defined by:

1) Is this dependency already activated?
2) How relaxed are the requirements?
3) Are there any conflicts for this dependency?
4) How many possibilities are there to satisfy this dependency?

Parameters:

  • dependencies (Array<Object>)
  • activated (DependencyGraph)

    the current dependency graph in the resolution process.

  • conflicts ({String => Array<Conflict>})

Returns:

  • (Array<Object>)

    a sorted copy of ‘dependencies`.



143
144
145
146
147
148
149
150
151
152
153
# File 'lib/inspec/dependencies.rb', line 143

def sort_dependencies(dependencies, activated, conflicts)
  dependencies.sort_by do |dependency|
    name = name_for(dependency)
    [
      activated.vertex_named(name).payload ? 0 : 1,
      # amount_constrained(dependency), # TODO
      conflicts[name] ? 0 : 1,
      # activated.vertex_named(name).payload ? 0 : search_for(dependency).count, # TODO
    ]
  end
end

#uncached_search_for(dep) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/inspec/dependencies.rb', line 69

def uncached_search_for(dep)
  # pre-cached and specified dependencies
  return [dep] unless dep.profile.nil?

  results = @vendor_index.find(dep)
  return [] unless results.any?

  # TODO: load dep from vendor index
  # vertex = @dep_graph.vertex_named(dep.name)
  # locked_requirement = vertex.payload.requirement if vertex
  fail NotImplementedError, "load dependency #{dep} from vendor index"
end