|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 from __future__ import unicode_literals |
|
6 |
|
7 from collections import OrderedDict |
|
8 from itertools import groupby |
|
9 from operator import itemgetter |
|
10 from os.path import dirname |
|
11 |
|
12 WHITESPACE_CHARACTERS = ' \t' |
|
13 |
|
14 def indentation(line): |
|
15 """Number of whitespace (tab and space) characters at start of |line|.""" |
|
16 i = 0 |
|
17 while i < len(line): |
|
18 if line[i] not in WHITESPACE_CHARACTERS: |
|
19 break |
|
20 i += 1 |
|
21 return i |
|
22 |
|
23 def dependency_map(lines): |
|
24 """Return a dictionary with keys that are targets and values that |
|
25 are ordered lists of targets that should also be built. |
|
26 |
|
27 This implementation is O(n^2), but lovely and simple! We walk the |
|
28 targets in the list, and for each target we walk backwards |
|
29 collecting its dependencies. To make the walking easier, we |
|
30 reverse the list so that we are always walking forwards. |
|
31 |
|
32 """ |
|
33 pairs = [(indentation(line), line.strip()) for line in lines] |
|
34 pairs.reverse() |
|
35 |
|
36 deps = {} |
|
37 |
|
38 for i, (indent, target) in enumerate(pairs): |
|
39 if not deps.has_key(target): |
|
40 deps[target] = [] |
|
41 |
|
42 for j in range(i+1, len(pairs)): |
|
43 ind, tar = pairs[j] |
|
44 if ind < indent: |
|
45 indent = ind |
|
46 if tar not in deps[target]: |
|
47 deps[target].append(tar) |
|
48 |
|
49 return deps |
|
50 |
|
51 def all_dependencies(*targets, **kwargs): |
|
52 """Return a list containing all the dependencies of |targets|. |
|
53 |
|
54 The relative order of targets is maintained if possible. |
|
55 |
|
56 """ |
|
57 dm = kwargs.pop('dependency_map', None) |
|
58 if dm is None: |
|
59 dm = dependency_map(targets) |
|
60 |
|
61 all_targets = OrderedDict() # Used as an ordered set. |
|
62 |
|
63 for target in targets: |
|
64 if target in dm: |
|
65 for dependency in dm[target]: |
|
66 # Move element back in the ordered set. |
|
67 if dependency in all_targets: |
|
68 del all_targets[dependency] |
|
69 all_targets[dependency] = True |
|
70 |
|
71 return all_targets.keys() |
|
72 |
|
73 def get_components(path): |
|
74 """Take a path and return all the components of the path.""" |
|
75 paths = [path] |
|
76 while True: |
|
77 parent = dirname(paths[-1]) |
|
78 if parent == "": |
|
79 break |
|
80 paths.append(parent) |
|
81 |
|
82 paths.reverse() |
|
83 return paths |
|
84 |
|
85 def add_extra_dependencies(target_pairs, dependency_map): |
|
86 """Take a list [(make_dir, make_target)] and expand (make_dir, None) |
|
87 entries with extra make dependencies from |dependency_map|. |
|
88 |
|
89 Returns an iterator of pairs (make_dir, make_target). |
|
90 |
|
91 """ |
|
92 all_targets = OrderedDict() # Used as an ordered set. |
|
93 make_dirs = OrderedDict() # Used as an ordered set. |
|
94 |
|
95 for make_target, group in groupby(target_pairs, itemgetter(1)): |
|
96 # Return non-simple directory targets untouched. |
|
97 if make_target is not None: |
|
98 for pair in group: |
|
99 # Generate dependencies for all components of a path. |
|
100 # Given path a/b/c, examine a, a/b, and a/b/c in that order. |
|
101 paths = get_components(pair[1]) |
|
102 # For each component of a path, find and add all dependencies |
|
103 # to the final target list. |
|
104 for target in paths: |
|
105 if target not in all_targets: |
|
106 yield pair[0], target |
|
107 all_targets[target] = True |
|
108 continue |
|
109 |
|
110 # Add extra dumbmake dependencies to simple directory targets. |
|
111 for make_dir, _ in group: |
|
112 if make_dir not in make_dirs: |
|
113 yield make_dir, None |
|
114 make_dirs[make_dir] = True |
|
115 |
|
116 all_components = [] |
|
117 for make_dir in make_dirs.iterkeys(): |
|
118 all_components.extend(get_components(make_dir)) |
|
119 |
|
120 for i in all_dependencies(*all_components, dependency_map=dependency_map): |
|
121 if i not in make_dirs: |
|
122 yield i, None |