summaryrefslogtreecommitdiff
path: root/Tools/Python/Calc-Deps.py
blob: b022a907ae118f2adc6d159ff4b5ecf9a6dbf609 (plain)
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
#!/usr/bin/env python

# Copyright (c) 2007, Intel Corporation
# All rights reserved. This program and the accompanying materials
# are licensed and made available under the terms and conditions of the BSD License
# which accompanies this distribution.  The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php
# 
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

"""Calculate the dependencies a given module has by looking through the source
code to see what guids and functions are referenced to see which Packages and
Library Classes need to be referenced. """

import os, sys, re, getopt, string, glob, xml.dom.minidom, pprint
from XmlRoutines import *

# Map each function name back to the lib class that declares it.
function_table = {}

# Map each guid name to a package name.
cname_table = {}

def inWorkspace(rel_path):
  """Treat the given path as relative to the workspace."""

  # Make sure the user has set the workspace variable:
  try:
    return os.path.join(os.environ["WORKSPACE"], rel_path )
  except:
    print "Oops! You must set the WORKSPACE environment variable to run this script."
    sys.exit()

def getIdentifiers(infiles):

  """Build a set of all the identifiers in this file."""

  # Start with an empty set.
  ids = set()

  for infile in infiles:

    # Open the file
    f = open(infile)

    # Create some lexical categories that we will use to filter out
    strings=re.compile('L?"[^"]*"')
    chars=re.compile("'[^']*'")
    hex=re.compile("0[Xx][0-9a-fA-F]*")
    keywords = re.compile('for|do|while|if|else|break|int|unsigned|switch|volatile|goto|case|char|long|struct|return|extern')
    common = re.compile('VOID|UINTN|UINT32|UINT8|UINT64')

    # Compile a Regular expression to grab all the identifers from the input.
    identifier = re.compile('[_a-zA-Z][0-9_a-zA-Z]{3,}')

    for line in f.readlines():

      # Filter some lexical categories out.
      # for filter in [strings, chars, hex, keywords, common]:
      for filter in [strings, chars, hex]:
        line = re.sub(filter, '', line)
      
      # Add all the identifiers that we found on this line.
      ids = ids.union(set(identifier.findall(line)))

    # Close the file
    f.close()    

  # Return the set of identifiers.
  return ids


def search_classes(ids):

  """ Search the set of classes for functions."""

  # Start with an empty set.
  classes = set()

  for id in ids:
    try:
      # If it is not a "hit" in the table add it to the set.
      classes.add(function_table[id])
    except:
      # If it is not a "hit" in the table, ignore it.
      pass

  return classes

def search_cnames(ids):

  """Search all the Packages to see if this code uses a Guid from one of them.
  Return a set of matching packages."""

  packages = set()

  for id in ids:
    try:
      # If it is not a "hit" in the table add it to the set.
      packages.add(cname_table[id])
    except:
      # If it is not a "hit" in the table, ignore it.
      pass

  return packages

def getSpds():

  """Open the database and get all the spd files out."""

  # Open the database
  database = xml.dom.minidom.parse(inWorkspace("Tools/Conf/FrameworkDatabase.db"))

  # Get a list of all the packages
  for filename in XmlList(database, "/FrameworkDatabase/PackageList/Filename"):
    spdFile = XmlElementData(filename)

    # Now open the spd file and build the database of guids.
    getCNames(inWorkspace(spdFile))
    getLibClasses(inWorkspace(spdFile))

def getCNames(spdFile):

  """Extract all the C_Names from an spd file."""

  # Begin to parse the XML of the .spd
  spd = xml.dom.minidom.parse(spdFile)

  # Get the name of the package
  packageName = XmlElement(spd, "PackageSurfaceArea/SpdHeader/PackageName")
  packageVersion = XmlElement(spd, "PackageSurfaceArea/SpdHeader/Version")
  packageGuid = XmlElement(spd, "PackageSurfaceArea/SpdHeader/GuidValue")

  # Find the C_Name
  for cname in XmlList(spd, "/PackageSurfaceArea/GuidDeclarations/Entry/C_Name") + \
               XmlList(spd, "/PackageSurfaceArea/PcdDeclarations/PcdEntry/C_Name") + \
               XmlList(spd, "/PackageSurfaceArea/PpiDeclarations/Entry/C_Name") + \
               XmlList(spd, "/PackageSurfaceArea/ProtocolDeclarations/Entry/C_Name"):

    # Get the text of the <C_Name> tag.
    cname_text = XmlElementData(cname)

    # Map the <C_Name> to the <PackageName>. We will use this to lookup every 
    # identifier in the Input Code.
    cname_table[cname_text] = {"name": packageName, "version": packageVersion, "guid": packageGuid}


  return

def getLibClasses(spdFile):

  """Extract all the Lib Classes from an spd file."""

  # Begin to parse the XML of the .spd
  spd = xml.dom.minidom.parse(spdFile)

  # Get the guid of the package
  packageGuid = XmlElement(spd, "/PackageSurfaceArea/SpdHeader/GuidValue")

  for libClass in XmlList(spd, "/PackageSurfaceArea/LibraryClassDeclarations/LibraryClass"):
    className = XmlAttribute(libClass, "Name")
    headerfile = XmlElementData(libClass.getElementsByTagName("IncludeHeader")[0])

    packageRoot=os.path.dirname(spdFile)

    headerfile = os.path.join(packageRoot, headerfile)

    f = open(headerfile)

    # This pattern can pick out function names if the EFI coding
    # standard is followed. We could also use dumpbin on library
    # instances to get a list of symbols.
    functionPattern = re.compile("([_a-zA-Z][_a-zA-Z0-9]*) *\( *");

    for line in f.readlines():
      m = functionPattern.match(line)
      if m:
        functionName = m.group(1)
        # Map it!
        function_table[functionName] = (className, packageGuid)

    f.close()

def guid(strVal):
  """Make a guid number out of a guid hex string."""
  return long(strVal.replace('-',''), 16)

# This acts like the main() function for the script, unless it is 'import'ed into another
# script.
if __name__ == '__main__':

  # Create a pretty printer for dumping data structures in a readable form.
  pp = pprint.PrettyPrinter(indent=2)

  # Process the command line args.
  optlist, args = getopt.getopt(sys.argv[1:], 'h', [ 'example-long-arg=', 'testing'])

  """You should pass a file name as a paramter. It should be preprocessed text
of all the .c and .h files in your module, which is cat'ed together into one
large file."""

  # Scrape out all the things that look like identifiers.
  ids = getIdentifiers(args)

  # Read in the spds from the workspace to find the Guids.
  getSpds()

  # Debug stuff.
  print "Function Table = "
  pp.pprint(function_table)
  print "CName Table = "
  pp.pprint(cname_table)
  print "Classes = "
  pp.pprint(list(search_classes(ids)))
  print "C_Names = "
  pp.pprint(list(search_cnames(ids)))