1# Copyright 2015 gRPC authors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Buildgen transitive dependencies
15
16This takes the list of libs, node_modules, and targets from our
17yaml dictionary, and adds to each the transitive closure
18of the list of dependencies.
19"""
20
21
22def transitive_deps(lib_map, node):
23    """Returns a list of transitive dependencies from node.
24
25    Recursively iterate all dependent node in a depth-first fashion and
26    list a result using a topological sorting.
27    """
28    result = []
29    seen = set()
30    start = node
31
32    def recursive_helper(node):
33        if node is None:
34            return
35        for dep in node.get("deps", []):
36            if dep not in seen:
37                seen.add(dep)
38                next_node = lib_map.get(dep)
39                recursive_helper(next_node)
40        if node is not start:
41            result.insert(0, node["name"])
42
43    recursive_helper(node)
44    return result
45
46
47def mako_plugin(dictionary):
48    """The exported plugin code for transitive_dependencies.
49
50    Iterate over each list and check each item for a deps list. We add a
51    transitive_deps property to each with the transitive closure of those
52    dependency lists. The result list is sorted in a topological ordering.
53    """
54    lib_map = {lib['name']: lib for lib in dictionary.get('libs')}
55
56    for target_name, target_list in list(dictionary.items()):
57        for target in target_list:
58            if isinstance(target, dict):
59                if 'deps' in target or target_name == 'libs':
60                    if not 'deps' in target:
61                        # make sure all the libs have the "deps" field populated
62                        target['deps'] = []
63                    target['transitive_deps'] = transitive_deps(lib_map, target)
64
65    python_dependencies = dictionary.get('python_dependencies')
66    python_dependencies['transitive_deps'] = transitive_deps(
67        lib_map, python_dependencies)
68