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