-
Notifications
You must be signed in to change notification settings - Fork 0
/
GenIncludeMap2.py
executable file
·243 lines (218 loc) · 9.61 KB
/
GenIncludeMap2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import sys
import os.path
import re
import subprocess
import glob
from graphviz import Digraph
def ErrorHandling(everything, errNo):
if(errNo == 1):
print("The source file [{0}] is not part of the build.".format(everything["srcFileFullPath"]))
elif (errNo == 2):
print("\"build.ninja\" file cannot be found at [{0}].".format(everything["bldDir"]))
print("Did you specify a wrong build folder?")
elif (errNo == 3):
print("Failed to render the graph.")
print("Is the file [{0}] writable?".format(everything["pdfFileFullPath"]))
sys.exit(0)
return
def GetNinjaBuildFile(everything):
ninjaBldFileFullPath = os.path.join(everything["bldDir"], "build.ninja")
if(not os.path.exists(ninjaBldFileFullPath)):
ErrorHandling(everything, 2)
everything["ninjaBldFile"] = ninjaBldFileFullPath
print("Ninja build file found:\n[%s]\n" % everything["ninjaBldFile"])
return
def GetNinjaBuildBlock4SourceFile(everything):
srcFileFullPath = everything["srcFileFullPath"]
srcDir = everything["srcDir"]
srcFileRelativePath = os.path.relpath(srcFileFullPath, srcDir).replace(os.path.sep, "/")
target = r"build\s.*{0}".format(srcFileRelativePath)
nextTarget = r"build\s[^:]*:"
buildBlockLines = []
with open(everything["ninjaBldFile"], "r") as f:
lines = f.readlines()
collect = False
for line in lines:
if(re.match(target, line)):
collect = True
continue
if(collect and re.match(nextTarget, line)):
break
if(collect):
buildBlockLines.append(line)
if(len(buildBlockLines) == 0):
ErrorHandling(everything, 1)
everything["buildBlockLines"] = buildBlockLines
return
def LoadIncludeSearchPaths(everything):
includeSearchPaths = []
for line in everything["buildBlockLines"]:
if("INCLUDES" in line):
break
m = re.findall(r"(-I[^\s]+)", line) #(-I[^\s]+)|(-isystem\s[^\s]+)
includeSearchPaths.extend(x[2:] if not x[-1] == "." else x[2:-1] for x in m)
rawIncludeSearchPaths = [x if os.path.isabs(x) else os.path.join(everything["bldDir"], x) for x in includeSearchPaths]
includeSearchPaths = " ".join(["-I{0}".format(os.path.normpath(x)) for x in rawIncludeSearchPaths])
rawSystemSearchPaths = re.findall(r"(-isystem\s[^\s]+)", line)
systemSearchPaths = " ".join(rawSystemSearchPaths)
everything["includeSearchPaths"] = "{0} {1}".format(includeSearchPaths, systemSearchPaths)
return
def LoadConfigMacros(everything):
for line in everything["buildBlockLines"]:
if("DEFINES" in line):
break
m = re.match(r".*=\s+(.*)", line)
configMacros = m.group(1) #load DEFINES from the build.ninja
everything["configMacros"] = configMacros
return
def LoadBuildFlags(everything):
bldFlags = list()
for line in everything["buildBlockLines"]:
if("FLAGS" in line):
break
m = re.match(r".*=\s+(.*)", line)
bldFlags = m.group(1) #load FLAGS from the build.ninja
everything["bldFlags"] = bldFlags
return
def PreProcessSrcFile(everything):
GetNinjaBuildFile(everything)
GetNinjaBuildBlock4SourceFile(everything)
LoadIncludeSearchPaths(everything)
LoadConfigMacros(everything)
LoadBuildFlags(everything)
cmdString = r"{0} -E {1} {2} {3} {4}".format(everything["gccFullPath"], everything["configMacros"], everything["includeSearchPaths"], everything["bldFlags"], everything["srcFileFullPath"])
srcFile = os.path.basename(everything["srcFileFullPath"])
ppSrcFile = "pp." + srcFile
ppSrcFileFullpath = os.path.join(".", ppSrcFile)
everything["ppFileFullPath"] = ppSrcFileFullpath
with open(ppSrcFileFullpath, "w") as f:
subprocess.run(cmdString, stdout=f, shell=True)
return
def GenerateGraphMatrix(everything):
lineStack = []
lineStack.append(os.path.normpath(os.path.realpath(everything["srcFileFullPath"])))
gm = everything["graphMatrix"]
with open(everything["ppFileFullPath"], "r") as f:
fileContent = f.read()
#https://gcc.gnu.org/onlinedocs/gcc-3.4.6/cpp/Preprocessor-Output.html
lineMarkerRegex = r"#\s+\d+\s+\"(.*)\"\s+([12])"
lineMarkers = re.findall(lineMarkerRegex, fileContent)
for rawLineMarker in lineMarkers:
filePath = os.path.normpath(os.path.realpath(rawLineMarker[0]))
fileFlag = rawLineMarker[1]
if(fileFlag == '1'):
fromFile = lineStack[-1]
lineStack.append(filePath)
if(not fromFile in gm.keys()):
gm[fromFile] = []
toFile = filePath
if(toFile not in gm[fromFile]):
gm[fromFile].append(toFile)
elif (fileFlag == '2'):
lineStack.pop(-1)
return
def IsGeneratedFile(everything, filePath):
return os.path.relpath(filePath, everything["bldDir"]) in filePath
def IsSrcFile(everything, filePath):
return os.path.relpath(filePath, everything["srcDir"]) in filePath
def IsTheStartingNode(everything, filePath):
return everything["srcFileFullPath"].lower() in filePath.lower()
def IsToolChainFile(everything, filePath):
isSrcFile = IsSrcFile(everything, filePath)
isBldFile = IsGeneratedFile(everything, filePath)
return not (isSrcFile or isBldFile)
def DetermineNodeLooks(everything, node):
shape = "oval"
style = "filled"
fontName = ""
if(IsGeneratedFile(everything, node)):
nodeText = os.path.relpath(node, everything["bldDir"]).replace(os.path.sep, "\n")
nodeColor = "lightblue"
elif(IsTheStartingNode(everything, node)):
nodeText = os.path.relpath(node, everything["srcDir"]).replace(os.path.sep, "\n")
shape = "box"
nodeColor = "green"
fontName = "bold"
elif(IsToolChainFile(everything, node)):
shape = "diamond"
nodeText = r"\<{0}\>".format(os.path.basename(node)) # use "<xxx>" for toolchain headers
nodeColor = "lightgrey"
else:
nodeText = os.path.relpath(node, everything["srcDir"]).replace(os.path.sep, "\n")
nodeColor = "black"
style = ""
return tuple([nodeText, nodeColor, shape, style, fontName])
def GenerateGraph(everything):
graph = Digraph(engine="dot", comment="Include Map for {0}".format(everything["srcFileFullPath"]))
gm = everything["graphMatrix"]
drawnNodes = []
for fromNode in gm.keys():
looks1 = DetermineNodeLooks(everything, fromNode)
if(looks1[0] not in drawnNodes):
graph.node(looks1[0], label = looks1[0], color = looks1[1], shape = looks1[2], style = looks1[3], fontname = looks1[4])
drawnNodes.append(looks1[0])
for toNode in gm[fromNode]:
looks2 = DetermineNodeLooks(everything, toNode)
if(looks2[0] not in drawnNodes):
graph.node(looks2[0], label = looks2[0], color = looks2[1], shape = looks2[2], style = looks2[3], fontname = looks2[4])
drawnNodes.append(looks2[0])
graph.edge(looks1[0], looks2[0])
graphFileName = os.path.basename(everything["srcFileFullPath"])
try:
pdfFileFullPath = os.path.realpath("./IncludeMap_{0}.gv.pdf".format(graphFileName))
everything["pdfFileFullPath"] = pdfFileFullPath
graph.render(os.path.realpath("./IncludeMap_{0}.gv".format(graphFileName)), view= False, format="pdf") # graphviz will add the pdf suffix
except:
print(sys.exc_info()[0])
ErrorHandling(everything, 3)
pass
def DoWork(everything):
print("Start generating include map for:\n[%s]\n" % everything["srcFileFullPath"])
PreProcessSrcFile(everything)
GenerateGraphMatrix(everything)
GenerateGraph(everything)
return
def Usage():
print("\n")
print("IncludeMap ver 0.1")
print("By Shao, Ming (smwikipedia@163.com)")
print("[Description]:")
print(" This tool generates a map of included headers for a Zephyr .c file in the context of a Zephyr build.")
print("[Pre-condition]:")
print(" A Zephyr build must be made before using this tool because some build-generated files are needed.")
print("[Usage]:")
print(" GenIncludeMap <srcDir> <bldDir> <gccFullPath> <srcFileFullPath>")
print(" <srcDir>: the Zephyr folder path.")
print(" <bldDir>: the Zephyr build folder where build.ninja file is located.")
print(" <gccFullPath>: the full path of the GCC used to build Zephyr.")
print(" <srcFileFullPath>: the full path of the Zephyr source file to generate include map for.")
return
def CleanseArgs(everything):
return
def OutputIncludeSearchPaths(everything):
print("The include search paths:")
for include in everything["includeSearchPaths"].split(" "):
print(os.path.normpath(include))
return
def CleanUp(everything):
ppFileList = glob.glob("./pp.*")
for ppFile in ppFileList:
os.remove(ppFile)
return
if __name__=="__main__":
everything = dict()
if(len(sys.argv)!= 5):
Usage()
else:
print("Zephyr Include Map Generator ver 0.1")
print("By Shao, Ming (smwikipedia@163.com)")
everything["srcDir"] = os.path.abspath(os.path.normpath(sys.argv[1]))
everything["bldDir"] = os.path.abspath(os.path.normpath(sys.argv[2]))
everything["gccFullPath"] = os.path.abspath(os.path.normpath(sys.argv[3]))
everything["srcFileFullPath"] = os.path.abspath(os.path.normpath(sys.argv[4]))
everything["graphMatrix"] = dict() # <nodeA, [nodeX, nodeY, nodeZ, ...]>, A connects "to" X, Y, Z, ...
CleanseArgs(everything)
DoWork(everything)
OutputIncludeSearchPaths(everything)
CleanUp(everything)
sys.exit(0)