## @file | |
# This module provide command line entry for generating package document! | |
# | |
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> | |
# | |
# 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. | |
# | |
import os, sys, logging, traceback, subprocess | |
from optparse import OptionParser | |
import plugins.EdkPlugins.edk2.model.baseobject as baseobject | |
import plugins.EdkPlugins.edk2.model.doxygengen as doxygengen | |
gArchMarcoDict = {'ALL' : 'MDE_CPU_IA32 MDE_CPU_X64 MDE_CPU_EBC MDE_CPU_IPF _MSC_EXTENSIONS __GNUC__ __INTEL_COMPILER', | |
'IA32_MSFT': 'MDE_CPU_IA32 _MSC_EXTENSIONS', | |
'IA32_GNU' : 'MDE_CPU_IA32 __GNUC__', | |
'X64_MSFT' : 'MDE_CPU_X64 _MSC_EXTENSIONS ASM_PFX= OPTIONAL= ', | |
'X64_GNU' : 'MDE_CPU_X64 __GNUC__ ASM_PFX= OPTIONAL= ', | |
'IPF_MSFT' : 'MDE_CPU_IPF _MSC_EXTENSIONS ASM_PFX= OPTIONAL= ', | |
'IPF_GNU' : 'MDE_CPU_IPF __GNUC__ ASM_PFX= OPTIONAL= ', | |
'EBC_INTEL': 'MDE_CPU_EBC __INTEL_COMPILER ASM_PFX= OPTIONAL= '} | |
def parseCmdArgs(): | |
parser = OptionParser(version="Package Document Generation Tools - Version 0.1") | |
parser.add_option('-w', '--workspace', action='store', type='string', dest='WorkspacePath', | |
help='Specify workspace absolute path. For example: c:\\tianocore') | |
parser.add_option('-p', '--decfile', action='store', dest='PackagePath', | |
help='Specify the absolute path for package DEC file. For example: c:\\tianocore\\MdePkg\\MdePkg.dec') | |
parser.add_option('-x', '--doxygen', action='store', dest='DoxygenPath', | |
help='Specify the absolute path of doxygen tools installation. For example: C:\\Program Files\\doxygen\bin\doxygen.exe') | |
parser.add_option('-o', '--output', action='store', dest='OutputPath', | |
help='Specify the document output path. For example: c:\\docoutput') | |
parser.add_option('-a', '--arch', action='store', dest='Arch', choices=gArchMarcoDict.keys(), | |
help='Specify the architecture used in preprocess package\'s source. For example: -a IA32_MSFT') | |
parser.add_option('-m', '--mode', action='store', dest='DocumentMode', choices=['CHM', 'HTML'], | |
help='Specify the document mode from : CHM or HTML') | |
parser.add_option('-i', '--includeonly', action='store_true', dest='IncludeOnly', | |
help='Only generate document for package\'s public interfaces produced by include folder. ') | |
parser.add_option('-c', '--htmlworkshop', dest='HtmlWorkshopPath', | |
help='Specify the absolute path for Microsoft HTML Workshop\'s hhc.exe file. For example: C:\\Program Files\\HTML Help Workshop\\hhc.exe') | |
(options, args) = parser.parse_args() | |
# validate the options | |
errors = [] | |
if options.WorkspacePath == None: | |
errors.append('- Please specify workspace path via option -w!') | |
elif not os.path.exists(options.WorkspacePath): | |
errors.append("- Invalid workspace path %s! The workspace path should be exist in absolute path!" % options.WorkspacePath) | |
if options.PackagePath == None: | |
errors.append('- Please specify package DEC file path via option -p!') | |
elif not os.path.exists(options.PackagePath): | |
errors.append("- Invalid package's DEC file path %s! The DEC path should be exist in absolute path!" % options.PackagePath) | |
default = "C:\\Program Files\\doxygen\\bin\\doxygen.exe" | |
if options.DoxygenPath == None: | |
if os.path.exists(default): | |
print "Warning: Assume doxygen tool is installed at %s. If not, please specify via -x" % default | |
options.DoxygenPath = default | |
else: | |
errors.append('- Please specify the path of doxygen tool installation via option -x! or install it in default path %s' % default) | |
elif not os.path.exists(options.DoxygenPath): | |
errors.append("- Invalid doxygen tool path %s! The doxygen tool path should be exist in absolute path!" % options.DoxygenPath) | |
if options.OutputPath != None: | |
if not os.path.exists(options.OutputPath): | |
# create output | |
try: | |
os.makedirs(options.OutputPath) | |
except: | |
errors.append('- Fail to create the output directory %s' % options.OutputPath) | |
else: | |
if options.PackagePath != None and os.path.exists(options.PackagePath): | |
dirpath = os.path.dirname(options.PackagePath) | |
default = os.path.join (dirpath, "Document") | |
print 'Warning: Assume document output at %s. If not, please specify via option -o' % default | |
options.OutputPath = default | |
if not os.path.exists(default): | |
try: | |
os.makedirs(default) | |
except: | |
errors.append('- Fail to create default output directory %s! Please specify document output diretory via option -o' % default) | |
else: | |
errors.append('- Please specify document output path via option -o!') | |
if options.Arch == None: | |
options.Arch = 'ALL' | |
print "Warning: Assume arch is \"ALL\". If not, specify via -a" | |
if options.DocumentMode == None: | |
options.DocumentMode = "HTML" | |
print "Warning: Assume document mode is \"HTML\". If not, specify via -m" | |
if options.IncludeOnly == None: | |
options.IncludeOnly = False | |
print "Warning: Assume generate package document for all package\'s source including publich interfaces and implementation libraries and modules." | |
if options.DocumentMode.lower() == 'chm': | |
default = "C:\\Program Files\\HTML Help Workshop\\hhc.exe" | |
if options.HtmlWorkshopPath == None: | |
if os.path.exists(default): | |
print 'Warning: Assume the installation path of Microsoft HTML Workshop is %s. If not, specify via option -c.' % default | |
options.HtmlWorkshopPath = default | |
else: | |
errors.append('- Please specify the installation path of Microsoft HTML Workshop via option -c!') | |
elif not os.path.exists(options.HtmlWorkshopPath): | |
errors.append('- The installation path of Microsoft HTML Workshop %s does not exists. ' % options.HtmlWorkshopPath) | |
if len(errors) != 0: | |
print '\n' | |
parser.error('Fail to start due to following reasons: \n%s' %'\n'.join(errors)) | |
return (options.WorkspacePath, options.PackagePath, options.DoxygenPath, options.OutputPath, | |
options.Arch, options.DocumentMode, options.IncludeOnly, options.HtmlWorkshopPath) | |
def createPackageObject(wsPath, pkgPath): | |
try: | |
pkgObj = baseobject.Package(None, wsPath) | |
pkgObj.Load(pkgPath) | |
except: | |
logging.getLogger().error ('Fail to create package object!') | |
return None | |
return pkgObj | |
def callbackLogMessage(msg, level): | |
print msg.strip() | |
def callbackCreateDoxygenProcess(doxPath, configPath): | |
if sys.platform == 'win32': | |
cmd = '"%s" %s' % (doxPath, configPath) | |
else: | |
cmd = '%s %s' % (doxPath, configPath) | |
print cmd | |
subprocess.call(cmd, shell=True) | |
def DocumentFixup(outPath, arch): | |
# find BASE_LIBRARY_JUMP_BUFFER structure reference page | |
print '\n >>> Start fixup document \n' | |
for root, dirs, files in os.walk(outPath): | |
for dir in dirs: | |
if dir.lower() in ['.svn', '_svn', 'cvs']: | |
dirs.remove(dir) | |
for file in files: | |
if not file.lower().endswith('.html'): continue | |
fullpath = os.path.join(outPath, root, file) | |
try: | |
f = open(fullpath, 'r') | |
text = f.read() | |
f.close() | |
except: | |
logging.getLogger().error('\nFail to open file %s\n' % fullpath) | |
continue | |
if arch.lower() == 'all': | |
if text.find('BASE_LIBRARY_JUMP_BUFFER Struct Reference') != -1: | |
FixPageBASE_LIBRARY_JUMP_BUFFER(fullpath, text) | |
if text.find('MdePkg/Include/Library/BaseLib.h File Reference') != -1: | |
FixPageBaseLib(fullpath, text) | |
if text.find('IA32_IDT_GATE_DESCRIPTOR Union Reference') != -1: | |
FixPageIA32_IDT_GATE_DESCRIPTOR(fullpath, text) | |
if text.find('MdePkg/Include/Library/UefiDriverEntryPoint.h File Reference') != -1: | |
FixPageUefiDriverEntryPoint(fullpath, text) | |
if text.find('MdePkg/Include/Library/UefiApplicationEntryPoint.h File Reference') != -1: | |
FixPageUefiApplicationEntryPoint(fullpath, text) | |
print ' >>> Finish all document fixing up! \n' | |
def FixPageBaseLib(path, text): | |
print ' >>> Fixup BaseLib file page at file %s \n' % path | |
lines = text.split('\n') | |
lastBaseJumpIndex = -1 | |
lastIdtGateDescriptor = -1 | |
for index in range(len(lines) - 1, -1, -1): | |
line = lines[index] | |
if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT 4 </td>': | |
lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT 4 [IA32] </td>' | |
if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT 0x10 </td>': | |
lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT 0x10 [IPF] </td>' | |
if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT 8 </td>': | |
lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT 9 [EBC, x64] </td>' | |
if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 4') != -1: | |
lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 4', | |
'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 4 [IA32]') | |
if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 0x10') != -1: | |
lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 0x10', | |
'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 0x10 [IPF]') | |
if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 8') != -1: | |
lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 8', | |
'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a> 8 [x64, EBC]') | |
if line.find('>BASE_LIBRARY_JUMP_BUFFER</a>') != -1: | |
if lastBaseJumpIndex != -1: | |
del lines[lastBaseJumpIndex] | |
lastBaseJumpIndex = index | |
if line.find('>IA32_IDT_GATE_DESCRIPTOR</a></td>') != -1: | |
if lastIdtGateDescriptor != -1: | |
del lines[lastIdtGateDescriptor] | |
lastIdtGateDescriptor = index | |
try: | |
f = open(path, 'w') | |
f.write('\n'.join(lines)) | |
f.close() | |
except: | |
logging.getLogger().error(" <<< Fail to fixup file %s\n" % path) | |
return | |
print " <<< Finish to fixup file %s\n" % path | |
def FixPageIA32_IDT_GATE_DESCRIPTOR(path, text): | |
print ' >>> Fixup structure reference IA32_IDT_GATE_DESCRIPTOR at file %s \n' % path | |
lines = text.split('\n') | |
for index in range(len(lines) - 1, -1, -1): | |
line = lines[index].strip() | |
if line.find('struct {</td>') != -1 and lines[index - 2].find('>Uint64</a></td>') != -1: | |
lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>') | |
if line.find('struct {</td>') != -1 and lines[index - 1].find('Data Fields') != -1: | |
lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>') | |
try: | |
f = open(path, 'w') | |
f.write('\n'.join(lines)) | |
f.close() | |
except: | |
logging.getLogger().error(" <<< Fail to fixup file %s\n" % path) | |
return | |
print " <<< Finish to fixup file %s\n" % path | |
def FixPageBASE_LIBRARY_JUMP_BUFFER(path, text): | |
print ' >>> Fixup structure reference BASE_LIBRARY_JUMP_BUFFER at file %s \n' % path | |
lines = text.split('\n') | |
bInDetail = True | |
bNeedRemove = False | |
for index in range(len(lines) - 1, -1, -1): | |
line = lines[index] | |
if line.find('Detailed Description') != -1: | |
bInDetail = False | |
if line.startswith('EBC context buffer used by') and lines[index - 1].startswith('x64 context buffer'): | |
lines[index] = "IA32/IPF/X64/" + line | |
bNeedRemove = True | |
if line.startswith("x64 context buffer") or line.startswith('IPF context buffer used by') or \ | |
line.startswith('IA32 context buffer used by'): | |
if bNeedRemove: | |
lines.remove(line) | |
if line.find('>R0</a>') != -1 and not bInDetail: | |
if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For EBC</h2></td></tr>': | |
lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For EBC</h2></td></tr>') | |
if line.find('>Rbx</a>') != -1 and not bInDetail: | |
if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>': | |
lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>') | |
if line.find('>F2</a>') != -1 and not bInDetail: | |
if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For IPF</h2></td></tr>': | |
lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IPF</h2></td></tr>') | |
if line.find('>Ebx</a>') != -1 and not bInDetail: | |
if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>': | |
lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>') | |
try: | |
f = open(path, 'w') | |
f.write('\n'.join(lines)) | |
f.close() | |
except: | |
logging.getLogger().error(" <<< Fail to fixup file %s" % path) | |
return | |
print " <<< Finish to fixup file %s\n" % path | |
def FixPageUefiDriverEntryPoint(path, text): | |
print ' >>> Fixup file reference MdePkg/Include/Library/UefiDriverEntryPoint.h at file %s \n' % path | |
lines = text.split('\n') | |
bInModuleEntry = False | |
bInEfiMain = False | |
ModuleEntryDlCount = 0 | |
ModuleEntryDelStart = 0 | |
ModuleEntryDelEnd = 0 | |
EfiMainDlCount = 0 | |
EfiMainDelStart = 0 | |
EfiMainDelEnd = 0 | |
for index in range(len(lines)): | |
line = lines[index].strip() | |
if line.find('EFI_STATUS</a> EFIAPI _ModuleEntryPoint </td>') != -1: | |
bInModuleEntry = True | |
if line.find('EFI_STATUS</a> EFIAPI EfiMain </td>') != -1: | |
bInEfiMain = True | |
if line.startswith('<p>References <a'): | |
if bInModuleEntry: | |
ModuleEntryDelEnd = index - 1 | |
bInModuleEntry = False | |
elif bInEfiMain: | |
EfiMainDelEnd = index - 1 | |
bInEfiMain = False | |
if bInModuleEntry: | |
if line.startswith('</dl>'): | |
ModuleEntryDlCount = ModuleEntryDlCount + 1 | |
if ModuleEntryDlCount == 1: | |
ModuleEntryDelStart = index + 1 | |
if bInEfiMain: | |
if line.startswith('</dl>'): | |
EfiMainDlCount = EfiMainDlCount + 1 | |
if EfiMainDlCount == 1: | |
EfiMainDelStart = index + 1 | |
if EfiMainDelEnd > EfiMainDelStart: | |
for index in range(EfiMainDelEnd, EfiMainDelStart, -1): | |
del lines[index] | |
if ModuleEntryDelEnd > ModuleEntryDelStart: | |
for index in range(ModuleEntryDelEnd, ModuleEntryDelStart, -1): | |
del lines[index] | |
try: | |
f = open(path, 'w') | |
f.write('\n'.join(lines)) | |
f.close() | |
except: | |
logging.getLogger().error(" <<< Fail to fixup file %s" % path) | |
return | |
print " <<< Finish to fixup file %s\n" % path | |
def FixPageUefiApplicationEntryPoint(path, text): | |
print ' >>> Fixup file reference MdePkg/Include/Library/UefiApplicationEntryPoint.h at file %s \n' % path | |
lines = text.split('\n') | |
bInModuleEntry = False | |
bInEfiMain = False | |
ModuleEntryDlCount = 0 | |
ModuleEntryDelStart = 0 | |
ModuleEntryDelEnd = 0 | |
EfiMainDlCount = 0 | |
EfiMainDelStart = 0 | |
EfiMainDelEnd = 0 | |
for index in range(len(lines)): | |
line = lines[index].strip() | |
if line.find('EFI_STATUS</a> EFIAPI _ModuleEntryPoint </td>') != -1: | |
bInModuleEntry = True | |
if line.find('EFI_STATUS</a> EFIAPI EfiMain </td>') != -1: | |
bInEfiMain = True | |
if line.startswith('<p>References <a'): | |
if bInModuleEntry: | |
ModuleEntryDelEnd = index - 1 | |
bInModuleEntry = False | |
elif bInEfiMain: | |
EfiMainDelEnd = index - 1 | |
bInEfiMain = False | |
if bInModuleEntry: | |
if line.startswith('</dl>'): | |
ModuleEntryDlCount = ModuleEntryDlCount + 1 | |
if ModuleEntryDlCount == 1: | |
ModuleEntryDelStart = index + 1 | |
if bInEfiMain: | |
if line.startswith('</dl>'): | |
EfiMainDlCount = EfiMainDlCount + 1 | |
if EfiMainDlCount == 1: | |
EfiMainDelStart = index + 1 | |
if EfiMainDelEnd > EfiMainDelStart: | |
for index in range(EfiMainDelEnd, EfiMainDelStart, -1): | |
del lines[index] | |
if ModuleEntryDelEnd > ModuleEntryDelStart: | |
for index in range(ModuleEntryDelEnd, ModuleEntryDelStart, -1): | |
del lines[index] | |
try: | |
f = open(path, 'w') | |
f.write('\n'.join(lines)) | |
f.close() | |
except: | |
logging.getLogger().error(" <<< Fail to fixup file %s" % path) | |
return | |
print " <<< Finish to fixup file %s\n" % path | |
if __name__ == '__main__': | |
wspath, pkgpath, doxpath, outpath, archtag, docmode, isinc, hwpath = parseCmdArgs() | |
# configure logging system | |
logfilepath = os.path.join(outpath, 'log.txt') | |
logging.basicConfig(format='%(levelname)-8s %(message)s', level=logging.DEBUG) | |
# create package model object firstly | |
pkgObj = createPackageObject(wspath, pkgpath) | |
if pkgObj == None: | |
sys.exit(-1) | |
# create doxygen action model | |
arch = None | |
tooltag = None | |
if archtag.lower() != 'all': | |
arch = archtag.split('_')[0] | |
tooltag = archtag.split('_')[1] | |
else: | |
arch = 'all' | |
tooltag = 'all' | |
# preprocess package and call doxygen | |
try: | |
action = doxygengen.PackageDocumentAction(doxpath, | |
hwpath, | |
outpath, | |
pkgObj, | |
docmode, | |
callbackLogMessage, | |
arch, | |
tooltag, | |
isinc, | |
True) | |
action.RegisterCallbackDoxygenProcess(callbackCreateDoxygenProcess) | |
action.Generate() | |
except: | |
message = traceback.format_exception(*sys.exc_info()) | |
logging.getLogger().error('Fail to create doxygen action! \n%s' % ''.join(message)) | |
sys.exit(-1) | |
DocumentFixup(outpath, arch) | |
# generate CHM is necessary | |
if docmode.lower() == 'chm': | |
indexpath = os.path.join(outpath, 'html', 'index.hhp') | |
if sys.platform == 'win32': | |
cmd = '"%s" %s' % (hwpath, indexpath) | |
else: | |
cmd = '%s %s' % (hwpath, indexpath) | |
subprocess.call(cmd) | |
print '\nFinish to generate package document! Please open %s for review' % os.path.join(outpath, 'html', 'index.chm') | |
else: | |
print '\nFinish to generate package document! Please open %s for review' % os.path.join(outpath, 'html', 'index.html') |