Class: Nanoc3::DependencyTracker

Nanoc3::DependencyTracker is responsible for remembering dependencies between items. It is used to speed up compilation by only letting an item be recompiled when it is outdated or any of its dependencies (or dependencies’ dependencies, etc) is outdated.

The dependencies tracked by the dependency tracker are not dependencies based on an item’s content. When one item uses an attribute of another item, then this is also treated as a dependency. While dependencies based on an item’s content (handled in Nanoc3::Compiler) cannot be mutually recursive, the more general dependencies in Nanoc3::DependencyTracker can (e.g. item A can use an attribute of item B and vice versa without problems).

Attributes

Instance Attributes

filename [RW] public

Sets the attribute filename.

Constructor Summary

public initialize(items)

FIXME The way the graph is stored is not exactly great. An adjacency matrix (wrapped in a Graph class, perhaps) would be a lot easier to work with, and it would not require an inverse graph to be maintained. Creates a new dependency tracker for the given items.

[View source]


28
29
30
31
32
33
34
35
# File 'lib/nanoc3/base/dependency_tracker.rb', line 28

def initialize(items)
  @items = items

  @filename = 'tmp/dependencies'

  @graph = {}
  @inverse_graph = {}
end

Public Visibility

Public Instance Method Summary

#all_dependencies_for(item)

Returns all dependencies (direct and indirect) for item, i.

#all_inverse_dependencies_for(item)

Returns all inverse dependencies (direct and indirect) for item, i.

#direct_dependencies_for(item)

Returns the direct dependencies for item, i.

#direct_inverse_dependencies_for(item)

Returns the direct inverse dependencies for item, i.

#forget_dependencies_for(item)

Empties the list of dependencies for the given item.

#load_graph

Loads the dependency graph from the file specified by the filename attribute.

#mark_outdated_items

Traverses the dependency graph and marks all items that (directly or indirectly) depend on an outdated item as outdated.

#record_dependency(src, dst)

Records a dependency from src to dst in the dependency graph.

#start

Starts listening for dependency messages (:visit_started and :visit_ended) and start recording dependencies.

#stop

Stop listening for dependency messages and stop recording dependencies.

#store_graph

Stores the dependency graph into the file specified by the filename attribute.

Public Instance Method Details

all_dependencies_for

public all_dependencies_for(item)

Returns all dependencies (direct and indirect) for item, i.e. the items that, when outdated, will cause item to be marked as outdated.

[View source]


78
79
80
81
82
83
84
85
# File 'lib/nanoc3/base/dependency_tracker.rb', line 78

def all_dependencies_for(item)
  # FIXME can result in an infinite loop

  direct_dependencies   = direct_dependencies_for(item)
  indirect_dependencies = direct_dependencies.map { |i| all_dependencies_for(i) }

  (direct_dependencies + indirect_dependencies).flatten
end

all_inverse_dependencies_for

public all_inverse_dependencies_for(item)

Returns all inverse dependencies (direct and indirect) for item, i.e. the items that will be marked as outdated when item is outdated.

[View source]


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/nanoc3/base/dependency_tracker.rb', line 97

def all_inverse_dependencies_for(item)
  # Init list of all found dependencies
  all_dependencies = []

  # Init lists with already checked and not yet checked dependencies
  checked_direct_dependencies = []
  pending_direct_dependencies = direct_inverse_dependencies_for(item)

  while !pending_direct_dependencies.empty?
    # Get next unchecked dependency
    dependency = pending_direct_dependencies.shift
    next if checked_direct_dependencies.include?(dependency)

    # Add dependencies of this unchecked dependency
    pending_direct_dependencies += direct_inverse_dependencies_for(dependency)

    # Mark this dependency as handled
    all_dependencies << dependency
    checked_direct_dependencies << dependency
  end

  all_dependencies
end

direct_dependencies_for

public direct_dependencies_for(item)

Returns the direct dependencies for item, i.e. the items that, when outdated, will cause item to be marked as outdated. Indirect dependencies will not be returned (e.g. if A depends on B which depends on C, then the direct dependencies of A do not include C).

[View source]


72
73
74
# File 'lib/nanoc3/base/dependency_tracker.rb', line 72

def direct_dependencies_for(item)
  @graph[item] || []
end

direct_inverse_dependencies_for

public direct_inverse_dependencies_for(item)

Returns the direct inverse dependencies for item, i.e. the items that will be marked as outdated when item is outdated. Indirect dependencies will not be returned (e.g. if A depends on B which depends on C, then the direct inverse dependencies of C do not include A).

[View source]


91
92
93
# File 'lib/nanoc3/base/dependency_tracker.rb', line 91

def direct_inverse_dependencies_for(item)
  inverted_graph[item] || []
end

forget_dependencies_for

public forget_dependencies_for(item)

