| # This Source Code Form is subject to the terms of the Mozilla Public |
| # License, v. 2.0. If a copy of the MPL was not distributed with this |
| # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| |
| from __future__ import absolute_import, unicode_literals |
| |
| from collections import OrderedDict |
| from itertools import groupby |
| from operator import itemgetter |
| from os.path import dirname |
| |
| WHITESPACE_CHARACTERS = ' \t' |
| |
| def indentation(line): |
| """Number of whitespace (tab and space) characters at start of |line|.""" |
| i = 0 |
| while i < len(line): |
| if line[i] not in WHITESPACE_CHARACTERS: |
| break |
| i += 1 |
| return i |
| |
| def dependency_map(lines): |
| """Return a dictionary with keys that are targets and values that |
| are ordered lists of targets that should also be built. |
| |
| This implementation is O(n^2), but lovely and simple! We walk the |
| targets in the list, and for each target we walk backwards |
| collecting its dependencies. To make the walking easier, we |
| reverse the list so that we are always walking forwards. |
| |
| """ |
| pairs = [(indentation(line), line.strip()) for line in lines] |
| pairs.reverse() |
| |
| deps = {} |
| |
| for i, (indent, target) in enumerate(pairs): |
| if not deps.has_key(target): |
| deps[target] = [] |
| |
| for j in range(i+1, len(pairs)): |
| ind, tar = pairs[j] |
| if ind < indent: |
| indent = ind |
| if tar not in deps[target]: |
| deps[target].append(tar) |
| |
| return deps |
| |
| def all_dependencies(*targets, **kwargs): |
| """Return a list containing all the dependencies of |targets|. |
| |
| The relative order of targets is maintained if possible. |
| |
| """ |
| dm = kwargs.pop('dependency_map', None) |
| if dm is None: |
| dm = dependency_map(targets) |
| |
| all_targets = OrderedDict() # Used as an ordered set. |
| |
| for target in targets: |
| if target in dm: |
| for dependency in dm[target]: |
| # Move element back in the ordered set. |
| if dependency in all_targets: |
| del all_targets[dependency] |
| all_targets[dependency] = True |
| |
| return all_targets.keys() |
| |
| def get_components(path): |
| """Take a path and return all the components of the path.""" |
| paths = [path] |
| while True: |
| parent = dirname(paths[-1]) |
| if parent == "": |
| break |
| paths.append(parent) |
| |
| paths.reverse() |
| return paths |
| |
| def add_extra_dependencies(target_pairs, dependency_map): |
| """Take a list [(make_dir, make_target)] and expand (make_dir, None) |
| entries with extra make dependencies from |dependency_map|. |
| |
| Returns an iterator of pairs (make_dir, make_target). |
| |
| """ |
| all_targets = OrderedDict() # Used as an ordered set. |
| make_dirs = OrderedDict() # Used as an ordered set. |
| |
| for make_target, group in groupby(target_pairs, itemgetter(1)): |
| # Return non-simple directory targets untouched. |
| if make_target is not None: |
| for pair in group: |
| # Generate dependencies for all components of a path. |
| # Given path a/b/c, examine a, a/b, and a/b/c in that order. |
| paths = get_components(pair[1]) |
| # For each component of a path, find and add all dependencies |
| # to the final target list. |
| for target in paths: |
| if target not in all_targets: |
| yield pair[0], target |
| all_targets[target] = True |
| continue |
| |
| # Add extra dumbmake dependencies to simple directory targets. |
| for make_dir, _ in group: |
| if make_dir not in make_dirs: |
| yield make_dir, None |
| make_dirs[make_dir] = True |
| |
| all_components = [] |
| for make_dir in make_dirs.iterkeys(): |
| all_components.extend(get_components(make_dir)) |
| |
| for i in all_dependencies(*all_components, dependency_map=dependency_map): |
| if i not in make_dirs: |
| yield i, None |