109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
# Licensed under the MIT License.
|
|
|
|
# Invokes clang-format and reports formatting errors
|
|
# Optionally can cause clang-format to run the format in place with --reformat
|
|
|
|
import re, argparse, sys, subprocess, os.path, xml.dom.minidom
|
|
|
|
parser=argparse.ArgumentParser(description='Validate clang-fromat for files')
|
|
parser.add_argument('--file', metavar='INPUTFILE', type=str, nargs='+', help='Files to validate', required=True)
|
|
parser.add_argument('--output', metavar='OUTPUT', type=str, help='Output location', required=True)
|
|
parser.add_argument('--clangformat', type=str, help='path to the clang-format tool', required=True)
|
|
parser.add_argument('--reformat', action='store_true')
|
|
def main(argv):
|
|
|
|
args = parser.parse_args()
|
|
errorcount = 0
|
|
for file in args.file:
|
|
reformatcount = 0
|
|
# Invoke clang-format
|
|
if args.reformat:
|
|
|
|
# Retry the reformat operation so long as the file continues to be modified
|
|
# Sometimes clang-format will reformat a file, but running it again will apply additional changes
|
|
filetime = os.path.getmtime(file)
|
|
updatefile = True
|
|
while (updatefile):
|
|
proc = subprocess.Popen([args.clangformat] + ['-i', file], stdout=subprocess.PIPE, encoding="utf-8")
|
|
stdout = proc.stdout.read()
|
|
proc.wait()
|
|
|
|
updatefile = False
|
|
updatedtime = os.path.getmtime(file)
|
|
|
|
if (updatedtime != filetime):
|
|
updatefile = True
|
|
print("clang-format updated {}".format(file))
|
|
filetime = updatedtime
|
|
else:
|
|
proc = subprocess.Popen([args.clangformat] + ['-output-replacements-xml', file], stdout=subprocess.PIPE, encoding="utf-8")
|
|
stdout = proc.stdout.read()
|
|
proc.wait()
|
|
|
|
# Parse the output
|
|
dom = xml.dom.minidom.parseString(stdout)
|
|
|
|
# Iterate through the file and count the number of replacements on each source line
|
|
|
|
# Offset within the file
|
|
offset = 0
|
|
# Line number within the file
|
|
lineno = 0
|
|
|
|
# Current line with errors
|
|
linewitherrors = 0
|
|
replacementsonline = 0
|
|
with open (file, 'r') as input:
|
|
for replacement in dom.getElementsByTagName('replacement'):
|
|
reformatcount = reformatcount + 1
|
|
# replacement offset
|
|
roffset = int(replacement.getAttribute('offset'))
|
|
|
|
# advance the file one line at a time until the file offset
|
|
# is past the current replacement offset
|
|
while offset < roffset:
|
|
line = input.readline()
|
|
offset = input.tell()
|
|
lineno = lineno + 1
|
|
|
|
# If this replacement is on a different line from the last one,
|
|
# print the error information from the previous line
|
|
if linewitherrors != lineno and linewitherrors != 0:
|
|
err = "Error {} ({}): {} clang-format replacements on line".format(
|
|
file,
|
|
linewitherrors,
|
|
replacementsonline
|
|
)
|
|
|
|
print(err)
|
|
replacementsonline = 0
|
|
|
|
# Count the number of replacements on this line
|
|
linewitherrors = lineno
|
|
replacementsonline = replacementsonline + 1
|
|
|
|
# Print the last line of replacment information if any
|
|
if linewitherrors != 0:
|
|
err = "Error {} ({}): {} clang-format replacements on line".format(
|
|
file,
|
|
linewitherrors,
|
|
replacementsonline,
|
|
)
|
|
print(err)
|
|
print(" Run the clangformat target to auto-clean (e.g. \"ninja clangformat\")")
|
|
|
|
errorcount = errorcount + reformatcount
|
|
|
|
# Touch the output file if the operation was successful
|
|
if errorcount == 0:
|
|
with open (args.output, 'w') as output:
|
|
output.write("")
|
|
|
|
sys.exit(errorcount)
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|