Example #1
0
 def open(self):
     """Open all the files that will be written by this dumper."""
     self._build_file = open(partfile(self.build_pathname()), "w")
     self._decl_file = open(partfile(self.decl_pathname()), "w")
     self._build_writer = NinjaWriter(self._build_file)
     self._decl_writer = NinjaWriter(self._decl_file)
     _write_caution_header(self._build_writer)
     _write_caution_header(self._decl_writer)
     self._write_build_header()
     # Keep track of added rule names because we dump rule only once.
     self._rules = {}
     # Keep track of added pool names because we dump them only once.
     self._pools = set()
     self._build_count = 0
     self._subgenerator_count = 0
Example #2
0
class NinjaDumper(object):

    def __init__(self, build_dir, top_build_dir=""):
        self._build_dir = build_dir.rstrip("/")
        if top_build_dir:
            self._top_build_dir = top_build_dir.rstrip("/")
        else:
            self._top_build_dir = self._build_dir
        self._build_file = None
        self._decl_file = None
        self._dumper = None
        self._build_count = None
        self._subgenerator_count = None

    def build_pathname(self):
        return os.path.join(self._build_dir, config.BUILD_FILENAME)

    def decl_pathname(self):
        return os.path.join(self._build_dir, config.DECL_FILENAME)

    def __enter__(self):
        self.open()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()
        if exc_type is None:
            # No exception was raised so we can safely apply changes.
            self.apply()
        return False # Tell to re-raise the exception if there was one.

    def open(self):
        """Open all the files that will be written by this dumper."""
        self._build_file = open(partfile(self.build_pathname()), "w")
        self._decl_file = open(partfile(self.decl_pathname()), "w")
        self._build_writer = NinjaWriter(self._build_file)
        self._decl_writer = NinjaWriter(self._decl_file)
        _write_caution_header(self._build_writer)
        _write_caution_header(self._decl_writer)
        self._write_build_header()
        # Keep track of added rule names because we dump rule only once.
        self._rules = {}
        # Keep track of added pool names because we dump them only once.
        self._pools = set()
        self._build_count = 0
        self._subgenerator_count = 0

    def close(self):
        """Close all the files opened by this dumper."""
        self._build_file.close()
        self._decl_file.close()
        self._build_file = None
        self._decl_file = None
        self._build_writer = None
        self._decl_writer = None

    def apply(self):
        """Apply the new generated files so that ninja can see them."""
        # TODO(Nicolas Despres): Make this function atomic w.r.t signals.
        for pathname in (self.build_pathname(),
                         self.decl_pathname()):
            os.rename(partfile(pathname), pathname)

    def is_opened(self):
        return self._build_file is not None \
            and self._decl_file is not None

    def _check_is_opened(self):
        if not self.is_opened():
            raise RuntimeError("dumper not opened")

    def _write_build_header(self):
        self._build_writer.newline()
        self._build_writer.variable("ninja_required_version", "1.6")
        self._build_writer.newline()
        self._build_writer.include(self.decl_pathname())

    def dump(self, obj):
        # TODO(Nicolas Despres): Catch raised exception to provide some context.
        self._check_is_opened()
        if hasattr(obj, "generate"):
            # TODO(Nicolas Despres): Redirect stdout,stderr to a logger of
            #   some kind.
            obj.generate()
        if isinstance(obj, Command):
            self._dump_command(obj)
        elif isinstance(obj, SubGenerator):
            self._dump_subgenerator(obj)
        elif isinstance(obj, Phony):
            self._dump_phony(obj)
        elif isinstance(obj, Default):
            self._dump_default(obj)
        else:
            raise TypeError("cannot dump {} object: {!r}"
                            .format(type(obj).__name__, obj))

    @property
    def rule_count(self):
        return len(self._rules)

    @property
    def build_count(self):
        return self._build_count

    @property
    def subgenerator_count(self):
        return self._subgenerator_count

    def _add_rule(self, rule):
        """Register the rule if it is not and compute its name.

        Return whether the rule has been added.
        """
        try:
            name = self._rules[rule]
        except KeyError:
            rule.name = "r{}".format(len(self._rules))
            self._rules[rule] = rule.name
            return True
        else:
            rule.name = name
            return False

    def _dump_command(self, cmd):
        if not cmd.has_outputs():
            raise ValueError("no outputs for command '{}'".format(cmd))
        if type(cmd).__name__ == "phony":
            raise ValueError("command class cannot be named 'phony'")
        if _is_custom_pool(cmd.pool):
            pool_name = _pool_name(cmd.pool)
            if pool_name not in self._pools:
                self._pools.add(pool_name)
                self._dump_pool_statement(cmd.pool)
        rule = _cmd_rule(cmd)
        if self._add_rule(rule):
            self._dump_rule_statement(rule)
        self._dump_build_statement(rule, cmd)
        self._build_count += 1

    def _dump_pool_statement(self, pool):
        self._decl_writer.newline()
        if pool.__doc__:
            self._decl_writer.raw_comment(_dedent_docstring(pool.__doc__))
        self._decl_writer.pool(_pool_name(pool), pool.depth)

    def _dump_rule_statement(self, rule):
        self._decl_writer.newline()
        if rule.doc:
            self._decl_writer.raw_comment(_dedent_docstring(rule.doc))
        if not rule.expanded_command():
            raise ValueError("empty command string")
        self._decl_writer.rule(
            rule.name,
            rule.command,
            description=rule.description,
            generator=rule.generator,
            restat=rule.restat,
            pool=rule.pool)

    def _dump_build_statement(self, rule, cmd):
        self._build_writer.newline()
        self._build_writer.build(
            self._format_targets(cmd.all_outputs()),
            rule.name,
            inputs=self._format_targets(cmd.inputs),
            implicit=self._format_targets(cmd.implicit_inputs),
            order_only=self._format_targets(cmd.orderonly_inputs),
            variables=rule.variables)

    def _dump_subgenerator(self, subgen):
        self._build_writer.comment("{name} sub-project from '{source}'"
                                   .format(name=type(subgen).__name__,
                                           source=subgen.source),
                                   has_path=True)
        self._build_writer.subninja(subgen.output)
        self._build_writer.newline()
        self._subgenerator_count += 1

    def _format_targets(self, filenames):
        def f(target):
            target = getattr(target, "build_edge_key", target)
            # We convert targets to str so that object like pathlib.Path are
            # accepted.
            return self._relative_to_build_dir(str(target))
        return sorted(map(f, filenames))

    def _relative_to_build_dir(self, path):
        if path.startswith(self._top_build_dir):
            assert not self._top_build_dir.endswith("/")
            return path[len(self._top_build_dir)+1:]
        return path

    def _dump_phony(self, phony):
        self._build_writer.newline()
        self._build_writer.build(
            self._format_targets(phony.outputs),
            "phony",
            inputs=self._format_targets(phony.inputs),
            implicit=self._format_targets(phony.implicit_inputs),
            order_only=self._format_targets(phony.orderonly_inputs))

    def _dump_default(self, default):
        self._build_writer.newline()
        self._build_writer.default(self._format_targets(default.targets))