Calls `build` after testing that at least one output file (as
        returned by `_outputs()` does not exist or is older than `mtime`. If
        the build fails, the build time is recorded and no other builds will be
        attempted on `input` until this method is called with a larger mtime.

def _build(self, src, path, dest, mtime):
        """Calls `build` after testing that at least one output file (as
        returned by `_outputs()` does not exist or is older than `mtime`. If
        the build fails, the build time is recorded and no other builds will be
        attempted on `input` until this method is called with a larger mtime.
        """
        input_path = os.path.join(src, path)
        output_paths = [os.path.join(dest, output) for output in
                        self._outputs(src, path)]

        if path in self.failures and mtime <= self.failures[path]:
            # the input file was not modified since the last recorded failure
            # as such, assume that the task will fail again and skip it
            return

        for output in output_paths:
            try:
                if \
                        os.path.exists(output) and \
                        mtime <= os.path.getmtime(output):
                    # output file exists and is up to date; no need to trigger
                    # build on this file's expense
                    continue
            except EnvironmentError:
                # usually happens when the output file has been deleted in
                # between the call to exists and the call to getmtime
                pass

            start = time.time()
            try:
                self.build(input_path, output_paths)
            except Exception as e:
                if isinstance(e, EnvironmentError):
                    # non-zero return code in sub-process; only show message
                    logging.error("{0} failed after {1:.2f}s: {2}".format(
                        termcolor.colored(path, "red", attrs=["bold"]),
                        time.time() - start, e.args[0]
                    ))
                else:
                    # probably a bug in the handler; show full trace
                    logging.exception("{0} failed after {1:.2f}s".format(
                        termcolor.colored(path, "red", attrs=["bold"]),
                        time.time() - start
                    ))
                self.failures[path] = start
            else:
                logging.info("{0} completed in {1:.2f}s".format(
                    termcolor.colored(path, "green", attrs=["bold"]),
                    time.time() - start
                ))
                self.failures.pop(path, None)
            break