packagedoc_cli.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. ## @file
  2. # This module provide command line entry for generating package document!
  3. #
  4. # Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
  5. #
  6. # SPDX-License-Identifier: BSD-2-Clause-Patent
  7. #
  8. from __future__ import print_function
  9. import os, sys, logging, traceback, subprocess
  10. from optparse import OptionParser
  11. from plugins.EdkPlugins.edk2.model import baseobject
  12. from plugins.EdkPlugins.edk2.model import doxygengen
  13. gArchMarcoDict = {'ALL' : 'MDE_CPU_IA32 MDE_CPU_X64 MDE_CPU_EBC MDE_CPU_IPF _MSC_EXTENSIONS __GNUC__ __INTEL_COMPILER',
  14. 'IA32_MSFT': 'MDE_CPU_IA32 _MSC_EXTENSIONS',
  15. 'IA32_GNU' : 'MDE_CPU_IA32 __GNUC__',
  16. 'X64_MSFT' : 'MDE_CPU_X64 _MSC_EXTENSIONS ASM_PFX= OPTIONAL= ',
  17. 'X64_GNU' : 'MDE_CPU_X64 __GNUC__ ASM_PFX= OPTIONAL= ',
  18. 'IPF_MSFT' : 'MDE_CPU_IPF _MSC_EXTENSIONS ASM_PFX= OPTIONAL= ',
  19. 'IPF_GNU' : 'MDE_CPU_IPF __GNUC__ ASM_PFX= OPTIONAL= ',
  20. 'EBC_INTEL': 'MDE_CPU_EBC __INTEL_COMPILER ASM_PFX= OPTIONAL= '}
  21. def parseCmdArgs():
  22. parser = OptionParser(version="Package Document Generation Tools - Version 0.1")
  23. parser.add_option('-w', '--workspace', action='store', type='string', dest='WorkspacePath',
  24. help='Specify workspace absolute path. For example: c:\\tianocore')
  25. parser.add_option('-p', '--decfile', action='store', dest='PackagePath',
  26. help='Specify the absolute path for package DEC file. For example: c:\\tianocore\\MdePkg\\MdePkg.dec')
  27. parser.add_option('-x', '--doxygen', action='store', dest='DoxygenPath',
  28. help='Specify the absolute path of doxygen tools installation. For example: C:\\Program Files\\doxygen\bin\doxygen.exe')
  29. parser.add_option('-o', '--output', action='store', dest='OutputPath',
  30. help='Specify the document output path. For example: c:\\docoutput')
  31. parser.add_option('-a', '--arch', action='store', dest='Arch', choices=list(gArchMarcoDict.keys()),
  32. help='Specify the architecture used in preprocess package\'s source. For example: -a IA32_MSFT')
  33. parser.add_option('-m', '--mode', action='store', dest='DocumentMode', choices=['CHM', 'HTML'],
  34. help='Specify the document mode from : CHM or HTML')
  35. parser.add_option('-i', '--includeonly', action='store_true', dest='IncludeOnly',
  36. help='Only generate document for package\'s public interfaces produced by include folder. ')
  37. parser.add_option('-c', '--htmlworkshop', dest='HtmlWorkshopPath',
  38. help='Specify the absolute path for Microsoft HTML Workshop\'s hhc.exe file. For example: C:\\Program Files\\HTML Help Workshop\\hhc.exe')
  39. (options, args) = parser.parse_args()
  40. # validate the options
  41. errors = []
  42. if options.WorkspacePath is None:
  43. errors.append('- Please specify workspace path via option -w!')
  44. elif not os.path.exists(options.WorkspacePath):
  45. errors.append("- Invalid workspace path %s! The workspace path should be exist in absolute path!" % options.WorkspacePath)
  46. if options.PackagePath is None:
  47. errors.append('- Please specify package DEC file path via option -p!')
  48. elif not os.path.exists(options.PackagePath):
  49. errors.append("- Invalid package's DEC file path %s! The DEC path should be exist in absolute path!" % options.PackagePath)
  50. default = "C:\\Program Files\\doxygen\\bin\\doxygen.exe"
  51. if options.DoxygenPath is None:
  52. if os.path.exists(default):
  53. print("Warning: Assume doxygen tool is installed at %s. If not, please specify via -x" % default)
  54. options.DoxygenPath = default
  55. else:
  56. errors.append('- Please specify the path of doxygen tool installation via option -x! or install it in default path %s' % default)
  57. elif not os.path.exists(options.DoxygenPath):
  58. errors.append("- Invalid doxygen tool path %s! The doxygen tool path should be exist in absolute path!" % options.DoxygenPath)
  59. if options.OutputPath is not None:
  60. if not os.path.exists(options.OutputPath):
  61. # create output
  62. try:
  63. os.makedirs(options.OutputPath)
  64. except:
  65. errors.append('- Fail to create the output directory %s' % options.OutputPath)
  66. else:
  67. if options.PackagePath is not None and os.path.exists(options.PackagePath):
  68. dirpath = os.path.dirname(options.PackagePath)
  69. default = os.path.join (dirpath, "Document")
  70. print('Warning: Assume document output at %s. If not, please specify via option -o' % default)
  71. options.OutputPath = default
  72. if not os.path.exists(default):
  73. try:
  74. os.makedirs(default)
  75. except:
  76. errors.append('- Fail to create default output directory %s! Please specify document output diretory via option -o' % default)
  77. else:
  78. errors.append('- Please specify document output path via option -o!')
  79. if options.Arch is None:
  80. options.Arch = 'ALL'
  81. print("Warning: Assume arch is \"ALL\". If not, specify via -a")
  82. if options.DocumentMode is None:
  83. options.DocumentMode = "HTML"
  84. print("Warning: Assume document mode is \"HTML\". If not, specify via -m")
  85. if options.IncludeOnly is None:
  86. options.IncludeOnly = False
  87. print("Warning: Assume generate package document for all package\'s source including publich interfaces and implementation libraries and modules.")
  88. if options.DocumentMode.lower() == 'chm':
  89. default = "C:\\Program Files\\HTML Help Workshop\\hhc.exe"
  90. if options.HtmlWorkshopPath is None:
  91. if os.path.exists(default):
  92. print('Warning: Assume the installation path of Microsoft HTML Workshop is %s. If not, specify via option -c.' % default)
  93. options.HtmlWorkshopPath = default
  94. else:
  95. errors.append('- Please specify the installation path of Microsoft HTML Workshop via option -c!')
  96. elif not os.path.exists(options.HtmlWorkshopPath):
  97. errors.append('- The installation path of Microsoft HTML Workshop %s does not exists. ' % options.HtmlWorkshopPath)
  98. if len(errors) != 0:
  99. print('\n')
  100. parser.error('Fail to start due to following reasons: \n%s' %'\n'.join(errors))
  101. return (options.WorkspacePath, options.PackagePath, options.DoxygenPath, options.OutputPath,
  102. options.Arch, options.DocumentMode, options.IncludeOnly, options.HtmlWorkshopPath)
  103. def createPackageObject(wsPath, pkgPath):
  104. try:
  105. pkgObj = baseobject.Package(None, wsPath)
  106. pkgObj.Load(pkgPath)
  107. except:
  108. logging.getLogger().error ('Fail to create package object!')
  109. return None
  110. return pkgObj
  111. def callbackLogMessage(msg, level):
  112. print(msg.strip())
  113. def callbackCreateDoxygenProcess(doxPath, configPath):
  114. if sys.platform == 'win32':
  115. cmd = '"%s" %s' % (doxPath, configPath)
  116. else:
  117. cmd = '%s %s' % (doxPath, configPath)
  118. print(cmd)
  119. subprocess.call(cmd, shell=True)
  120. def DocumentFixup(outPath, arch):
  121. # find BASE_LIBRARY_JUMP_BUFFER structure reference page
  122. print('\n >>> Start fixup document \n')
  123. for root, dirs, files in os.walk(outPath):
  124. for dir in dirs:
  125. if dir.lower() in ['.svn', '_svn', 'cvs']:
  126. dirs.remove(dir)
  127. for file in files:
  128. if not file.lower().endswith('.html'): continue
  129. fullpath = os.path.join(outPath, root, file)
  130. try:
  131. f = open(fullpath, 'r')
  132. text = f.read()
  133. f.close()
  134. except:
  135. logging.getLogger().error('\nFail to open file %s\n' % fullpath)
  136. continue
  137. if arch.lower() == 'all':
  138. if text.find('BASE_LIBRARY_JUMP_BUFFER Struct Reference') != -1:
  139. FixPageBASE_LIBRARY_JUMP_BUFFER(fullpath, text)
  140. if text.find('MdePkg/Include/Library/BaseLib.h File Reference') != -1:
  141. FixPageBaseLib(fullpath, text)
  142. if text.find('IA32_IDT_GATE_DESCRIPTOR Union Reference') != -1:
  143. FixPageIA32_IDT_GATE_DESCRIPTOR(fullpath, text)
  144. if text.find('MdePkg/Include/Library/UefiDriverEntryPoint.h File Reference') != -1:
  145. FixPageUefiDriverEntryPoint(fullpath, text)
  146. if text.find('MdePkg/Include/Library/UefiApplicationEntryPoint.h File Reference') != -1:
  147. FixPageUefiApplicationEntryPoint(fullpath, text)
  148. print(' >>> Finish all document fixing up! \n')
  149. def FixPageBaseLib(path, text):
  150. print(' >>> Fixup BaseLib file page at file %s \n' % path)
  151. lines = text.split('\n')
  152. lastBaseJumpIndex = -1
  153. lastIdtGateDescriptor = -1
  154. for index in range(len(lines) - 1, -1, -1):
  155. line = lines[index]
  156. if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;4 </td>':
  157. lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;4&nbsp;[IA32] </td>'
  158. if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;0x10 </td>':
  159. lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;0x10&nbsp;[IPF] </td>'
  160. if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;8 </td>':
  161. lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;9&nbsp;[EBC, x64] </td>'
  162. if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;4') != -1:
  163. lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;4',
  164. 'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;4&nbsp;[IA32]')
  165. if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;0x10') != -1:
  166. lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;0x10',
  167. 'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;0x10&nbsp;[IPF]')
  168. if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;8') != -1:
  169. lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;8',
  170. 'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;8&nbsp;[x64, EBC]')
  171. if line.find('>BASE_LIBRARY_JUMP_BUFFER</a>') != -1:
  172. if lastBaseJumpIndex != -1:
  173. del lines[lastBaseJumpIndex]
  174. lastBaseJumpIndex = index
  175. if line.find('>IA32_IDT_GATE_DESCRIPTOR</a></td>') != -1:
  176. if lastIdtGateDescriptor != -1:
  177. del lines[lastIdtGateDescriptor]
  178. lastIdtGateDescriptor = index
  179. try:
  180. f = open(path, 'w')
  181. f.write('\n'.join(lines))
  182. f.close()
  183. except:
  184. logging.getLogger().error(" <<< Fail to fixup file %s\n" % path)
  185. return
  186. print(" <<< Finish to fixup file %s\n" % path)
  187. def FixPageIA32_IDT_GATE_DESCRIPTOR(path, text):
  188. print(' >>> Fixup structure reference IA32_IDT_GATE_DESCRIPTOR at file %s \n' % path)
  189. lines = text.split('\n')
  190. for index in range(len(lines) - 1, -1, -1):
  191. line = lines[index].strip()
  192. if line.find('struct {</td>') != -1 and lines[index - 2].find('>Uint64</a></td>') != -1:
  193. lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>')
  194. if line.find('struct {</td>') != -1 and lines[index - 1].find('Data Fields') != -1:
  195. lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>')
  196. try:
  197. f = open(path, 'w')
  198. f.write('\n'.join(lines))
  199. f.close()
  200. except:
  201. logging.getLogger().error(" <<< Fail to fixup file %s\n" % path)
  202. return
  203. print(" <<< Finish to fixup file %s\n" % path)
  204. def FixPageBASE_LIBRARY_JUMP_BUFFER(path, text):
  205. print(' >>> Fixup structure reference BASE_LIBRARY_JUMP_BUFFER at file %s \n' % path)
  206. lines = text.split('\n')
  207. bInDetail = True
  208. bNeedRemove = False
  209. for index in range(len(lines) - 1, -1, -1):
  210. line = lines[index]
  211. if line.find('Detailed Description') != -1:
  212. bInDetail = False
  213. if line.startswith('EBC context buffer used by') and lines[index - 1].startswith('x64 context buffer'):
  214. lines[index] = "IA32/IPF/X64/" + line
  215. bNeedRemove = True
  216. if line.startswith("x64 context buffer") or line.startswith('IPF context buffer used by') or \
  217. line.startswith('IA32 context buffer used by'):
  218. if bNeedRemove:
  219. lines.remove(line)
  220. if line.find('>R0</a>') != -1 and not bInDetail:
  221. if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For EBC</h2></td></tr>':
  222. lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For EBC</h2></td></tr>')
  223. if line.find('>Rbx</a>') != -1 and not bInDetail:
  224. if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>':
  225. lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>')
  226. if line.find('>F2</a>') != -1 and not bInDetail:
  227. if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For IPF</h2></td></tr>':
  228. lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IPF</h2></td></tr>')
  229. if line.find('>Ebx</a>') != -1 and not bInDetail:
  230. if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>':
  231. lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>')
  232. try:
  233. f = open(path, 'w')
  234. f.write('\n'.join(lines))
  235. f.close()
  236. except:
  237. logging.getLogger().error(" <<< Fail to fixup file %s" % path)
  238. return
  239. print(" <<< Finish to fixup file %s\n" % path)
  240. def FixPageUefiDriverEntryPoint(path, text):
  241. print(' >>> Fixup file reference MdePkg/Include/Library/UefiDriverEntryPoint.h at file %s \n' % path)
  242. lines = text.split('\n')
  243. bInModuleEntry = False
  244. bInEfiMain = False
  245. ModuleEntryDlCount = 0
  246. ModuleEntryDelStart = 0
  247. ModuleEntryDelEnd = 0
  248. EfiMainDlCount = 0
  249. EfiMainDelStart = 0
  250. EfiMainDelEnd = 0
  251. for index in range(len(lines)):
  252. line = lines[index].strip()
  253. if line.find('EFI_STATUS</a> EFIAPI _ModuleEntryPoint </td>') != -1:
  254. bInModuleEntry = True
  255. if line.find('EFI_STATUS</a> EFIAPI EfiMain </td>') != -1:
  256. bInEfiMain = True
  257. if line.startswith('<p>References <a'):
  258. if bInModuleEntry:
  259. ModuleEntryDelEnd = index - 1
  260. bInModuleEntry = False
  261. elif bInEfiMain:
  262. EfiMainDelEnd = index - 1
  263. bInEfiMain = False
  264. if bInModuleEntry:
  265. if line.startswith('</dl>'):
  266. ModuleEntryDlCount = ModuleEntryDlCount + 1
  267. if ModuleEntryDlCount == 1:
  268. ModuleEntryDelStart = index + 1
  269. if bInEfiMain:
  270. if line.startswith('</dl>'):
  271. EfiMainDlCount = EfiMainDlCount + 1
  272. if EfiMainDlCount == 1:
  273. EfiMainDelStart = index + 1
  274. if EfiMainDelEnd > EfiMainDelStart:
  275. for index in range(EfiMainDelEnd, EfiMainDelStart, -1):
  276. del lines[index]
  277. if ModuleEntryDelEnd > ModuleEntryDelStart:
  278. for index in range(ModuleEntryDelEnd, ModuleEntryDelStart, -1):
  279. del lines[index]
  280. try:
  281. f = open(path, 'w')
  282. f.write('\n'.join(lines))
  283. f.close()
  284. except:
  285. logging.getLogger().error(" <<< Fail to fixup file %s" % path)
  286. return
  287. print(" <<< Finish to fixup file %s\n" % path)
  288. def FixPageUefiApplicationEntryPoint(path, text):
  289. print(' >>> Fixup file reference MdePkg/Include/Library/UefiApplicationEntryPoint.h at file %s \n' % path)
  290. lines = text.split('\n')
  291. bInModuleEntry = False
  292. bInEfiMain = False
  293. ModuleEntryDlCount = 0
  294. ModuleEntryDelStart = 0
  295. ModuleEntryDelEnd = 0
  296. EfiMainDlCount = 0
  297. EfiMainDelStart = 0
  298. EfiMainDelEnd = 0
  299. for index in range(len(lines)):
  300. line = lines[index].strip()
  301. if line.find('EFI_STATUS</a> EFIAPI _ModuleEntryPoint </td>') != -1:
  302. bInModuleEntry = True
  303. if line.find('EFI_STATUS</a> EFIAPI EfiMain </td>') != -1:
  304. bInEfiMain = True
  305. if line.startswith('<p>References <a'):
  306. if bInModuleEntry:
  307. ModuleEntryDelEnd = index - 1
  308. bInModuleEntry = False
  309. elif bInEfiMain:
  310. EfiMainDelEnd = index - 1
  311. bInEfiMain = False
  312. if bInModuleEntry:
  313. if line.startswith('</dl>'):
  314. ModuleEntryDlCount = ModuleEntryDlCount + 1
  315. if ModuleEntryDlCount == 1:
  316. ModuleEntryDelStart = index + 1
  317. if bInEfiMain:
  318. if line.startswith('</dl>'):
  319. EfiMainDlCount = EfiMainDlCount + 1
  320. if EfiMainDlCount == 1:
  321. EfiMainDelStart = index + 1
  322. if EfiMainDelEnd > EfiMainDelStart:
  323. for index in range(EfiMainDelEnd, EfiMainDelStart, -1):
  324. del lines[index]
  325. if ModuleEntryDelEnd > ModuleEntryDelStart:
  326. for index in range(ModuleEntryDelEnd, ModuleEntryDelStart, -1):
  327. del lines[index]
  328. try:
  329. f = open(path, 'w')
  330. f.write('\n'.join(lines))
  331. f.close()
  332. except:
  333. logging.getLogger().error(" <<< Fail to fixup file %s" % path)
  334. return
  335. print(" <<< Finish to fixup file %s\n" % path)
  336. if __name__ == '__main__':
  337. wspath, pkgpath, doxpath, outpath, archtag, docmode, isinc, hwpath = parseCmdArgs()
  338. # configure logging system
  339. logfilepath = os.path.join(outpath, 'log.txt')
  340. logging.basicConfig(format='%(levelname)-8s %(message)s', level=logging.DEBUG)
  341. # create package model object firstly
  342. pkgObj = createPackageObject(wspath, pkgpath)
  343. if pkgObj is None:
  344. sys.exit(-1)
  345. # create doxygen action model
  346. arch = None
  347. tooltag = None
  348. if archtag.lower() != 'all':
  349. arch = archtag.split('_')[0]
  350. tooltag = archtag.split('_')[1]
  351. else:
  352. arch = 'all'
  353. tooltag = 'all'
  354. # preprocess package and call doxygen
  355. try:
  356. action = doxygengen.PackageDocumentAction(doxpath,
  357. hwpath,
  358. outpath,
  359. pkgObj,
  360. docmode,
  361. callbackLogMessage,
  362. arch,
  363. tooltag,
  364. isinc,
  365. True)
  366. action.RegisterCallbackDoxygenProcess(callbackCreateDoxygenProcess)
  367. action.Generate()
  368. except:
  369. message = traceback.format_exception(*sys.exc_info())
  370. logging.getLogger().error('Fail to create doxygen action! \n%s' % ''.join(message))
  371. sys.exit(-1)
  372. DocumentFixup(outpath, arch)
  373. # generate CHM is necessary
  374. if docmode.lower() == 'chm':
  375. indexpath = os.path.join(outpath, 'html', 'index.hhp')
  376. if sys.platform == 'win32':
  377. cmd = '"%s" %s' % (hwpath, indexpath)
  378. else:
  379. cmd = '%s %s' % (hwpath, indexpath)
  380. subprocess.call(cmd)
  381. print('\nFinish to generate package document! Please open %s for review' % os.path.join(outpath, 'html', 'index.chm'))
  382. else:
  383. print('\nFinish to generate package document! Please open %s for review' % os.path.join(outpath, 'html', 'index.html'))