Set the exe bit and point to the right interpreter on Unix.
[mirror_edk2.git] / Tools / Python / calcdeps.py
1 #!/usr/bin/env python
2
3 """Calculate the dependencies a given module has by looking through the source
4 code to see what guids and functions are referenced to see which Packages and
5 Library Classes need to be referenced. """
6
7 import os, sys, re, getopt, string, glob, xml.dom.minidom, pprint
8
9 # Map each function name back to the lib class that declares it.
10 function_table = {}
11
12 # Map each guid name to a package name.
13 cname_table = {}
14
15 def XmlList(Dom, String):
16 """Get a list of XML Elements using XPath style syntax."""
17 if Dom.nodeType==Dom.DOCUMENT_NODE:
18 return XmlList(Dom.documentElement, String)
19 if String[0] == "/":
20 return XmlList(Dom, String[1:])
21 if String == "" :
22 return []
23 TagList = String.split('/')
24 nodes = []
25 if Dom.nodeType == Dom.ELEMENT_NODE and Dom.tagName.strip() == TagList[0]:
26 if len(TagList) == 1:
27 nodes = [Dom]
28 else:
29 restOfPath = "/".join(TagList[1:])
30 for child in Dom.childNodes:
31 nodes = nodes + XmlList(child, restOfPath)
32 return nodes
33
34 def XmlElement (Dom, String):
35 """Return a single element that matches the String which is XPath style syntax."""
36 try:
37 return XmlList (Dom, String)[0].firstChild.data.strip(' ')
38 except:
39 return ''
40
41 def XmlElementData (Dom):
42 """Get the text for this element."""
43 return Dom.firstChild.data.strip(' ')
44
45 def XmlAttribute (Dom, String):
46 """Return a single attribute that named by String."""
47 try:
48 return Dom.getAttribute(String)
49 except:
50 return ''
51
52 def inWorkspace(rel_path):
53 """Treat the given path as relative to the workspace."""
54
55 # Make sure the user has set the workspace variable:
56 try:
57 return os.path.join(os.environ["WORKSPACE"], rel_path )
58 except:
59 print "Oops! You must set the WORKSPACE environment variable to run this script."
60 sys.exit()
61
62 def getIdentifiers(infiles):
63
64 """Build a set of all the identifiers in this file."""
65
66 # Start with an empty set.
67 ids = set()
68
69 for infile in infiles:
70
71 # Open the file
72 f = open(infile)
73
74 # Create some lexical categories that we will use to filter out
75 strings=re.compile('L?"[^"]*"')
76 chars=re.compile("'[^']*'")
77 hex=re.compile("0[Xx][0-9a-fA-F]*")
78 keywords = re.compile('for|do|while|if|else|break|int|unsigned|switch|volatile|goto|case|char|long|struct|return|extern')
79 common = re.compile('VOID|UINTN|UINT32|UINT8|UINT64')
80
81 # Compile a Regular expression to grab all the identifers from the input.
82 identifier = re.compile('[_a-zA-Z][0-9_a-zA-Z]{3,}')
83
84 for line in f.readlines():
85
86 # Filter some lexical categories out.
87 # for filter in [strings, chars, hex, keywords, common]:
88 for filter in [strings, chars, hex]:
89 line = re.sub(filter, '', line)
90
91 # Add all the identifiers that we found on this line.
92 ids = ids.union(set(identifier.findall(line)))
93
94 # Close the file
95 f.close()
96
97 # Return the set of identifiers.
98 return ids
99
100
101 def search_classes(ids):
102
103 """ Search the set of classes for functions."""
104
105 # Start with an empty set.
106 classes = set()
107
108 for id in ids:
109 try:
110 # If it is not a "hit" in the table add it to the set.
111 classes.add(function_table[id])
112 except:
113 # If it is not a "hit" in the table, ignore it.
114 pass
115
116 return classes
117
118 def search_cnames(ids):
119
120 """Search all the Packages to see if this code uses a Guid from one of them.
121 Return a set of matching packages."""
122
123 packages = set()
124
125 for id in ids:
126 try:
127 # If it is not a "hit" in the table add it to the set.
128 packages.add(cname_table[id])
129 except:
130 # If it is not a "hit" in the table, ignore it.
131 pass
132
133 return packages
134
135 def getSpds():
136
137 """Open the database and get all the spd files out."""
138
139 # Open the database
140 database = xml.dom.minidom.parse(inWorkspace("Tools/Conf/FrameworkDatabase.db"))
141
142 # Get a list of all the packages
143 for filename in XmlList(database, "/FrameworkDatabase/PackageList/Filename"):
144 spdFile = XmlElementData(filename)
145
146 # Now open the spd file and build the database of guids.
147 getCNames(inWorkspace(spdFile))
148 getLibClasses(inWorkspace(spdFile))
149
150 def getCNames(spdFile):
151
152 """Extract all the C_Names from an spd file."""
153
154 # Begin to parse the XML of the .spd
155 spd = xml.dom.minidom.parse(spdFile)
156
157 # Get the name of the package
158 packageName = XmlElement(spd, "PackageSurfaceArea/SpdHeader/PackageName")
159
160 # Find the C_Name
161 for cname in XmlList(spd, "/PackageSurfaceArea/GuidDeclarations/Entry/C_Name") + \
162 XmlList(spd, "/PackageSurfaceArea/PcdDeclarations/PcdEntry/C_Name") + \
163 XmlList(spd, "/PackageSurfaceArea/PpiDeclarations/Entry/C_Name") + \
164 XmlList(spd, "/PackageSurfaceArea/ProtocolDeclarations/Entry/C_Name"):
165
166 # Get the text of the <C_Name> tag.
167 cname_text = XmlElementData(cname)
168
169 # Map the <C_Name> to the <PackageName>. We will use this to lookup every
170 # identifier in the Input Code.
171 cname_table[cname_text] = packageName
172
173 return
174
175 def getLibClasses(spdFile):
176
177 """Extract all the Lib Classes from an spd file."""
178
179 # Begin to parse the XML of the .spd
180 spd = xml.dom.minidom.parse(spdFile)
181
182 # Get the guid of the package
183 packageGuid = XmlElement(spd, "/PackageSurfaceArea/SpdHeader/GuidValue")
184
185 for libClass in XmlList(spd, "/PackageSurfaceArea/LibraryClassDeclarations/LibraryClass"):
186 className = XmlAttribute(libClass, "Name")
187 headerfile = XmlElementData(libClass.getElementsByTagName("IncludeHeader")[0])
188
189 packageRoot=os.path.dirname(spdFile)
190
191 headerfile = os.path.join(packageRoot, headerfile)
192
193 f = open(headerfile)
194
195 # This pattern can pick out function names if the EFI coding
196 # standard is followed. We could also use dumpbin on library
197 # instances to get a list of symbols.
198 functionPattern = re.compile("([_a-zA-Z][_a-zA-Z0-9]*) *\( *");
199
200 for line in f.readlines():
201 m = functionPattern.match(line)
202 if m:
203 functionName = m.group(1)
204 # Map it!
205 function_table[functionName] = (className, packageGuid)
206
207 f.close()
208
209 def guid(strVal):
210 """Make a guid number out of a guid hex string."""
211 return long(strVal.replace('-',''), 16)
212
213 # This acts like the main() function for the script, unless it is 'import'ed into another
214 # script.
215 if __name__ == '__main__':
216
217 # Create a pretty printer for dumping data structures in a readable form.
218 pp = pprint.PrettyPrinter(indent=2)
219
220 # Process the command line args.
221 optlist, args = getopt.getopt(sys.argv[1:], 'h', [ 'example-long-arg=', 'testing'])
222
223 """You should pass a file name as a paramter. It should be preprocessed text
224 of all the .c and .h files in your module, which is cat'ed together into one
225 large file."""
226
227 # Scrape out all the things that look like identifiers.
228 ids = getIdentifiers(args)
229
230 # Read in the spds from the workspace to find the Guids.
231 getSpds()
232
233 # Debug stuff.
234 print pp.pprint(function_table)
235 print pp.pprint(cname_table)
236 print "Classes = ", pp.pprint(list(search_classes(ids)))
237 print "C_Names = ", pp.pprint(list(search_cnames(ids)))