1#!/usr/bin/env python3 2# Script to check that headers are in a consistent order 3# Canonical header order is defined in scripts/HeaderOrder.txt 4# Requires Python 3.6 or later 5 6import pathlib 7 8patterns = [ 9 "include/*.h", 10 "src/*.cxx", 11 "lexlib/*.cxx", 12 "lexers/*.cxx", 13 "win32/*.cxx", 14 "gtk/*.cxx", 15 "cocoa/*.mm", 16 "cocoa/*.h", 17 "test/unit/*.cxx", 18 "lexilla/src/*.cxx", 19 "lexilla/test/*.cxx", 20] 21 22def IsHeader(x): 23 return x.strip().startswith("#") and \ 24 ("include" in x or "import" in x) and \ 25 "dllimport" not in x 26 27def HeaderFromIncludeLine(s): 28 #\s*#\s*(include|import)\s+\S+\s* 29 return s.strip()[1:].strip()[7:].strip() 30 31def ExtractHeaders(filename): 32 with filename.open(encoding="cp437") as infile: 33 return [HeaderFromIncludeLine(l) for l in infile if IsHeader(l)] 34 35def ExcludeName(name): 36 # LexCaml adds system headers in #if to be an external lexer 37 # moc_ files are generated by Qt and follow its rules 38 return "LexCaml" in name or "moc_" in name 39 40def SortLike(incs, order): 41 return sorted(incs, key = lambda i: order.index(i)) 42 43def CheckFiles(root): 44 # Find all the lexer source code files 45 filePaths = [] 46 for p in patterns: 47 filePaths += root.glob(p) 48 # The Qt platform code interleaves system and Scintilla headers 49 #~ filePaths += root.glob("qt/ScintillaEditBase/*.cpp") 50 #~ filePaths += root.glob("qt/ScintillaEdit/*.cpp") 51 #~ print(filePaths) 52 scriptDirectory = root / "scripts" 53 headerOrderFile = scriptDirectory / "HeaderOrder.txt" 54 headerOrder = ExtractHeaders(headerOrderFile) 55 originalOrder = headerOrder[:] 56 orderedPaths = [p for p in sorted(filePaths) if not ExcludeName(str(p))] 57 allIncs = set() 58 for f in orderedPaths: 59 print(" File ", f.relative_to(root)) 60 incs = ExtractHeaders(f) 61 #~ print("\n".join(incs)) 62 news = set(incs) - set(headerOrder) 63 allIncs = allIncs.union(set(incs)) 64 65 m = 0 66 i = 0 67 # Detect headers not in header order list and insert at OK position 68 needs = [] 69 while i < len(incs): 70 if m == len(headerOrder): 71 #~ print("**** extend", incs[i:]) 72 headerOrder.extend(incs[i:]) 73 needs.extend(incs[i:]) 74 break 75 if headerOrder[m] == incs[i]: 76 #~ print("equal", headerOrder[m]) 77 i += 1 78 m += 1 79 else: 80 if headerOrder[m] not in incs: 81 #~ print("skip", headerOrder[m]) 82 m += 1 83 elif incs[i] not in headerOrder: 84 #~ print(str(f) + ":1: Add master", incs[i]) 85 headerOrder.insert(m, incs[i]) 86 needs.append(incs[i]) 87 i += 1 88 m += 1 89 else: 90 i += 1 91 if needs: 92 print(f"{f}:1: needs these headers:") 93 for header in needs: 94 print("#include " + header) 95 96 # Detect out of order 97 ordered = SortLike(incs, headerOrder) 98 if incs != ordered: 99 print(f"{f}:1: is out of order") 100 fOrdered = pathlib.Path(str(f) + ".ordered") 101 with fOrdered.open("w") as headerOut: 102 for header in ordered: 103 headerOut.write("#include " + header + "\n") 104 print(f"{fOrdered}:1: is ordered") 105 106 if headerOrder != originalOrder: 107 newIncludes = set(headerOrder) - set(originalOrder) 108 headerOrderNew = scriptDirectory / "NewOrder.txt" 109 print(f"{headerOrderFile}:1: changed to {headerOrderNew}") 110 print(f" Added {', '.join(newIncludes)}.") 111 with headerOrderNew.open("w") as headerOut: 112 for header in headerOrder: 113 headerOut.write("#include " + header + "\n") 114 115 unused = sorted(set(headerOrder) - allIncs) 116 if unused: 117 print("In HeaderOrder.txt but not used") 118 print("\n".join(unused)) 119 120CheckFiles(pathlib.Path(__file__).resolve().parent.parent) 121