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
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.
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
Returns all dependencies (direct and indirect) for item, i.e. the items that, when outdated, will cause item to be marked as outdated.
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
Returns all inverse dependencies (direct and indirect) for item, i.e. the items that will be marked as outdated when item is outdated.
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
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).
72 73 74 |
# File 'lib/nanoc3/base/dependency_tracker.rb', line 72 def direct_dependencies_for(item) @graph[item] || [] end |
direct_inverse_dependencies_for
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).
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
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.
225 226 227 |
# File 'lib/nanoc3/base/dependency_tracker.rb', line 225 def forget_dependencies_for(item) @graph[item] = [] end |
load_graph
Loads the dependency graph from the file specified by the filename attribute. This method will overwrite an existing dependency graph.
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
Traverses the dependency graph and marks all items that (directly or indirectly) depend on an outdated item as outdated.
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
Records a dependency from src to dst in the dependency graph. When dst is oudated, src will also become outdated.
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
Starts listening for dependency messages (:visit_started and :visit_ended) and start recording dependencies.
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
Stop listening for dependency messages and stop recording dependencies.
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
Stores the dependency graph into the file specified by the filename attribute.
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 |