Source code

Revision control

Copy as Markdown

Other Tools

# 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
import os
import re
from import Iterable
import six
class Makefile(object):
"""Provides an interface for writing simple makefiles
Instances of this class are created, populated with rules, then
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.
targets = list(targets)
for target in targets:
assert isinstance(target, six.text_type)
rule = Rule(targets)
return rule
def add_statement(self, statement):
Add a raw statement in the makefile. Meant to be used for
simple variable assignments.
assert isinstance(statement, six.text_type)
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):
fh.write("%s\n" % statement)
if removal_guard:
guard = Rule(sorted(all_deps - all_targets))
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 __bool__(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
added = list(_add(iterable))
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 = []
def add_targets(self, targets):
"""Add additional targets to the rule."""
assert isinstance(targets, Iterable) and not isinstance(
targets, six.string_types
targets = list(targets)
for target in targets:
assert isinstance(target, six.text_type)
return self
def add_dependencies(self, deps):
"""Add dependencies to the rule."""
assert isinstance(deps, Iterable) and not isinstance(deps, six.string_types)
deps = list(deps)
for dep in deps:
assert isinstance(dep, six.text_type)
return self
def add_commands(self, commands):
"""Add commands to the rule."""
assert isinstance(commands, Iterable) and not isinstance(
commands, six.string_types
commands = list(commands)
for command in commands:
assert isinstance(command, six.text_type)
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 d not 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:
fh.write("%s:" % " ".join(self._targets))
if self._dependencies:
fh.write(" %s" % " ".join(self.dependencies()))
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():
line = six.ensure_text(line)
assert not line.startswith("\t")
line = line.strip()
if line.endswith("\\"):
rule += line[:-1]
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(
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
mk = Makefile()
rule = mk.create_rule(targets=[target])
mk.dump(fh, removal_guard=True)