Empties the list of dependencies for the given item. This is necessary before recompiling the given item, because otherwise old dependencies will stick around and new dependencies will appear twice.

[View source]


225
226
227
# File 'lib/nanoc3/base/dependency_tracker.rb', line 225

def forget_dependencies_for(item)
  @graph[item] = []
end

load_graph

public load_graph

Loads the dependency graph from the file specified by the filename attribute. This method will overwrite an existing dependency graph.

[View source]


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/nanoc3/base/dependency_tracker.rb', line 166

def load_graph
  # Create new graph
  @graph = {}

  # Don't do anything if dependencies haven't been stored yet
  return if !File.file?(self.filename)

  # Load dependencies
  store = PStore.new(self.filename)
  store.transaction do
    # Convert graph of identifiers into graph of items
    store[:dependencies].each_pair do |second_item_identifier, first_item_identifiers|
      # Convert second and first item identifiers into items
      second_item = item_with_identifier(second_item_identifier)
      first_items = first_item_identifiers.map { |p| item_with_identifier(p) }

      @graph[second_item] = first_items
    end
  end
end

mark_outdated_items

public mark_outdated_items

Traverses the dependency graph and marks all items that (directly or indirectly) depend on an outdated item as outdated.

[View source]


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/nanoc3/base/dependency_tracker.rb', line 189

def mark_outdated_items
  # Unmark everything
  @items.each { |i| i.dependencies_outdated = false }

  # Mark items that appear in @items but not in the dependency graph
  added_items = @items - @graph.keys
  added_items.each { |i| i.dependencies_outdated = true }

  # Walk graph and mark items as outdated if necessary
  # (#keys and #sort is used instead of #each_pair to add determinism)
  first_items = inverted_graph.keys.sort_by { |i| i.nil? ? '/' : i.identifier }
  something_changed = true
  while something_changed
    something_changed = false

    first_items.each do |first_item|
      second_items = inverted_graph[first_item]

      if first_item.nil? ||                # item was removed
         first_item.outdated? ||           # item itself is outdated
         first_item.dependencies_outdated? # item is outdated because of its dependencies
        second_items.each do |item|
          # Ignore this item
          next if item.nil?

          something_changed = true if !item.dependencies_outdated?
          item.dependencies_outdated = true
        end
      end
    end
  end
end

record_dependency

public record_dependency(src, dst)

Records a dependency from src to dst in the dependency graph. When dst is oudated, src will also become outdated.

[View source]


123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/nanoc3/base/dependency_tracker.rb', line 123

def record_dependency(src, dst)
  # Initialize graph if necessary
  @graph[src] ||= []

  # Don't include self or doubles in dependencies
  return if src == dst
  return if @graph[src].include?(dst)

  # Record dependency
  invalidate_inverted_graph
  @graph[src] << dst
end

start

public start

Starts listening for dependency messages (:visit_started and :visit_ended) and start recording dependencies.

[View source]


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/nanoc3/base/dependency_tracker.rb', line 39

def start
  # Initialize dependency stack. An item will be pushed onto this stack
  # when it is visited. Therefore, an item on the stack always depends on
  # all items pushed above it.
  @stack = []

  # Register start of visits
  Nanoc3::NotificationCenter.on(:visit_started, self) do |item|
    # Record possible dependency
    unless @stack.empty?
      self.record_dependency(@stack[-1], item)
    end

    @stack.push(item)
  end

  # Register end of visits
  Nanoc3::NotificationCenter.on(:visit_ended, self) do |item|
    @stack.pop
  end
end

stop

public stop

Stop listening for dependency messages and stop recording dependencies.

[View source]


62
63
64
65
66
# File 'lib/nanoc3/base/dependency_tracker.rb', line 62

def stop
  # Unregister
  Nanoc3::NotificationCenter.remove(:visit_started, self)
  Nanoc3::NotificationCenter.remove(:visit_ended,   self)
end

store_graph

public store_graph

Stores the dependency graph into the file specified by the filename attribute.

[View source]


138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/nanoc3/base/dependency_tracker.rb', line 138

def store_graph
  # Create dir
  FileUtils.mkdir_p(File.dirname(self.filename))

  # Complete the graph
  complete_graph

  # Convert graph of items into graph of item identifiers
  new_graph = {}
  @graph.each_pair do |second_item, first_items|
    # Don't store nil because that would be pointless (if first_item is
    # outdated, something that does not exist is also outdated… makes no
    # sense).
    # FIXME can second_item really be nil?
    next if second_item.nil?

    new_graph[second_item.identifier] = first_items.map { |f| f && f.identifier }.compact
  end

  # Store dependencies
  store = PStore.new(self.filename)
  store.transaction do
    store[:dependencies] = new_graph
  end
end
Generated on Sunday, August 09 2009 at 01:43:09 PM by YARD 0.2.3.2 (ruby-1.8.7).