1## @file
2#
3# This file produce action class to generate doxygen document for edk2 codebase.
4# The action classes are shared by GUI and command line tools.
5#
6# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
7#
8# SPDX-License-Identifier: BSD-2-Clause-Patent
9
10"""This file produce action class to generate doxygen document for edk2 codebase.
11   The action classes are shared by GUI and command line tools.
12"""
13from plugins.EdkPlugins.basemodel import doxygen
14import os
15try:
16    import wx
17    gInGui = True
18except:
19    gInGui = False
20import re
21from plugins.EdkPlugins.edk2.model import inf
22from plugins.EdkPlugins.edk2.model import dec
23from plugins.EdkPlugins.basemodel.message import *
24
25_ignore_dir = ['.svn', '_svn', 'cvs']
26_inf_key_description_mapping_table = {
27  'INF_VERSION':'Version of INF file specification',
28  #'BASE_NAME':'Module Name',
29  'FILE_GUID':'Module Guid',
30  'MODULE_TYPE': 'Module Type',
31  'VERSION_STRING': 'Module Version',
32  'LIBRARY_CLASS': 'Produced Library Class',
33  'EFI_SPECIFICATION_VERSION': 'UEFI Specification Version',
34  'PI_SPECIFICATION_VERSION': 'PI Specification Version',
35  'ENTRY_POINT': 'Module Entry Point Function',
36  'CONSTRUCTOR': 'Library Constructor Function'
37}
38
39_dec_key_description_mapping_table = {
40  'DEC_SPECIFICATION': 'Version of DEC file specification',
41  'PACKAGE_GUID': 'Package Guid'
42}
43class DoxygenAction:
44    """This is base class for all doxygen action.
45    """
46
47    def __init__(self, doxPath, chmPath, outputPath, projname, mode='html', log=None, verbose=False):
48        """Constructor function.
49        @param  doxPath         the obosolution path of doxygen execute file.
50        @param  outputPath      the obosolution output path.
51        @param  log             log function for output message
52        """
53        self._doxPath       = doxPath
54        self._chmPath       = chmPath
55        self._outputPath    = outputPath
56        self._projname      = projname
57        self._configFile    = None          # doxygen config file is used by doxygen exe file
58        self._indexPageFile = None          # doxygen page file for index page.
59        self._log           = log
60        self._mode          = mode
61        self._verbose       = verbose
62        self._doxygenCallback = None
63        self._chmCallback     = None
64
65    def Log(self, message, level='info'):
66        if self._log is not None:
67            self._log(message, level)
68
69    def IsVerbose(self):
70        return self._verbose
71
72    def Generate(self):
73        """Generate interface called by outer directly"""
74        self.Log(">>>>>> Start generate doxygen document for %s... Zzz....\n" % self._projname)
75
76        # create doxygen config file at first
77        self._configFile = doxygen.DoxygenConfigFile()
78        self._configFile.SetOutputDir(self._outputPath)
79
80        self._configFile.SetWarningFilePath(os.path.join(self._outputPath, 'warning.txt'))
81        if self._mode.lower() == 'html':
82            self._configFile.SetHtmlMode()
83        else:
84            self._configFile.SetChmMode()
85
86        self.Log("    >>>>>> Initialize doxygen config file...Zzz...\n")
87        self.InitializeConfigFile()
88
89        self.Log("    >>>>>> Generate doxygen index page file...Zzz...\n")
90        indexPagePath = self.GenerateIndexPage()
91        if indexPagePath is None:
92            self.Log("Fail to generate index page!\n", 'error')
93            return False
94        else:
95            self.Log("Success to create doxygen index page file %s \n" % indexPagePath)
96
97        # Add index page doxygen file to file list.
98        self._configFile.AddFile(indexPagePath)
99
100        # save config file to output path
101        configFilePath = os.path.join(self._outputPath, self._projname + '.doxygen_config')
102        self._configFile.Generate(configFilePath)
103        self.Log("    <<<<<< Success Save doxygen config file to %s...\n" % configFilePath)
104
105        # launch doxygen tool to generate document
106        if self._doxygenCallback is not None:
107            self.Log("    >>>>>> Start doxygen process...Zzz...\n")
108            if not self._doxygenCallback(self._doxPath, configFilePath):
109                return False
110        else:
111            self.Log("Fail to create doxygen process!", 'error')
112            return False
113
114        return True
115
116    def InitializeConfigFile(self):
117        """Initialize config setting for doxygen project. It will be invoked after config file
118           object is created. Inherited class should implement it.
119        """
120
121    def GenerateIndexPage(self):
122        """Generate doxygen index page. Inherited class should implement it."""
123        return None
124
125    def RegisterCallbackDoxygenProcess(self, callback):
126        self._doxygenCallback = callback
127
128    def RegisterCallbackCHMProcess(self, callback):
129        self._chmCallback = callback
130
131class PlatformDocumentAction(DoxygenAction):
132    """Generate platform doxygen document, will be implement at future."""
133
134class PackageDocumentAction(DoxygenAction):
135    """Generate package reference document"""
136
137    def __init__(self, doxPath, chmPath, outputPath, pObj, mode='html', log=None, arch=None, tooltag=None,
138                  onlyInclude=False, verbose=False):
139        DoxygenAction.__init__(self, doxPath, chmPath, outputPath, pObj.GetName(), mode, log, verbose)
140        self._pObj   = pObj
141        self._arch   = arch
142        self._tooltag = tooltag
143        self._onlyIncludeDocument = onlyInclude
144
145    def InitializeConfigFile(self):
146        if self._arch == 'IA32':
147            self._configFile.AddPreDefined('MDE_CPU_IA32')
148        elif self._arch == 'X64':
149            self._configFile.AddPreDefined('MDE_CPU_X64')
150        elif self._arch == 'IPF':
151            self._configFile.AddPreDefined('MDE_CPU_IPF')
152        elif self._arch == 'EBC':
153            self._configFile.AddPreDefined('MDE_CPU_EBC')
154        else:
155            self._arch = None
156            self._configFile.AddPreDefined('MDE_CPU_IA32')
157            self._configFile.AddPreDefined('MDE_CPU_X64')
158            self._configFile.AddPreDefined('MDE_CPU_IPF')
159            self._configFile.AddPreDefined('MDE_CPU_EBC')
160            self._configFile.AddPreDefined('MDE_CPU_ARM')
161
162        namestr = self._pObj.GetName()
163        if self._arch is not None:
164            namestr += '[%s]' % self._arch
165        if self._tooltag is not None:
166            namestr += '[%s]' % self._tooltag
167        self._configFile.SetProjectName(namestr)
168        self._configFile.SetStripPath(self._pObj.GetWorkspace())
169        self._configFile.SetProjectVersion(self._pObj.GetFileObj().GetVersion())
170        self._configFile.AddPattern('*.decdoxygen')
171
172        if self._tooltag.lower() == 'msft':
173            self._configFile.AddPreDefined('_MSC_EXTENSIONS')
174        elif self._tooltag.lower() == 'gnu':
175            self._configFile.AddPreDefined('__GNUC__')
176        elif self._tooltag.lower() == 'intel':
177            self._configFile.AddPreDefined('__INTEL_COMPILER')
178        else:
179            self._tooltag = None
180            self._configFile.AddPreDefined('_MSC_EXTENSIONS')
181            self._configFile.AddPreDefined('__GNUC__')
182            self._configFile.AddPreDefined('__INTEL_COMPILER')
183
184        self._configFile.AddPreDefined('ASM_PFX= ')
185        self._configFile.AddPreDefined('OPTIONAL= ')
186
187    def GenerateIndexPage(self):
188        """Generate doxygen index page. Inherited class should implement it."""
189        fObj   = self._pObj.GetFileObj()
190        pdObj  = doxygen.DoxygenFile('%s Package Document' % self._pObj.GetName(),
191                                     '%s.decdoxygen' % self._pObj.GetFilename())
192        self._configFile.AddFile(pdObj.GetFilename())
193        pdObj.AddDescription(fObj.GetFileHeader())
194
195        defSection = fObj.GetSectionByName('defines')[0]
196        baseSection = doxygen.Section('PackageBasicInformation', 'Package Basic Information')
197        descr = '<TABLE>'
198        for obj in defSection.GetObjects():
199            if obj.GetKey() in _dec_key_description_mapping_table.keys():
200                descr += '<TR>'
201                descr += '<TD><B>%s</B></TD>' % _dec_key_description_mapping_table[obj.GetKey()]
202                descr += '<TD>%s</TD>' % obj.GetValue()
203                descr += '</TR>'
204        descr += '</TABLE><br>'
205        baseSection.AddDescription(descr)
206        pdObj.AddSection(baseSection)
207
208        knownIssueSection = doxygen.Section('Known_Issue_section', 'Known Issue')
209        knownIssueSection.AddDescription('<ul>')
210        knownIssueSection.AddDescription('<li> OPTIONAL macro for function parameter can not be dealed with doxygen, so it disapear in this document! </li>')
211        knownIssueSection.AddDescription('</ul>')
212        pdObj.AddSection(knownIssueSection)
213
214        self.AddAllIncludeFiles(self._pObj, self._configFile)
215        pages = self.GenerateIncludesSubPage(self._pObj, self._configFile)
216        if len(pages) != 0:
217            pdObj.AddPages(pages)
218        pages = self.GenerateLibraryClassesSubPage(self._pObj, self._configFile)
219        if len(pages) != 0:
220            pdObj.AddPages(pages)
221        pages = self.GeneratePcdSubPages(self._pObj, self._configFile)
222        if len(pages) != 0:
223            pdObj.AddPages(pages)
224        pages = self.GenerateGuidSubPages(self._pObj, self._configFile)
225        if len(pages) != 0:
226            pdObj.AddPages(pages)
227        pages = self.GeneratePpiSubPages(self._pObj, self._configFile)
228        if len(pages) != 0:
229            pdObj.AddPages(pages)
230        pages = self.GenerateProtocolSubPages(self._pObj, self._configFile)
231        if len(pages) != 0:
232            pdObj.AddPages(pages)
233        if not self._onlyIncludeDocument:
234            pdObj.AddPages(self.GenerateModulePages(self._pObj, self._configFile))
235
236        pdObj.Save()
237        return pdObj.GetFilename()
238
239    def GenerateIncludesSubPage(self, pObj, configFile):
240        # by default add following path as include path to config file
241        pkpath = pObj.GetFileObj().GetPackageRootPath()
242        configFile.AddIncludePath(os.path.join(pkpath, 'Include'))
243        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Library'))
244        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Protocol'))
245        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Ppi'))
246        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Guid'))
247        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'IndustryStandard'))
248
249        rootArray = []
250        pageRoot = doxygen.Page("Public Includes", "%s_public_includes" % pObj.GetName())
251        objs = pObj.GetFileObj().GetSectionObjectsByName('includes')
252        if len(objs) == 0: return []
253
254        for obj in objs:
255            # Add path to include path
256            path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetPath())
257            configFile.AddIncludePath(path)
258
259            # only list common folder's include file
260            if obj.GetArch().lower() != 'common':
261                continue
262
263            bNeedAddIncludePage = False
264            topPage = doxygen.Page(self._ConvertPathToDoxygen(path, pObj), 'public_include_top')
265
266            topPage.AddDescription('<ul>\n')
267            for file in os.listdir(path):
268                if file.lower() in _ignore_dir: continue
269                fullpath = os.path.join(path, file)
270                if os.path.isfile(fullpath):
271                    self.ProcessSourceFileForInclude(fullpath, pObj, configFile)
272                    topPage.AddDescription('<li> \link %s\endlink </li>\n' % self._ConvertPathToDoxygen(fullpath, pObj))
273                else:
274                    if file.lower() in ['library', 'protocol', 'guid', 'ppi', 'ia32', 'x64', 'ipf', 'ebc', 'arm', 'pi', 'uefi', 'aarch64']:
275                        continue
276                    bNeedAddSubPage = False
277                    subpage = doxygen.Page(self._ConvertPathToDoxygen(fullpath, pObj), 'public_include_%s' % file)
278                    subpage.AddDescription('<ul>\n')
279                    for subfile in os.listdir(fullpath):
280                        if subfile.lower() in _ignore_dir: continue
281                        bNeedAddSubPage = True
282                        subfullpath = os.path.join(fullpath, subfile)
283                        self.ProcessSourceFileForInclude(subfullpath, pObj, configFile)
284                        subpage.AddDescription('<li> \link %s \endlink </li>\n' % self._ConvertPathToDoxygen(subfullpath, pObj))
285                    subpage.AddDescription('</ul>\n')
286                    if bNeedAddSubPage:
287                        bNeedAddIncludePage = True
288                        pageRoot.AddPage(subpage)
289            topPage.AddDescription('</ul>\n')
290            if bNeedAddIncludePage:
291                pageRoot.AddPage(topPage)
292
293        if pageRoot.GetSubpageCount() != 0:
294            return [pageRoot]
295        else:
296            return []
297
298    def GenerateLibraryClassesSubPage(self, pObj, configFile):
299        """
300        Generate sub page for library class for package.
301        One DEC file maybe contains many library class sections
302        for different architecture.
303
304        @param  fObj DEC file object.
305        """
306        rootArray = []
307        pageRoot = doxygen.Page("Library Class", "%s_libraryclass" % pObj.GetName())
308        objs = pObj.GetFileObj().GetSectionObjectsByName('libraryclass', self._arch)
309        if len(objs) == 0: return []
310
311        if self._arch is not None:
312            for obj in objs:
313                classPage = doxygen.Page(obj.GetClassName(),
314                                         "lc_%s" % obj.GetClassName())
315                comments = obj.GetComment()
316                if len(comments) != 0:
317                    classPage.AddDescription('<br>\n'.join(comments) + '<br>\n')
318                pageRoot.AddPage(classPage)
319                path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
320                path = path[len(pObj.GetWorkspace()) + 1:]
321                if len(comments) == 0:
322                    classPage.AddDescription('\copydoc %s<p>' % obj.GetHeaderFile())
323                section = doxygen.Section('ref', 'Refer to Header File')
324                section.AddDescription('\link %s\n' % obj.GetHeaderFile())
325                section.AddDescription(' \endlink<p>\n')
326                classPage.AddSection(section)
327                fullPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
328                self.ProcessSourceFileForInclude(fullPath, pObj, configFile)
329        else:
330            archPageDict = {}
331            for obj in objs:
332                if obj.GetArch() not in archPageDict.keys():
333                    archPageDict[obj.GetArch()] = doxygen.Page(obj.GetArch(),
334                                                               'lc_%s' % obj.GetArch())
335                    pageRoot.AddPage(archPageDict[obj.GetArch()])
336                subArchRoot = archPageDict[obj.GetArch()]
337                classPage = doxygen.Page(obj.GetClassName(),
338                                         "lc_%s" % obj.GetClassName())
339                comments = obj.GetComment()
340                if len(comments) != 0:
341                    classPage.AddDescription('<br>\n'.join(comments) + '<br>\n')
342                subArchRoot.AddPage(classPage)
343                path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
344                path = path[len(pObj.GetWorkspace()) + 1:]
345                if len(comments) == 0:
346                    classPage.AddDescription('\copydoc %s<p>' % obj.GetHeaderFile())
347                section = doxygen.Section('ref', 'Refer to Header File')
348                section.AddDescription('\link %s\n' % obj.GetHeaderFile())
349                section.AddDescription(' \endlink<p>\n')
350                classPage.AddSection(section)
351                fullPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
352
353                self.ProcessSourceFileForInclude(fullPath, pObj, configFile)
354        rootArray.append(pageRoot)
355        return rootArray
356
357    def ProcessSourceFileForInclude(self, path, pObj, configFile, infObj=None):
358        """
359        @param path        the analysising file full path
360        @param pObj        package object
361        @param configFile  doxygen config file.
362        """
363        if gInGui:
364            wx.Yield()
365        if not os.path.exists(path):
366            ErrorMsg('Source file path %s does not exist!' % path)
367            return
368
369        if configFile.FileExists(path):
370            return
371
372        try:
373            with open(path, 'r') as f:
374                lines = f.readlines()
375        except UnicodeDecodeError:
376            return
377        except IOError:
378            ErrorMsg('Fail to open file %s' % path)
379            return
380
381        configFile.AddFile(path)
382
383        no = 0
384        for no in range(len(lines)):
385            if len(lines[no].strip()) == 0:
386                continue
387            if lines[no].strip()[:2] in ['##', '//', '/*', '*/']:
388                continue
389            index = lines[no].lower().find('include')
390            #mo = IncludePattern.finditer(lines[no].lower())
391            mo = re.match(r"^#\s*include\s+[<\"]([\\/\w.]+)[>\"]$", lines[no].strip().lower())
392            if not mo:
393                continue
394            mo = re.match(r"^[#\w\s]+[<\"]([\\/\w.]+)[>\"]$", lines[no].strip())
395            filePath = mo.groups()[0]
396
397            if filePath is None or len(filePath) == 0:
398                continue
399
400            # find header file in module's path firstly.
401            fullPath = None
402
403            if os.path.exists(os.path.join(os.path.dirname(path), filePath)):
404                # Find the file in current directory
405                fullPath = os.path.join(os.path.dirname(path), filePath).replace('\\', '/')
406            else:
407                # find in depedent package's include path
408                incObjs = pObj.GetFileObj().GetSectionObjectsByName('includes')
409                for incObj in incObjs:
410                    incPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), incObj.GetPath()).strip()
411                    incPath = os.path.realpath(os.path.join(incPath, filePath))
412                    if os.path.exists(incPath):
413                        fullPath = incPath
414                        break
415                if infObj is not None:
416                    pkgInfObjs = infObj.GetSectionObjectsByName('packages')
417                    for obj in  pkgInfObjs:
418                        decObj = dec.DECFile(os.path.join(pObj.GetWorkspace(), obj.GetPath()))
419                        if not decObj:
420                            ErrorMsg ('Fail to create pacakge object for %s' % obj.GetPackageName())
421                            continue
422                        if not decObj.Parse():
423                            ErrorMsg ('Fail to load package object for %s' % obj.GetPackageName())
424                            continue
425                        incObjs = decObj.GetSectionObjectsByName('includes')
426                        for incObj in incObjs:
427                            incPath = os.path.join(decObj.GetPackageRootPath(), incObj.GetPath()).replace('\\', '/')
428                            if os.path.exists(os.path.join(incPath, filePath)):
429                                fullPath = os.path.join(os.path.join(incPath, filePath))
430                                break
431                        if fullPath is not None:
432                            break
433
434            if fullPath is None and self.IsVerbose():
435                self.Log('Can not resolve header file %s for file %s in package %s\n' % (filePath, path, pObj.GetFileObj().GetFilename()), 'error')
436                return
437            else:
438                fullPath = fullPath.replace('\\', '/')
439                if self.IsVerbose():
440                    self.Log('Preprocessing: Add include file %s for file %s\n' % (fullPath, path))
441                #LogMsg ('Preprocessing: Add include file %s for file %s' % (fullPath, path))
442                self.ProcessSourceFileForInclude(fullPath, pObj, configFile, infObj)
443
444    def AddAllIncludeFiles(self, pObj, configFile):
445        objs = pObj.GetFileObj().GetSectionObjectsByName('includes')
446        for obj in objs:
447            incPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetPath())
448            for root, dirs, files in os.walk(incPath):
449                for dir in dirs:
450                    if dir.lower() in _ignore_dir:
451                        dirs.remove(dir)
452                for file in files:
453                    path = os.path.normpath(os.path.join(root, file))
454                    configFile.AddFile(path.replace('/', '\\'))
455
456    def GeneratePcdSubPages(self, pObj, configFile):
457        """
458        Generate sub pages for package's PCD definition.
459        @param pObj         package object
460        @param configFile   config file object
461        """
462        rootArray = []
463        objs = pObj.GetFileObj().GetSectionObjectsByName('pcd')
464        if len(objs) == 0:
465            return []
466
467        pcdRootPage = doxygen.Page('PCD', 'pcd_root_page')
468        typeRootPageDict = {}
469        typeArchRootPageDict = {}
470        for obj in objs:
471            if obj.GetPcdType() not in typeRootPageDict.keys():
472                typeRootPageDict[obj.GetPcdType()] = doxygen.Page(obj.GetPcdType(), 'pcd_%s_root_page' % obj.GetPcdType())
473                pcdRootPage.AddPage(typeRootPageDict[obj.GetPcdType()])
474            typeRoot = typeRootPageDict[obj.GetPcdType()]
475            if self._arch is not None:
476                pcdPage = doxygen.Page('%s' % obj.GetPcdName(),
477                                        'pcd_%s_%s_%s' % (obj.GetPcdType(), obj.GetArch(), obj.GetPcdName().split('.')[1]))
478                pcdPage.AddDescription('<br>\n'.join(obj.GetComment()) + '<br>\n')
479                section = doxygen.Section('PCDinformation', 'PCD Information')
480                desc  = '<TABLE>'
481                desc += '<TR>'
482                desc += '<TD><CAPTION>Name</CAPTION></TD>'
483                desc += '<TD><CAPTION>Token Space</CAPTION></TD>'
484                desc += '<TD><CAPTION>Token number</CAPTION></TD>'
485                desc += '<TD><CAPTION>Data Type</CAPTION></TD>'
486                desc += '<TD><CAPTION>Default Value</CAPTION></TD>'
487                desc += '</TR>'
488                desc += '<TR>'
489                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdName().split('.')[1]
490                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdName().split('.')[0]
491                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdToken()
492                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdDataType()
493                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdValue()
494                desc += '</TR>'
495                desc += '</TABLE>'
496                section.AddDescription(desc)
497                pcdPage.AddSection(section)
498                typeRoot.AddPage(pcdPage)
499            else:
500                keystr = obj.GetPcdType() + obj.GetArch()
501                if keystr not in typeArchRootPageDict.keys():
502                    typeArchRootPage = doxygen.Page(obj.GetArch(), 'pcd_%s_%s_root_page' % (obj.GetPcdType(), obj.GetArch()))
503                    typeArchRootPageDict[keystr] = typeArchRootPage
504                    typeRoot.AddPage(typeArchRootPage)
505                typeArchRoot = typeArchRootPageDict[keystr]
506                pcdPage = doxygen.Page('%s' % obj.GetPcdName(),
507                                        'pcd_%s_%s_%s' % (obj.GetPcdType(), obj.GetArch(), obj.GetPcdName().split('.')[1]))
508                pcdPage.AddDescription('<br>\n'.join(obj.GetComment()) + '<br>\n')
509                section = doxygen.Section('PCDinformation', 'PCD Information')
510                desc  = '<TABLE>'
511                desc += '<TR>'
512                desc += '<TD><CAPTION>Name</CAPTION></TD>'
513                desc += '<TD><CAPTION>Token Space</CAPTION></TD>'
514                desc += '<TD><CAPTION>Token number</CAPTION></TD>'
515                desc += '<TD><CAPTION>Data Type</CAPTION></TD>'
516                desc += '<TD><CAPTION>Default Value</CAPTION></TD>'
517                desc += '</TR>'
518                desc += '<TR>'
519                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdName().split('.')[1]
520                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdName().split('.')[0]
521                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdToken()
522                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdDataType()
523                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdValue()
524                desc += '</TR>'
525                desc += '</TABLE>'
526                section.AddDescription(desc)
527                pcdPage.AddSection(section)
528                typeArchRoot.AddPage(pcdPage)
529        return [pcdRootPage]
530
531    def _GenerateGuidSubPage(self, pObj, obj, configFile):
532        guidPage = doxygen.Page('%s' % obj.GetName(),
533                                'guid_%s_%s' % (obj.GetArch(), obj.GetName()))
534        comments = obj.GetComment()
535        if len(comments) != 0:
536            guidPage.AddDescription('<br>'.join(obj.GetComment()) + '<br>')
537        section = doxygen.Section('BasicGuidInfo', 'GUID Information')
538        desc  = '<TABLE>'
539        desc += '<TR>'
540        desc += '<TD><CAPTION>GUID\'s Guid Name</CAPTION></TD><TD><CAPTION>GUID\'s Guid</CAPTION></TD>'
541        desc += '</TR>'
542        desc += '<TR>'
543        desc += '<TD>%s</TD>' % obj.GetName()
544        desc += '<TD>%s</TD>' % obj.GetGuid()
545        desc += '</TR>'
546        desc += '</TABLE>'
547        section.AddDescription(desc)
548        guidPage.AddSection(section)
549        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
550        if refFile:
551            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
552            if len(comments) == 0:
553                guidPage.AddDescription(' \\copydoc %s <br>' % relPath)
554
555            section = doxygen.Section('ref', 'Refer to Header File')
556            section.AddDescription('\link %s\n' % relPath)
557            section.AddDescription('\endlink\n')
558            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
559            guidPage.AddSection(section)
560        return guidPage
561
562    def GenerateGuidSubPages(self, pObj, configFile):
563        """
564        Generate sub pages for package's GUID definition.
565        @param  pObj            package object
566        @param  configFilf      doxygen config file object
567        """
568        pageRoot = doxygen.Page('GUID', 'guid_root_page')
569        objs = pObj.GetFileObj().GetSectionObjectsByName('guids', self._arch)
570        if len(objs) == 0: return []
571        if self._arch is not None:
572            for obj in objs:
573                pageRoot.AddPage(self._GenerateGuidSubPage(pObj, obj, configFile))
574        else:
575            guidArchRootPageDict = {}
576            for obj in objs:
577                if obj.GetArch() not in guidArchRootPageDict.keys():
578                    guidArchRoot = doxygen.Page(obj.GetArch(), 'guid_arch_root_%s' % obj.GetArch())
579                    pageRoot.AddPage(guidArchRoot)
580                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
581                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
582                guidArchRoot.AddPage(self._GenerateGuidSubPage(pObj, obj, configFile))
583        return [pageRoot]
584
585    def _GeneratePpiSubPage(self, pObj, obj, configFile):
586        guidPage = doxygen.Page(obj.GetName(), 'ppi_page_%s' % obj.GetName())
587        comments = obj.GetComment()
588        if len(comments) != 0:
589            guidPage.AddDescription('<br>'.join(obj.GetComment()) + '<br>')
590        section = doxygen.Section('BasicPpiInfo', 'PPI Information')
591        desc  = '<TABLE>'
592        desc += '<TR>'
593        desc += '<TD><CAPTION>PPI\'s Guid Name</CAPTION></TD><TD><CAPTION>PPI\'s Guid</CAPTION></TD>'
594        desc += '</TR>'
595        desc += '<TR>'
596        desc += '<TD>%s</TD>' % obj.GetName()
597        desc += '<TD>%s</TD>' % obj.GetGuid()
598        desc += '</TR>'
599        desc += '</TABLE>'
600        section.AddDescription(desc)
601        guidPage.AddSection(section)
602        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
603        if refFile:
604            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
605            if len(comments) == 0:
606                guidPage.AddDescription(' \\copydoc %s <br>' % relPath)
607            section = doxygen.Section('ref', 'Refer to Header File')
608            section.AddDescription('\link %s\n' % relPath)
609            section.AddDescription('\endlink\n')
610            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
611            guidPage.AddSection(section)
612
613        return guidPage
614
615    def GeneratePpiSubPages(self, pObj, configFile):
616        """
617        Generate sub pages for package's GUID definition.
618        @param  pObj            package object
619        @param  configFilf      doxygen config file object
620        """
621        pageRoot = doxygen.Page('PPI', 'ppi_root_page')
622        objs = pObj.GetFileObj().GetSectionObjectsByName('ppis', self._arch)
623        if len(objs) == 0: return []
624        if self._arch is not None:
625            for obj in objs:
626                pageRoot.AddPage(self._GeneratePpiSubPage(pObj, obj, configFile))
627        else:
628            guidArchRootPageDict = {}
629            for obj in objs:
630                if obj.GetArch() not in guidArchRootPageDict.keys():
631                    guidArchRoot = doxygen.Page(obj.GetArch(), 'ppi_arch_root_%s' % obj.GetArch())
632                    pageRoot.AddPage(guidArchRoot)
633                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
634                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
635                guidArchRoot.AddPage(self._GeneratePpiSubPage(pObj, obj, configFile))
636        return [pageRoot]
637
638    def _GenerateProtocolSubPage(self, pObj, obj, configFile):
639        guidPage = doxygen.Page(obj.GetName(), 'protocol_page_%s' % obj.GetName())
640        comments = obj.GetComment()
641        if len(comments) != 0:
642            guidPage.AddDescription('<br>'.join(obj.GetComment()) + '<br>')
643        section = doxygen.Section('BasicProtocolInfo', 'PROTOCOL Information')
644        desc  = '<TABLE>'
645        desc += '<TR>'
646        desc += '<TD><CAPTION>PROTOCOL\'s Guid Name</CAPTION></TD><TD><CAPTION>PROTOCOL\'s Guid</CAPTION></TD>'
647        desc += '</TR>'
648        desc += '<TR>'
649        desc += '<TD>%s</TD>' % obj.GetName()
650        desc += '<TD>%s</TD>' % obj.GetGuid()
651        desc += '</TR>'
652        desc += '</TABLE>'
653        section.AddDescription(desc)
654        guidPage.AddSection(section)
655
656        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
657        if refFile:
658            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
659            if len(comments) == 0:
660                guidPage.AddDescription(' \\copydoc %s <br>' % relPath)
661            section = doxygen.Section('ref', 'Refer to Header File')
662            section.AddDescription('\link %s\n' % relPath)
663            section.AddDescription('\endlink\n')
664            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
665            guidPage.AddSection(section)
666
667        return guidPage
668
669    def GenerateProtocolSubPages(self, pObj, configFile):
670        """
671        Generate sub pages for package's GUID definition.
672        @param  pObj            package object
673        @param  configFilf      doxygen config file object
674        """
675        pageRoot = doxygen.Page('PROTOCOL', 'protocol_root_page')
676        objs = pObj.GetFileObj().GetSectionObjectsByName('protocols', self._arch)
677        if len(objs) == 0: return []
678        if self._arch is not None:
679            for obj in objs:
680                pageRoot.AddPage(self._GenerateProtocolSubPage(pObj, obj, configFile))
681        else:
682            guidArchRootPageDict = {}
683            for obj in objs:
684                if obj.GetArch() not in guidArchRootPageDict.keys():
685                    guidArchRoot = doxygen.Page(obj.GetArch(), 'protocol_arch_root_%s' % obj.GetArch())
686                    pageRoot.AddPage(guidArchRoot)
687                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
688                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
689                guidArchRoot.AddPage(self._GenerateProtocolSubPage(pObj, obj, configFile))
690        return [pageRoot]
691
692    def FindHeaderFileForGuid(self, pObj, name, configFile):
693        """
694        For declaration header file for GUID/PPI/Protocol.
695
696        @param pObj         package object
697        @param name         guid/ppi/protocol's name
698        @param configFile   config file object
699
700        @return full path of header file and None if not found.
701        """
702        startPath  = pObj.GetFileObj().GetPackageRootPath()
703        incPath    = os.path.join(startPath, 'Include').replace('\\', '/')
704        # if <PackagePath>/include exist, then search header under it.
705        if os.path.exists(incPath):
706            startPath = incPath
707
708        for root, dirs, files in os.walk(startPath):
709            for dir in dirs:
710                if dir.lower() in _ignore_dir:
711                    dirs.remove(dir)
712            for file in files:
713                fPath = os.path.join(root, file)
714                if not IsCHeaderFile(fPath):
715                    continue
716                try:
717                    f = open(fPath, 'r')
718                    lines = f.readlines()
719                    f.close()
720                except IOError:
721                    self.Log('Fail to open file %s\n' % fPath)
722                    continue
723                for line in lines:
724                    if line.find(name) != -1 and \
725                       line.find('extern') != -1:
726                        return fPath.replace('\\', '/')
727        return None
728
729    def GetPackageModuleList(self, pObj):
730        """
731        Get all module's INF path under package's root path
732        @param     pObj  package object
733        @return    arrary of INF full path
734        """
735        mArray = []
736        packPath = pObj.GetFileObj().GetPackageRootPath()
737        if not os.path.exists:
738            return None
739        for root, dirs, files in os.walk(packPath):
740            for dir in dirs:
741                if dir.lower() in _ignore_dir:
742                    dirs.remove(dir)
743            for file in files:
744                if CheckPathPostfix(file, 'inf'):
745                    fPath = os.path.join(root, file).replace('\\', '/')
746                    mArray.append(fPath)
747        return mArray
748
749    def GenerateModulePages(self, pObj, configFile):
750        """
751        Generate sub pages for package's module which is under the package
752        root directory.
753
754        @param  pObj            package object
755        @param  configFilf      doxygen config file object
756        """
757        infList = self.GetPackageModuleList(pObj)
758        rootPages = []
759        libObjs = []
760        modObjs = []
761        for infpath in infList:
762            infObj = inf.INFFile(infpath)
763            #infObj = INFFileObject.INFFile (pObj.GetWorkspacePath(),
764            #                                inf)
765            if not infObj:
766                self.Log('Fail create INF object for %s' % inf)
767                continue
768            if not infObj.Parse():
769                self.Log('Fail to load INF file %s' % inf)
770                continue
771            if infObj.GetProduceLibraryClass() is not None:
772                libObjs.append(infObj)
773            else:
774                modObjs.append(infObj)
775
776        if len(libObjs) != 0:
777            libRootPage = doxygen.Page('Libraries', 'lib_root_page')
778            rootPages.append(libRootPage)
779            for libInf in libObjs:
780                libRootPage.AddPage(self.GenerateModulePage(pObj, libInf, configFile, True))
781
782        if len(modObjs) != 0:
783            modRootPage = doxygen.Page('Modules', 'module_root_page')
784            rootPages.append(modRootPage)
785            for modInf in modObjs:
786                modRootPage.AddPage(self.GenerateModulePage(pObj, modInf, configFile, False))
787
788        return rootPages
789
790    def GenerateModulePage(self, pObj, infObj, configFile, isLib):
791        """
792        Generate page for a module/library.
793        @param infObj     INF file object for module/library
794        @param configFile doxygen config file object
795        @param isLib      Whether this module is library
796
797        @param module doxygen page object
798        """
799        workspace = pObj.GetWorkspace()
800        refDecObjs = []
801        for obj in  infObj.GetSectionObjectsByName('packages'):
802            decObj = dec.DECFile(os.path.join(workspace, obj.GetPath()))
803            if not decObj:
804                ErrorMsg ('Fail to create pacakge object for %s' % obj.GetPackageName())
805                continue
806            if not decObj.Parse():
807                ErrorMsg ('Fail to load package object for %s' % obj.GetPackageName())
808                continue
809            refDecObjs.append(decObj)
810
811        modPage = doxygen.Page('%s' % infObj.GetBaseName(),
812                               'module_%s' % infObj.GetBaseName())
813        modPage.AddDescription(infObj.GetFileHeader())
814
815        basicInfSection = doxygen.Section('BasicModuleInformation', 'Basic Module Information')
816        desc = "<TABLE>"
817        for obj in infObj.GetSectionObjectsByName('defines'):
818            key = obj.GetKey()
819            value = obj.GetValue()
820            if key not in _inf_key_description_mapping_table.keys(): continue
821            if key == 'LIBRARY_CLASS' and value.find('|') != -1:
822                clsname, types = value.split('|')
823                desc += '<TR>'
824                desc += '<TD><B>%s</B></TD>' % _inf_key_description_mapping_table[key]
825                desc += '<TD>%s</TD>' % clsname
826                desc += '</TR>'
827
828                desc += '<TR>'
829                desc += '<TD><B>Supported Module Types</B></TD>'
830                desc += '<TD>%s</TD>' % types
831                desc += '</TR>'
832            else:
833                desc += '<TR>'
834                desc += '<TD><B>%s</B></TD>' % _inf_key_description_mapping_table[key]
835                if key == 'EFI_SPECIFICATION_VERSION' and value == '0x00020000':
836                    value = '2.0'
837                desc += '<TD>%s</TD>' % value
838                desc += '</TR>'
839        desc += '</TABLE>'
840        basicInfSection.AddDescription(desc)
841        modPage.AddSection(basicInfSection)
842
843        # Add protocol section
844        data  = []
845        for obj in infObj.GetSectionObjectsByName('pcd', self._arch):
846            data.append(obj.GetPcdName().strip())
847        if len(data) != 0:
848            s = doxygen.Section('Pcds', 'Pcds')
849            desc = "<TABLE>"
850            desc += '<TR><TD><B>PCD Name</B></TD><TD><B>TokenSpace</B></TD><TD><B>Package</B></TD></TR>'
851            for item in data:
852                desc += '<TR>'
853                desc += '<TD>%s</TD>' % item.split('.')[1]
854                desc += '<TD>%s</TD>' % item.split('.')[0]
855                pkgbasename = self.SearchPcdPackage(item, workspace, refDecObjs)
856                desc += '<TD>%s</TD>' % pkgbasename
857                desc += '</TR>'
858            desc += "</TABLE>"
859            s.AddDescription(desc)
860            modPage.AddSection(s)
861
862        # Add protocol section
863        #sects = infObj.GetSectionByString('protocol')
864        data  = []
865        #for sect in sects:
866        for obj in infObj.GetSectionObjectsByName('protocol', self._arch):
867            data.append(obj.GetName().strip())
868        if len(data) != 0:
869            s = doxygen.Section('Protocols', 'Protocols')
870            desc = "<TABLE>"
871            desc += '<TR><TD><B>Name</B></TD><TD><B>Package</B></TD></TR>'
872            for item in data:
873                desc += '<TR>'
874                desc += '<TD>%s</TD>' % item
875                pkgbasename = self.SearchProtocolPackage(item, workspace, refDecObjs)
876                desc += '<TD>%s</TD>' % pkgbasename
877                desc += '</TR>'
878            desc += "</TABLE>"
879            s.AddDescription(desc)
880            modPage.AddSection(s)
881
882        # Add ppi section
883        #sects = infObj.GetSectionByString('ppi')
884        data  = []
885        #for sect in sects:
886        for obj in infObj.GetSectionObjectsByName('ppi', self._arch):
887            data.append(obj.GetName().strip())
888        if len(data) != 0:
889            s = doxygen.Section('Ppis', 'Ppis')
890            desc = "<TABLE>"
891            desc += '<TR><TD><B>Name</B></TD><TD><B>Package</B></TD></TR>'
892            for item in data:
893                desc += '<TR>'
894                desc += '<TD>%s</TD>' % item
895                pkgbasename = self.SearchPpiPackage(item, workspace, refDecObjs)
896                desc += '<TD>%s</TD>' % pkgbasename
897                desc += '</TR>'
898            desc += "</TABLE>"
899            s.AddDescription(desc)
900            modPage.AddSection(s)
901
902        # Add guid section
903        #sects = infObj.GetSectionByString('guid')
904        data  = []
905        #for sect in sects:
906        for obj in infObj.GetSectionObjectsByName('guid', self._arch):
907            data.append(obj.GetName().strip())
908        if len(data) != 0:
909            s = doxygen.Section('Guids', 'Guids')
910            desc = "<TABLE>"
911            desc += '<TR><TD><B>Name</B></TD><TD><B>Package</B></TD></TR>'
912            for item in data:
913                desc += '<TR>'
914                desc += '<TD>%s</TD>' % item
915                pkgbasename = self.SearchGuidPackage(item, workspace, refDecObjs)
916                desc += '<TD>%s</TD>' % pkgbasename
917                desc += '</TR>'
918            desc += "</TABLE>"
919            s.AddDescription(desc)
920            modPage.AddSection(s)
921
922        section = doxygen.Section('LibraryClasses', 'Library Classes')
923        desc = "<TABLE>"
924        desc += '<TR><TD><B>Name</B></TD><TD><B>Type</B></TD><TD><B>Package</B></TD><TD><B>Header File</B></TD></TR>'
925        if isLib:
926            desc += '<TR>'
927            desc += '<TD>%s</TD>' % infObj.GetProduceLibraryClass()
928            desc += '<TD>Produce</TD>'
929            try:
930                pkgname, hPath = self.SearchLibraryClassHeaderFile(infObj.GetProduceLibraryClass(),
931                                                              workspace,
932                                                              refDecObjs)
933            except:
934                self.Log ('fail to get package header file for lib class %s' % infObj.GetProduceLibraryClass())
935                pkgname = 'NULL'
936                hPath   = 'NULL'
937            desc += '<TD>%s</TD>' % pkgname
938            if hPath != "NULL":
939                desc += '<TD>\link %s \endlink</TD>' % hPath
940            else:
941                desc += '<TD>%s</TD>' % hPath
942            desc += '</TR>'
943        for lcObj in infObj.GetSectionObjectsByName('libraryclasses', self._arch):
944            desc += '<TR>'
945            desc += '<TD>%s</TD>' % lcObj.GetClass()
946            retarr = self.SearchLibraryClassHeaderFile(lcObj.GetClass(),
947                                                       workspace,
948                                                       refDecObjs)
949            if retarr is not None:
950                pkgname, hPath = retarr
951            else:
952                self.Log('Fail find the library class %s definition from module %s dependent package!' % (lcObj.GetClass(), infObj.GetFilename()), 'error')
953                pkgname = 'NULL'
954                hPath   = 'NULL'
955            desc += '<TD>Consume</TD>'
956            desc += '<TD>%s</TD>' % pkgname
957            desc += '<TD>\link %s \endlink</TD>' % hPath
958            desc += '</TR>'
959        desc += "</TABLE>"
960        section.AddDescription(desc)
961        modPage.AddSection(section)
962
963        section = doxygen.Section('SourceFiles', 'Source Files')
964        section.AddDescription('<ul>\n')
965        for obj in infObj.GetSourceObjects(self._arch, self._tooltag):
966            sPath = infObj.GetModuleRootPath()
967            sPath = os.path.join(sPath, obj.GetSourcePath()).replace('\\', '/').strip()
968            if sPath.lower().endswith('.uni') or sPath.lower().endswith('.s') or sPath.lower().endswith('.asm') or sPath.lower().endswith('.nasm'):
969                newPath = self.TranslateUniFile(sPath)
970                configFile.AddFile(newPath)
971                newPath = newPath[len(pObj.GetWorkspace()) + 1:]
972                section.AddDescription('<li> \link %s \endlink </li>' %  newPath)
973            else:
974                self.ProcessSourceFileForInclude(sPath, pObj, configFile, infObj)
975                sPath = sPath[len(pObj.GetWorkspace()) + 1:]
976                section.AddDescription('<li>\link %s \endlink </li>' % sPath)
977        section.AddDescription('</ul>\n')
978        modPage.AddSection(section)
979
980        #sects = infObj.GetSectionByString('depex')
981        data  = []
982        #for sect in sects:
983        for obj in infObj.GetSectionObjectsByName('depex'):
984            data.append(str(obj))
985        if len(data) != 0:
986            s = doxygen.Section('DependentSection', 'Module Dependencies')
987            s.AddDescription('<br>'.join(data))
988            modPage.AddSection(s)
989
990        return modPage
991
992    def TranslateUniFile(self, path):
993        newpath = path + '.dox'
994        #import core.textfile as textfile
995        #file = textfile.TextFile(path)
996
997        try:
998            file = open(path, 'r')
999        except (IOError, OSError) as msg:
1000            return None
1001
1002        t = file.read()
1003        file.close()
1004
1005        output = '/** @file \n'
1006        #output = '<html><body>'
1007        arr = t.split('\r\n')
1008        for line in arr:
1009            if line.find('@file') != -1:
1010                continue
1011            if line.find('*/') != -1:
1012                continue
1013            line = line.strip()
1014            if line.strip().startswith('/'):
1015                arr = line.split(' ')
1016                if len(arr) > 1:
1017                    line = ' '.join(arr[1:])
1018                else:
1019                    continue
1020            output += '%s<br>\n' % line
1021        output += '**/'
1022
1023        if os.path.exists(newpath):
1024            os.remove(newpath)
1025
1026        file = open(newpath, "w")
1027        file.write(output)
1028        file.close()
1029        return newpath
1030
1031    def SearchPcdPackage(self, pcdname, workspace, decObjs):
1032        for decObj in  decObjs:
1033            for pcd in decObj.GetSectionObjectsByName('pcd'):
1034                if pcdname == pcd.GetPcdName():
1035                    return decObj.GetBaseName()
1036        return None
1037
1038    def SearchProtocolPackage(self, protname, workspace, decObjs):
1039        for decObj in  decObjs:
1040            for proto in decObj.GetSectionObjectsByName('protocol'):
1041                if protname == proto.GetName():
1042                    return decObj.GetBaseName()
1043        return None
1044
1045    def SearchPpiPackage(self, ppiname, workspace, decObjs):
1046        for decObj in  decObjs:
1047            for ppi in decObj.GetSectionObjectsByName('ppi'):
1048                if ppiname == ppi.GetName():
1049                    return decObj.GetBaseName()
1050        return None
1051
1052    def SearchGuidPackage(self, guidname, workspace, decObjs):
1053        for decObj in  decObjs:
1054            for guid in decObj.GetSectionObjectsByName('guid'):
1055                if guidname == guid.GetName():
1056                    return decObj.GetBaseName()
1057        return None
1058
1059    def SearchLibraryClassHeaderFile(self, className, workspace, decObjs):
1060        for decObj in  decObjs:
1061            for cls in decObj.GetSectionObjectsByName('libraryclasses'):
1062                if cls.GetClassName().strip() == className:
1063                    path = cls.GetHeaderFile().strip()
1064                    path = os.path.join(decObj.GetPackageRootPath(), path)
1065                    path = path[len(workspace) + 1:]
1066                    return decObj.GetBaseName(), path.replace('\\', '/')
1067
1068        return None
1069
1070    def _ConvertPathToDoxygen(self, path, pObj):
1071        pRootPath = pObj.GetWorkspace()
1072        path = path[len(pRootPath) + 1:]
1073        return path.replace('\\', '/')
1074
1075def IsCHeaderFile(path):
1076    return CheckPathPostfix(path, 'h')
1077
1078def CheckPathPostfix(path, str):
1079    index = path.rfind('.')
1080    if index == -1:
1081        return False
1082    if path[index + 1:].lower() == str.lower():
1083        return True
1084    return False
1085