Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(107)

Side by Side Diff: pylib/gyp/input.py

Issue 664253005: Simplify and optimize FindCycles (Closed) Base URL: https://chromium.googlesource.com/external/gyp.git@master
Patch Set: Add test for file cycle and fix file full cycle case Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | test/errors/dependency_cycle.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2012 Google Inc. All rights reserved. 1 # Copyright (c) 2012 Google Inc. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 from compiler.ast import Const 5 from compiler.ast import Const
6 from compiler.ast import Dict 6 from compiler.ast import Dict
7 from compiler.ast import Discard 7 from compiler.ast import Discard
8 from compiler.ast import List 8 from compiler.ast import List
9 from compiler.ast import Module 9 from compiler.ast import Module
10 from compiler.ast import Node 10 from compiler.ast import Node
(...skipping 1538 matching lines...) Expand 10 before | Expand all | Expand 10 after
1549 break 1549 break
1550 1550
1551 if is_in_degree_zero: 1551 if is_in_degree_zero:
1552 # All of the dependent's dependencies are already in flat_list. Add 1552 # All of the dependent's dependencies are already in flat_list. Add
1553 # it to in_degree_zeros where it will be processed in a future 1553 # it to in_degree_zeros where it will be processed in a future
1554 # iteration of the outer loop. 1554 # iteration of the outer loop.
1555 in_degree_zeros.add(node_dependent) 1555 in_degree_zeros.add(node_dependent)
1556 1556
1557 return list(flat_list) 1557 return list(flat_list)
1558 1558
1559 def FindCycles(self, path=None): 1559 def FindCycles(self):
1560 """ 1560 """
1561 Returns a list of cycles in the graph, where each cycle is its own list. 1561 Returns a list of cycles in the graph, where each cycle is its own list.
1562 """ 1562 """
1563 if path is None: 1563 results = []
1564 path = [self] 1564 visited = set()
1565 1565
1566 results = [] 1566 def Visit(node, path):
1567 for node in self.dependents: 1567 for child in node.dependents:
1568 if node in path: 1568 if child in path:
1569 cycle = [node] 1569 results.append([child] + path[:path.index(child) + 1])
1570 for part in path: 1570 elif not child in visited:
1571 cycle.append(part) 1571 visited.add(child)
1572 if part == node: 1572 Visit(child, [child] + path)
1573 break
1574 results.append(tuple(cycle))
1575 else:
1576 results.extend(node.FindCycles([node] + path))
1577 1573
1578 return list(set(results)) 1574 visited.add(self)
1575 Visit(self, [self])
1576
1577 return results
1579 1578
1580 def DirectDependencies(self, dependencies=None): 1579 def DirectDependencies(self, dependencies=None):
1581 """Returns a list of just direct dependencies.""" 1580 """Returns a list of just direct dependencies."""
1582 if dependencies == None: 1581 if dependencies == None:
1583 dependencies = [] 1582 dependencies = []
1584 1583
1585 for dependency in self.dependencies: 1584 for dependency in self.dependencies:
1586 # Check for None, corresponding to the root node. 1585 # Check for None, corresponding to the root node.
1587 if dependency.ref != None and dependency.ref not in dependencies: 1586 if dependency.ref != None and dependency.ref not in dependencies:
1588 dependencies.append(dependency.ref) 1587 dependencies.append(dependency.ref)
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after
1785 dependency_node = dependency_nodes.get(dependency) 1784 dependency_node = dependency_nodes.get(dependency)
1786 if not dependency_node: 1785 if not dependency_node:
1787 raise GypError("Dependency '%s' not found while " 1786 raise GypError("Dependency '%s' not found while "
1788 "trying to load target %s" % (dependency, target)) 1787 "trying to load target %s" % (dependency, target))
1789 target_node.dependencies.append(dependency_node) 1788 target_node.dependencies.append(dependency_node)
1790 dependency_node.dependents.append(target_node) 1789 dependency_node.dependents.append(target_node)
1791 1790
1792 flat_list = root_node.FlattenToList() 1791 flat_list = root_node.FlattenToList()
1793 1792
1794 # If there's anything left unvisited, there must be a circular dependency 1793 # If there's anything left unvisited, there must be a circular dependency
1795 # (cycle). If you need to figure out what's wrong, look for elements of 1794 # (cycle).
1796 # targets that are not in flat_list.
1797 if len(flat_list) != len(targets): 1795 if len(flat_list) != len(targets):
1798 raise DependencyGraphNode.CircularException( 1796 if not root_node.dependents:
1799 'Some targets not reachable, cycle in dependency graph detected: ' + 1797 # If all targets have dependencies, add the first target as a dependent
1800 ' '.join(set(flat_list) ^ set(targets))) 1798 # of root_node so that the cycle can be discovered from root_node.
1799 target = targets.keys()[0]
1800 target_node = dependency_nodes[target]
1801 target_node.dependencies.append(root_node)
1802 root_node.dependents.append(target_node)
1803
1804 cycles = []
1805 for cycle in root_node.FindCycles():
1806 paths = [node.ref for node in cycle]
1807 cycles.append('Cycle: %s' % ' -> '.join(paths))
1808 raise DependencyGraphNode.CircularException, \
scottmg 2014/10/21 18:31:47 I meant this \ vs. (, mostly since that's what it
cjhopman 2014/10/21 19:52:54 Ha, I really thought I fixed it here before.
1809 'Cycles in dependency graph detected:\n' + '\n'.join(cycles)
1801 1810
1802 return [dependency_nodes, flat_list] 1811 return [dependency_nodes, flat_list]
1803 1812
1804 1813
1805 def VerifyNoGYPFileCircularDependencies(targets): 1814 def VerifyNoGYPFileCircularDependencies(targets):
1806 # Create a DependencyGraphNode for each gyp file containing a target. Put 1815 # Create a DependencyGraphNode for each gyp file containing a target. Put
1807 # it into a dict for easy access. 1816 # it into a dict for easy access.
1808 dependency_nodes = {} 1817 dependency_nodes = {}
1809 for target in targets.iterkeys(): 1818 for target in targets.iterkeys():
1810 build_file = gyp.common.BuildFile(target) 1819 build_file = gyp.common.BuildFile(target)
(...skipping 29 matching lines...) Expand all
1840 for build_file_node in dependency_nodes.itervalues(): 1849 for build_file_node in dependency_nodes.itervalues():
1841 if len(build_file_node.dependencies) == 0: 1850 if len(build_file_node.dependencies) == 0:
1842 build_file_node.dependencies.append(root_node) 1851 build_file_node.dependencies.append(root_node)
1843 root_node.dependents.append(build_file_node) 1852 root_node.dependents.append(build_file_node)
1844 1853
1845 flat_list = root_node.FlattenToList() 1854 flat_list = root_node.FlattenToList()
1846 1855
1847 # If there's anything left unvisited, there must be a circular dependency 1856 # If there's anything left unvisited, there must be a circular dependency
1848 # (cycle). 1857 # (cycle).
1849 if len(flat_list) != len(dependency_nodes): 1858 if len(flat_list) != len(dependency_nodes):
1850 bad_files = [] 1859 if not root_node.dependents:
1851 for file in dependency_nodes.iterkeys(): 1860 # If all files have dependencies, add the first file as a dependent
1852 if not file in flat_list: 1861 # of root_node so that the cycle can be discovered from root_node.
1853 bad_files.append(file) 1862 file_node = dependency_nodes.values()[0]
1854 common_path_prefix = os.path.commonprefix(dependency_nodes) 1863 file_node.dependencies.append(root_node)
1864 root_node.dependents.append(file_node)
1855 cycles = [] 1865 cycles = []
1856 for cycle in root_node.FindCycles(): 1866 for cycle in root_node.FindCycles():
1857 simplified_paths = [] 1867 paths = [node.ref for node in cycle]
1858 for node in cycle: 1868 cycles.append('Cycle: %s' % ' -> '.join(paths))
1859 assert(node.ref.startswith(common_path_prefix)) 1869 raise DependencyGraphNode.CircularException(
1860 simplified_paths.append(node.ref[len(common_path_prefix):]) 1870 'Cycles in .gyp file dependency graph detected:\n' + '\n'.join(cycles))
1861 cycles.append('Cycle: %s' % ' -> '.join(simplified_paths))
1862 raise DependencyGraphNode.CircularException, \
1863 'Cycles in .gyp file dependency graph detected:\n' + '\n'.join(cycles)
1864 1871
1865 1872
1866 def DoDependentSettings(key, flat_list, targets, dependency_nodes): 1873 def DoDependentSettings(key, flat_list, targets, dependency_nodes):
1867 # key should be one of all_dependent_settings, direct_dependent_settings, 1874 # key should be one of all_dependent_settings, direct_dependent_settings,
1868 # or link_settings. 1875 # or link_settings.
1869 1876
1870 for target in flat_list: 1877 for target in flat_list:
1871 target_dict = targets[target] 1878 target_dict = targets[target]
1872 build_file = gyp.common.BuildFile(target) 1879 build_file = gyp.common.BuildFile(target)
1873 1880
(...skipping 990 matching lines...) Expand 10 before | Expand all | Expand 10 after
2864 ValidateRunAsInTarget(target, target_dict, build_file) 2871 ValidateRunAsInTarget(target, target_dict, build_file)
2865 ValidateActionsInTarget(target, target_dict, build_file) 2872 ValidateActionsInTarget(target, target_dict, build_file)
2866 2873
2867 # Generators might not expect ints. Turn them into strs. 2874 # Generators might not expect ints. Turn them into strs.
2868 TurnIntIntoStrInDict(data) 2875 TurnIntIntoStrInDict(data)
2869 2876
2870 # TODO(mark): Return |data| for now because the generator needs a list of 2877 # TODO(mark): Return |data| for now because the generator needs a list of
2871 # build files that came in. In the future, maybe it should just accept 2878 # build files that came in. In the future, maybe it should just accept
2872 # a list, and not the whole data dict. 2879 # a list, and not the whole data dict.
2873 return [flat_list, targets, data] 2880 return [flat_list, targets, data]
OLDNEW
« no previous file with comments | « no previous file | test/errors/dependency_cycle.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698