#!/usr/bin/env python3 # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. from __future__ import print_function import lintutils from subprocess import PIPE import argparse import difflib import multiprocessing as mp import sys from functools import partial # examine the output of clang-format and if changes are # present assemble a (unified)patch of the difference def _check_one_file(filename, formatted): with open(filename, "rb") as reader: original = reader.read() if formatted != original: # Run the equivalent of diff -u diff = list(difflib.unified_diff( original.decode('utf8').splitlines(True), formatted.decode('utf8').splitlines(True), fromfile=filename, tofile="{} (after clang format)".format( filename))) else: diff = None return filename, diff if __name__ == "__main__": parser = argparse.ArgumentParser( description="Runs clang-format on all of the source " "files. If --fix is specified enforce format by " "modifying in place, otherwise compare the output " "with the existing file and output any necessary " "changes as a patch in unified diff format") parser.add_argument("--clang_format_binary", required=True, help="Path to the clang-format binary") parser.add_argument("--exclude_globs", help="Filename containing globs for files " "that should be excluded from the checks") parser.add_argument("--source_dir", required=True, help="Root directory of the source code") parser.add_argument("--fix", default=False, action="store_true", help="If specified, will re-format the source " "code instead of comparing the re-formatted " "output, defaults to %(default)s") parser.add_argument("--quiet", default=False, action="store_true", help="If specified, only print errors") arguments = parser.parse_args() exclude_globs = [] if arguments.exclude_globs: with open(arguments.exclude_globs) as f: exclude_globs.extend(line.strip() for line in f) formatted_filenames = [] for path in lintutils.get_sources(arguments.source_dir, exclude_globs): formatted_filenames.append(str(path)) if arguments.fix: if not arguments.quiet: print("\n".join(map(lambda x: "Formatting {}".format(x), formatted_filenames))) # Break clang-format invocations into chunks: each invocation formats # 16 files. Wait for all processes to complete results = lintutils.run_parallel([ [arguments.clang_format_binary, "-i"] + some for some in lintutils.chunk(formatted_filenames, 16) ]) for returncode, stdout, stderr in results: # if any clang-format reported a parse error, bubble it if returncode != 0: sys.exit(returncode) else: # run an instance of clang-format for each source file in parallel, # then wait for all processes to complete results = lintutils.run_parallel([ [arguments.clang_format_binary, filename] for filename in formatted_filenames ], stdout=PIPE, stderr=PIPE) checker_args = [] for filename, res in zip(formatted_filenames, results): # if any clang-format reported a parse error, bubble it returncode, stdout, stderr = res if returncode != 0: print(stderr) sys.exit(returncode) checker_args.append((filename, stdout)) error = False pool = mp.Pool() try: # check the output from each invocation of clang-format in parallel for filename, diff in pool.starmap(_check_one_file, checker_args): if not arguments.quiet: print("Checking {}".format(filename)) if diff: print("{} had clang-format style issues".format(filename)) # Print out the diff to stderr error = True # pad with a newline print(file=sys.stderr) sys.stderr.writelines(diff) except Exception: error = True raise finally: pool.terminate() pool.join() sys.exit(1 if error else 0)