Benjamin Schubert pushed to branch bschubert/rework-circular-check at BuildStream / buildstream
Commits:
-
4c626bad
by Benjamin Schubert at 2019-02-12T16:55:38Z
3 changed files:
Changes:
... | ... | @@ -129,6 +129,58 @@ class LoadElement(): |
129 | 129 |
def depends(self, other):
|
130 | 130 |
return other._node_id in self._dep_cache
|
131 | 131 |
|
132 |
+ # _ensure_no_circular_deps()
|
|
133 |
+ #
|
|
134 |
+ # Ensure the node is not part of a dependency cycle
|
|
135 |
+ #
|
|
136 |
+ # Raises:
|
|
137 |
+ # (LoadError): In case there was a circular dependency
|
|
138 |
+ #
|
|
139 |
+ def _ensure_no_circular_deps(self):
|
|
140 |
+ if self._node_id in self._dep_cache:
|
|
141 |
+ self._find_circular_deps(set(), set(), [])
|
|
142 |
+ |
|
143 |
+ # _find_circular_deps():
|
|
144 |
+ #
|
|
145 |
+ # Detect circular dependencies on LoadElements with
|
|
146 |
+ # dependencies already resolved.
|
|
147 |
+ #
|
|
148 |
+ # Args:
|
|
149 |
+ # check_elements (set[LoadElement]): The elements in the chain being checked
|
|
150 |
+ # validated (set[LoadElement]): The elements already validated to not have
|
|
151 |
+ # circular dependencies
|
|
152 |
+ # sequence (list[Element]): current sequence of elements that is checked
|
|
153 |
+ #
|
|
154 |
+ # Raises:
|
|
155 |
+ # (LoadError): In case there was a circular dependency error
|
|
156 |
+ #
|
|
157 |
+ def _find_circular_deps(self, check_elements, validated, sequence):
|
|
158 |
+ # Skip already validated branches
|
|
159 |
+ if self in validated:
|
|
160 |
+ return
|
|
161 |
+ |
|
162 |
+ if self in check_elements:
|
|
163 |
+ # Create `chain`, the loop of element dependencies from this
|
|
164 |
+ # element back to itself, by trimming everything before this
|
|
165 |
+ # element from the sequence under consideration.
|
|
166 |
+ chain = sequence[sequence.index(self.full_name):]
|
|
167 |
+ chain.append(self.full_name)
|
|
168 |
+ raise LoadError(LoadErrorReason.CIRCULAR_DEPENDENCY,
|
|
169 |
+ ("Circular dependency detected at element: {}\n" +
|
|
170 |
+ "Dependency chain: {}")
|
|
171 |
+ .format(self.full_name, " -> ".join(chain)))
|
|
172 |
+ |
|
173 |
+ # Push / Check each dependency / Pop
|
|
174 |
+ check_elements.add(self)
|
|
175 |
+ sequence.append(self.full_name)
|
|
176 |
+ for dep in self.dependencies:
|
|
177 |
+ dep.element._check_circular_deps(check_elements, validated, sequence)
|
|
178 |
+ check_elements.remove(self)
|
|
179 |
+ sequence.pop()
|
|
180 |
+ |
|
181 |
+ # Eliminate duplicate paths
|
|
182 |
+ validated.add(self)
|
|
183 |
+ |
|
132 | 184 |
|
133 | 185 |
# _extract_depends_from_node():
|
134 | 186 |
#
|
... | ... | @@ -127,23 +127,6 @@ class Loader(): |
127 | 127 |
target_elements.append(element)
|
128 | 128 |
profile_end(Topics.LOAD_PROJECT, target)
|
129 | 129 |
|
130 |
- #
|
|
131 |
- # Now that we've resolve the dependencies, scan them for circular dependencies
|
|
132 |
- #
|
|
133 |
- |
|
134 |
- # Set up a dummy element that depends on all top-level targets
|
|
135 |
- # to resolve potential circular dependencies between them
|
|
136 |
- dummy_target = LoadElement("", "", self)
|
|
137 |
- dummy_target.dependencies.extend(
|
|
138 |
- LoadElement.Dependency(element, Symbol.RUNTIME)
|
|
139 |
- for element in target_elements
|
|
140 |
- )
|
|
141 |
- |
|
142 |
- profile_key = "_".join(t for t in targets)
|
|
143 |
- profile_start(Topics.CIRCULAR_CHECK, profile_key)
|
|
144 |
- self._check_circular_deps(dummy_target)
|
|
145 |
- profile_end(Topics.CIRCULAR_CHECK, profile_key)
|
|
146 |
- |
|
147 | 130 |
ret = []
|
148 | 131 |
#
|
149 | 132 |
# Sort direct dependencies of elements by their dependency ordering
|
... | ... | @@ -278,53 +261,9 @@ class Loader(): |
278 | 261 |
deps_names = [dep.name for dep in dependencies]
|
279 | 262 |
self._warn_invalid_elements(deps_names)
|
280 | 263 |
|
281 |
- return element
|
|
264 |
+ element._ensure_no_circular_deps()
|
|
282 | 265 |
|
283 |
- # _check_circular_deps():
|
|
284 |
- #
|
|
285 |
- # Detect circular dependencies on LoadElements with
|
|
286 |
- # dependencies already resolved.
|
|
287 |
- #
|
|
288 |
- # Args:
|
|
289 |
- # element (str): The element to check
|
|
290 |
- #
|
|
291 |
- # Raises:
|
|
292 |
- # (LoadError): In case there was a circular dependency error
|
|
293 |
- #
|
|
294 |
- def _check_circular_deps(self, element, check_elements=None, validated=None, sequence=None):
|
|
295 |
- |
|
296 |
- if check_elements is None:
|
|
297 |
- check_elements = {}
|
|
298 |
- if validated is None:
|
|
299 |
- validated = {}
|
|
300 |
- if sequence is None:
|
|
301 |
- sequence = []
|
|
302 |
- |
|
303 |
- # Skip already validated branches
|
|
304 |
- if validated.get(element) is not None:
|
|
305 |
- return
|
|
306 |
- |
|
307 |
- if check_elements.get(element) is not None:
|
|
308 |
- # Create `chain`, the loop of element dependencies from this
|
|
309 |
- # element back to itself, by trimming everything before this
|
|
310 |
- # element from the sequence under consideration.
|
|
311 |
- chain = sequence[sequence.index(element.full_name):]
|
|
312 |
- chain.append(element.full_name)
|
|
313 |
- raise LoadError(LoadErrorReason.CIRCULAR_DEPENDENCY,
|
|
314 |
- ("Circular dependency detected at element: {}\n" +
|
|
315 |
- "Dependency chain: {}")
|
|
316 |
- .format(element.full_name, " -> ".join(chain)))
|
|
317 |
- |
|
318 |
- # Push / Check each dependency / Pop
|
|
319 |
- check_elements[element] = True
|
|
320 |
- sequence.append(element.full_name)
|
|
321 |
- for dep in element.dependencies:
|
|
322 |
- dep.element._loader._check_circular_deps(dep.element, check_elements, validated, sequence)
|
|
323 |
- del check_elements[element]
|
|
324 |
- sequence.pop()
|
|
325 |
- |
|
326 |
- # Eliminate duplicate paths
|
|
327 |
- validated[element] = True
|
|
266 |
+ return element
|
|
328 | 267 |
|
329 | 268 |
# _sort_dependencies():
|
330 | 269 |
#
|
... | ... | @@ -42,7 +42,6 @@ initialized = False |
42 | 42 |
#
|
43 | 43 |
# The special 'all' value will enable all profiles.
|
44 | 44 |
class Topics():
|
45 |
- CIRCULAR_CHECK = 'circ-dep-check'
|
|
46 | 45 |
SORT_DEPENDENCIES = 'sort-deps'
|
47 | 46 |
LOAD_LOADER = 'load-loader'
|
48 | 47 |
LOAD_CONTEXT = 'load-context'
|