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