1ec727ea7Spatrick#!/usr/bin/env python 2ec727ea7Spatrick# A tool to parse creates a document outlining how clang formatted the 3ec727ea7Spatrick# LLVM project is. 4ec727ea7Spatrick 5ec727ea7Spatrickimport sys 6ec727ea7Spatrickimport os 7ec727ea7Spatrickimport subprocess 8ec727ea7Spatrickfrom datetime import datetime 9ec727ea7Spatrick 10ec727ea7Spatrick 11ec727ea7Spatrickdef get_git_revision_short_hash(): 12*12c85518Srobert """ Get the get SHA in short hash form. """ 13ec727ea7Spatrick return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'] 14ec727ea7Spatrick ).decode(sys.stdout.encoding).strip() 15ec727ea7Spatrick 16ec727ea7Spatrick 17ec727ea7Spatrickdef get_style(count, passed): 18*12c85518Srobert """ Determine if this directory is good based on the number of clean 19*12c85518Srobert files vs the number of files in total. """ 20ec727ea7Spatrick if passed == count: 21ec727ea7Spatrick return ":good:" 22*12c85518Srobert if passed != 0: 23ec727ea7Spatrick return ":part:" 24ec727ea7Spatrick return ":none:" 25ec727ea7Spatrick 26ec727ea7Spatrick 27ec727ea7SpatrickTOP_DIR = os.path.join(os.path.dirname(__file__), '../../..') 28ec727ea7SpatrickCLANG_DIR = os.path.join(os.path.dirname(__file__), '../..') 29ec727ea7SpatrickDOC_FILE = os.path.join(CLANG_DIR, 'docs/ClangFormattedStatus.rst') 30*12c85518SrobertCLEAN_FILE = os.path.join(CLANG_DIR, 'docs/tools/clang-formatted-files.txt') 31ec727ea7Spatrick 32ec727ea7Spatrickrootdir = TOP_DIR 33ec727ea7Spatrick 34ec727ea7Spatrickskipped_dirs = [".git", "test"] 35ec727ea7Spatricksuffixes = (".cpp", ".h") 36ec727ea7Spatrick 37*12c85518SrobertRST_PREFIX = """\ 38ec727ea7Spatrick.. raw:: html 39ec727ea7Spatrick 40ec727ea7Spatrick <style type="text/css"> 41ec727ea7Spatrick .total {{ font-weight: bold; }} 42*12c85518Srobert .none {{ background-color: #FFFF99; height: 20px; display: inline-block; width: 120px; text-align: center; border-radius: 5px; color: #000000; font-family="Verdana,Geneva,DejaVu Sans,sans-serif" }} 43*12c85518Srobert .part {{ background-color: #FFCC99; height: 20px; display: inline-block; width: 120px; text-align: center; border-radius: 5px; color: #000000; font-family="Verdana,Geneva,DejaVu Sans,sans-serif" }} 44*12c85518Srobert .good {{ background-color: #2CCCFF; height: 20px; display: inline-block; width: 120px; text-align: center; border-radius: 5px; color: #000000; font-family="Verdana,Geneva,DejaVu Sans,sans-serif" }} 45ec727ea7Spatrick </style> 46ec727ea7Spatrick 47ec727ea7Spatrick.. role:: none 48ec727ea7Spatrick.. role:: part 49ec727ea7Spatrick.. role:: good 50ec727ea7Spatrick.. role:: total 51ec727ea7Spatrick 52ec727ea7Spatrick====================== 53ec727ea7SpatrickClang Formatted Status 54ec727ea7Spatrick====================== 55ec727ea7Spatrick 56ec727ea7Spatrick:doc:`ClangFormattedStatus` describes the state of LLVM source 57ec727ea7Spatricktree in terms of conformance to :doc:`ClangFormat` as of: {today} (`{sha} <https://github.com/llvm/llvm-project/commit/{sha}>`_). 58ec727ea7Spatrick 59ec727ea7Spatrick 60ec727ea7Spatrick.. list-table:: LLVM Clang-Format Status 61ec727ea7Spatrick :widths: 50 25 25 25 25 62ec727ea7Spatrick :header-rows: 1\n 63ec727ea7Spatrick * - Directory 64ec727ea7Spatrick - Total Files 65ec727ea7Spatrick - Formatted Files 66ec727ea7Spatrick - Unformatted Files 67ec727ea7Spatrick - % Complete 68ec727ea7Spatrick""" 69ec727ea7Spatrick 70*12c85518SrobertTABLE_ROW = """\ 71ec727ea7Spatrick * - {path} 72ec727ea7Spatrick - {style}`{count}` 73ec727ea7Spatrick - {style}`{passes}` 74ec727ea7Spatrick - {style}`{fails}` 75ec727ea7Spatrick - {style2}`{percent}%` 76ec727ea7Spatrick""" 77ec727ea7Spatrick 78ec727ea7SpatrickFNULL = open(os.devnull, 'w') 79ec727ea7Spatrick 80*12c85518Srobert 81ec727ea7Spatrickwith open(DOC_FILE, 'wb') as output: 82*12c85518Srobert cleanfiles = open(CLEAN_FILE, "wb") 83ec727ea7Spatrick sha = get_git_revision_short_hash() 84ec727ea7Spatrick today = datetime.now().strftime("%B %d, %Y %H:%M:%S") 85*12c85518Srobert output.write(bytes(RST_PREFIX.format(today=today, 86ec727ea7Spatrick sha=sha).encode("utf-8"))) 87ec727ea7Spatrick 88ec727ea7Spatrick total_files_count = 0 89ec727ea7Spatrick total_files_pass = 0 90ec727ea7Spatrick total_files_fail = 0 91ec727ea7Spatrick for root, subdirs, files in os.walk(rootdir): 92ec727ea7Spatrick for subdir in subdirs: 93ec727ea7Spatrick if any(sd == subdir for sd in skipped_dirs): 94ec727ea7Spatrick subdirs.remove(subdir) 95ec727ea7Spatrick else: 96ec727ea7Spatrick act_sub_dir = os.path.join(root, subdir) 97ec727ea7Spatrick # Check the git index to see if the directory contains tracked 98ec727ea7Spatrick # files. Reditect the output to a null descriptor as we aren't 99ec727ea7Spatrick # interested in it, just the return code. 100ec727ea7Spatrick git_check = subprocess.Popen( 101ec727ea7Spatrick ["git", "ls-files", "--error-unmatch", act_sub_dir], 102ec727ea7Spatrick stdout=FNULL, 103ec727ea7Spatrick stderr=FNULL) 104ec727ea7Spatrick if git_check.wait() != 0: 105ec727ea7Spatrick print("Skipping directory: ", act_sub_dir) 106ec727ea7Spatrick subdirs.remove(subdir) 107ec727ea7Spatrick 108ec727ea7Spatrick path = os.path.relpath(root, TOP_DIR) 109ec727ea7Spatrick path = path.replace('\\', '/') 110ec727ea7Spatrick 111ec727ea7Spatrick file_count = 0 112ec727ea7Spatrick file_pass = 0 113ec727ea7Spatrick file_fail = 0 114ec727ea7Spatrick for filename in files: 115ec727ea7Spatrick file_path = os.path.join(root, filename) 116ec727ea7Spatrick ext = os.path.splitext(file_path)[-1].lower() 117ec727ea7Spatrick if not ext.endswith(suffixes): 118ec727ea7Spatrick continue 119ec727ea7Spatrick 120ec727ea7Spatrick file_count += 1 121ec727ea7Spatrick 122ec727ea7Spatrick args = ["clang-format", "-n", file_path] 123ec727ea7Spatrick cmd = subprocess.Popen(args, stderr=subprocess.PIPE) 124ec727ea7Spatrick stdout, err = cmd.communicate() 125ec727ea7Spatrick 126ec727ea7Spatrick relpath = os.path.relpath(file_path, TOP_DIR) 127ec727ea7Spatrick relpath = relpath.replace('\\', '/') 128ec727ea7Spatrick if err.decode(sys.stdout.encoding).find(': warning:') > 0: 129ec727ea7Spatrick print(relpath, ":", "FAIL") 130ec727ea7Spatrick file_fail += 1 131ec727ea7Spatrick else: 132ec727ea7Spatrick print(relpath, ":", "PASS") 133ec727ea7Spatrick file_pass += 1 134*12c85518Srobert cleanfiles.write(bytes(relpath + "\n")) 135*12c85518Srobert cleanfiles.flush() 136ec727ea7Spatrick 137ec727ea7Spatrick total_files_count += file_count 138ec727ea7Spatrick total_files_pass += file_pass 139ec727ea7Spatrick total_files_fail += file_fail 140ec727ea7Spatrick 141ec727ea7Spatrick if file_count > 0: 142ec727ea7Spatrick percent = (int(100.0 * (float(file_pass)/float(file_count)))) 143ec727ea7Spatrick style = get_style(file_count, file_pass) 144*12c85518Srobert output.write(bytes(TABLE_ROW.format(path=path, 145ec727ea7Spatrick count=file_count, 146ec727ea7Spatrick passes=file_pass, 147ec727ea7Spatrick fails=file_fail, 148ec727ea7Spatrick percent=str(percent), style="", 149ec727ea7Spatrick style2=style).encode("utf-8"))) 150ec727ea7Spatrick output.flush() 151ec727ea7Spatrick 152ec727ea7Spatrick print("----\n") 153ec727ea7Spatrick print(path, file_count, file_pass, file_fail, percent) 154ec727ea7Spatrick print("----\n") 155ec727ea7Spatrick 156ec727ea7Spatrick total_percent = (float(total_files_pass)/float(total_files_count)) 157ec727ea7Spatrick percent_str = str(int(100.0 * total_percent)) 158*12c85518Srobert output.write(bytes(TABLE_ROW.format(path="Total", 159ec727ea7Spatrick count=total_files_count, 160ec727ea7Spatrick passes=total_files_pass, 161ec727ea7Spatrick fails=total_files_fail, 162ec727ea7Spatrick percent=percent_str, style=":total:", 163ec727ea7Spatrick style2=":total:").encode("utf-8"))) 164