DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (2658e3d7a2b2)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
# 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

import os
import re
from types import StringTypes
from collections import Iterable


class Makefile(object):
    '''Provides an interface for writing simple makefiles

    Instances of this class are created, populated with rules, then
    written.
    '''

    def __init__(self):
        self._statements = []

    def create_rule(self, targets=[]):
        '''
        Create a new rule in the makefile for the given targets.
        Returns the corresponding Rule instance.
        '''
        rule = Rule(targets)
        self._statements.append(rule)
        return rule

    def add_statement(self, statement):
        '''
        Add a raw statement in the makefile. Meant to be used for
        simple variable assignments.
        '''
        self._statements.append(statement)

    def dump(self, fh, removal_guard=True):
        '''
        Dump all the rules to the given file handle. Optionally (and by
        default), add guard rules for file removals (empty rules for other
        rules' dependencies)
        '''
        all_deps = set()
        all_targets = set()
        for statement in self._statements:
            if isinstance(statement, Rule):
                statement.dump(fh)
                all_deps.update(statement.dependencies())
                all_targets.update(statement.targets())
            else:
                fh.write('%s\n' % statement)
        if removal_guard:
            guard = Rule(sorted(all_deps - all_targets))
            guard.dump(fh)


class _SimpleOrderedSet(object):
    '''
    Simple ordered set, specialized for used in Rule below only.
    It doesn't expose a complete API, and normalizes path separators
    at insertion.
    '''
    def __init__(self):
        self._list = []
        self._set = set()

    def __nonzero__(self):
        return bool(self._set)

    def __iter__(self):
        return iter(self._list)

    def __contains__(self, key):
        return key in self._set

    def update(self, iterable):
        def _add(iterable):
            emitted = set()
            for i in iterable:
                i = i.replace(os.sep, '/')
                if i not in self._set and i not in emitted:
                    yield i
                    emitted.add(i)
        added = list(_add(iterable))
        self._set.update(added)
        self._list.extend(added)


class Rule(object):
    '''Class handling simple rules in the form:
           target1 target2 ... : dep1 dep2 ...
                   command1
                   command2
                   ...
    '''
    def __init__(self, targets=[]):
        self._targets = _SimpleOrderedSet()
        self._dependencies = _SimpleOrderedSet()
        self._commands = []
        self.add_targets(targets)

    def add_targets(self, targets):
        '''Add additional targets to the rule.'''
        assert isinstance(targets, Iterable) and not isinstance(targets, StringTypes)
        self._targets.update(targets)
        return self

    def add_dependencies(self, deps):
        '''Add dependencies to the rule.'''
        assert isinstance(deps, Iterable) and not isinstance(deps, StringTypes)
        self._dependencies.update(deps)
        return self

    def add_commands(self, commands):
        '''Add commands to the rule.'''
        assert isinstance(commands, Iterable) and not isinstance(commands, StringTypes)
        self._commands.extend(commands)
        return self

    def targets(self):
        '''Return an iterator on the rule targets.'''
        # Ensure the returned iterator is actually just that, an iterator.
        # Avoids caller fiddling with the set itself.
        return iter(self._targets)

    def dependencies(self):
        '''Return an iterator on the rule dependencies.'''
        return iter(d for d in self._dependencies if not d in self._targets)

    def commands(self):
        '''Return an iterator on the rule commands.'''
        return iter(self._commands)

    def dump(self, fh):
        '''
        Dump the rule to the given file handle.
        '''
        if not self._targets:
            return
        fh.write('%s:' % ' '.join(self._targets))
        if self._dependencies:
            fh.write(' %s' % ' '.join(self.dependencies()))
        fh.write('\n')
        for cmd in self._commands:
            fh.write('\t%s\n' % cmd)


# colon followed by anything except a slash (Windows path detection)
_depfilesplitter = re.compile(r':(?![\\/])')


def read_dep_makefile(fh):
    """
    Read the file handler containing a dep makefile (simple makefile only
    containing dependencies) and returns an iterator of the corresponding Rules
    it contains. Ignores removal guard rules.
    """

    rule = ''
    for line in fh.readlines():
        assert not line.startswith('\t')
        line = line.strip()
        if line.endswith('\\'):
            rule += line[:-1]
        else:
            rule += line
            split_rule = _depfilesplitter.split(rule, 1)
            if len(split_rule) > 1 and split_rule[1].strip():
                yield Rule(split_rule[0].strip().split()) \
                      .add_dependencies(split_rule[1].strip().split())
            rule = ''

    if rule:
        raise Exception('Makefile finishes with a backslash. Expected more input.')

def write_dep_makefile(fh, target, deps):
    '''
    Write a Makefile containing only target's dependencies to the file handle
    specified.
    '''
    mk = Makefile()
    rule = mk.create_rule(targets=[target])
    rule.add_dependencies(deps)
    mk.dump(fh, removal_guard=True)