doxygen.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. #! /usr/bin/env python
  2. # encoding: UTF-8
  3. # Thomas Nagy 2008-2010 (ita)
  4. """
  5. Doxygen support
  6. Variables passed to bld():
  7. * doxyfile -- the Doxyfile to use
  8. * doxy_tar -- destination archive for generated documentation (if desired)
  9. * install_path -- where to install the documentation
  10. * pars -- dictionary overriding doxygen configuration settings
  11. When using this tool, the wscript will look like:
  12. def options(opt):
  13. opt.load('doxygen')
  14. def configure(conf):
  15. conf.load('doxygen')
  16. # check conf.env.DOXYGEN, if it is mandatory
  17. def build(bld):
  18. if bld.env.DOXYGEN:
  19. bld(features="doxygen", doxyfile='Doxyfile', ...)
  20. """
  21. import os, os.path, re
  22. from waflib import Task, Utils, Node
  23. from waflib.TaskGen import feature
  24. DOXY_STR = '"${DOXYGEN}" - '
  25. DOXY_FMTS = 'html latex man rft xml'.split()
  26. DOXY_FILE_PATTERNS = '*.' + ' *.'.join('''
  27. c cc cxx cpp c++ java ii ixx ipp i++ inl h hh hxx hpp h++ idl odl cs php php3
  28. inc m mm py f90c cc cxx cpp c++ java ii ixx ipp i++ inl h hh hxx
  29. '''.split())
  30. re_rl = re.compile('\\\\\r*\n', re.MULTILINE)
  31. re_nl = re.compile('\r*\n', re.M)
  32. def parse_doxy(txt):
  33. tbl = {}
  34. txt = re_rl.sub('', txt)
  35. lines = re_nl.split(txt)
  36. for x in lines:
  37. x = x.strip()
  38. if not x or x.startswith('#') or x.find('=') < 0:
  39. continue
  40. if x.find('+=') >= 0:
  41. tmp = x.split('+=')
  42. key = tmp[0].strip()
  43. if key in tbl:
  44. tbl[key] += ' ' + '+='.join(tmp[1:]).strip()
  45. else:
  46. tbl[key] = '+='.join(tmp[1:]).strip()
  47. else:
  48. tmp = x.split('=')
  49. tbl[tmp[0].strip()] = '='.join(tmp[1:]).strip()
  50. return tbl
  51. class doxygen(Task.Task):
  52. vars = ['DOXYGEN', 'DOXYFLAGS']
  53. color = 'BLUE'
  54. def runnable_status(self):
  55. '''
  56. self.pars are populated in runnable_status - because this function is being
  57. run *before* both self.pars "consumers" - scan() and run()
  58. set output_dir (node) for the output
  59. '''
  60. for x in self.run_after:
  61. if not x.hasrun:
  62. return Task.ASK_LATER
  63. if not getattr(self, 'pars', None):
  64. txt = self.inputs[0].read()
  65. self.pars = parse_doxy(txt)
  66. if self.pars.get('OUTPUT_DIRECTORY'):
  67. # Use the path parsed from the Doxyfile as an absolute path
  68. output_node = self.inputs[0].parent.get_bld().make_node(self.pars['OUTPUT_DIRECTORY'])
  69. else:
  70. # If no OUTPUT_PATH was specified in the Doxyfile, build path from the Doxyfile name + '.doxy'
  71. output_node = self.inputs[0].parent.get_bld().make_node(self.inputs[0].name + '.doxy')
  72. output_node.mkdir()
  73. self.pars['OUTPUT_DIRECTORY'] = output_node.abspath()
  74. # Override with any parameters passed to the task generator
  75. if getattr(self.generator, 'pars', None):
  76. for k, v in self.generator.pars.items():
  77. self.pars[k] = v
  78. self.doxy_inputs = getattr(self, 'doxy_inputs', [])
  79. if not self.pars.get('INPUT'):
  80. self.doxy_inputs.append(self.inputs[0].parent)
  81. else:
  82. for i in self.pars.get('INPUT').split():
  83. if os.path.isabs(i):
  84. node = self.generator.bld.root.find_node(i)
  85. else:
  86. node = self.inputs[0].parent.find_node(i)
  87. if not node:
  88. self.generator.bld.fatal('Could not find the doxygen input %r' % i)
  89. self.doxy_inputs.append(node)
  90. if not getattr(self, 'output_dir', None):
  91. bld = self.generator.bld
  92. # Output path is always an absolute path as it was transformed above.
  93. self.output_dir = bld.root.find_dir(self.pars['OUTPUT_DIRECTORY'])
  94. self.signature()
  95. ret = Task.Task.runnable_status(self)
  96. if ret == Task.SKIP_ME:
  97. # in case the files were removed
  98. self.add_install()
  99. return ret
  100. def scan(self):
  101. exclude_patterns = self.pars.get('EXCLUDE_PATTERNS','').split()
  102. exclude_patterns = [pattern.replace('*/', '**/') for pattern in exclude_patterns]
  103. file_patterns = self.pars.get('FILE_PATTERNS','').split()
  104. if not file_patterns:
  105. file_patterns = DOXY_FILE_PATTERNS.split()
  106. if self.pars.get('RECURSIVE') == 'YES':
  107. file_patterns = ["**/%s" % pattern for pattern in file_patterns]
  108. nodes = []
  109. names = []
  110. for node in self.doxy_inputs:
  111. if os.path.isdir(node.abspath()):
  112. for m in node.ant_glob(incl=file_patterns, excl=exclude_patterns):
  113. nodes.append(m)
  114. else:
  115. nodes.append(node)
  116. return (nodes, names)
  117. def run(self):
  118. dct = self.pars.copy()
  119. code = '\n'.join(['%s = %s' % (x, dct[x]) for x in self.pars])
  120. code = code.encode() # for python 3
  121. #fmt = DOXY_STR % (self.inputs[0].parent.abspath())
  122. cmd = Utils.subst_vars(DOXY_STR, self.env)
  123. env = self.env.env or None
  124. proc = Utils.subprocess.Popen(cmd, shell=True, stdin=Utils.subprocess.PIPE, env=env, cwd=self.inputs[0].parent.abspath())
  125. proc.communicate(code)
  126. return proc.returncode
  127. def post_run(self):
  128. nodes = self.output_dir.ant_glob('**/*', quiet=True)
  129. for x in nodes:
  130. self.generator.bld.node_sigs[x] = self.uid()
  131. self.add_install()
  132. return Task.Task.post_run(self)
  133. def add_install(self):
  134. nodes = self.output_dir.ant_glob('**/*', quiet=True)
  135. self.outputs += nodes
  136. if getattr(self.generator, 'install_path', None):
  137. if not getattr(self.generator, 'doxy_tar', None):
  138. self.generator.add_install_files(install_to=self.generator.install_path,
  139. install_from=self.outputs,
  140. postpone=False,
  141. cwd=self.output_dir,
  142. relative_trick=True)
  143. class tar(Task.Task):
  144. "quick tar creation"
  145. run_str = '${TAR} ${TAROPTS} ${TGT} ${SRC}'
  146. color = 'RED'
  147. after = ['doxygen']
  148. def runnable_status(self):
  149. for x in getattr(self, 'input_tasks', []):
  150. if not x.hasrun:
  151. return Task.ASK_LATER
  152. if not getattr(self, 'tar_done_adding', None):
  153. # execute this only once
  154. self.tar_done_adding = True
  155. for x in getattr(self, 'input_tasks', []):
  156. self.set_inputs(x.outputs)
  157. if not self.inputs:
  158. return Task.SKIP_ME
  159. return Task.Task.runnable_status(self)
  160. def __str__(self):
  161. tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
  162. return '%s: %s\n' % (self.__class__.__name__, tgt_str)
  163. @feature('doxygen')
  164. def process_doxy(self):
  165. if not getattr(self, 'doxyfile', None):
  166. self.bld.fatal('no doxyfile variable specified??')
  167. node = self.doxyfile
  168. if not isinstance(node, Node.Node):
  169. node = self.path.find_resource(node)
  170. if not node:
  171. self.bld.fatal('doxygen file %s not found' % self.doxyfile)
  172. # the task instance
  173. dsk = self.create_task('doxygen', node)
  174. if getattr(self, 'doxy_tar', None):
  175. tsk = self.create_task('tar')
  176. tsk.input_tasks = [dsk]
  177. tsk.set_outputs(self.path.find_or_declare(self.doxy_tar))
  178. if self.doxy_tar.endswith('bz2'):
  179. tsk.env['TAROPTS'] = ['cjf']
  180. elif self.doxy_tar.endswith('gz'):
  181. tsk.env['TAROPTS'] = ['czf']
  182. else:
  183. tsk.env['TAROPTS'] = ['cf']
  184. if getattr(self, 'install_path', None):
  185. self.add_install_files(install_to=self.install_path, install_from=tsk.outputs)
  186. def configure(conf):
  187. '''
  188. Check if doxygen and tar commands are present in the system
  189. If the commands are present, then conf.env.DOXYGEN and conf.env.TAR
  190. variables will be set. Detection can be controlled by setting DOXYGEN and
  191. TAR environmental variables.
  192. '''
  193. conf.find_program('doxygen', var='DOXYGEN', mandatory=False)
  194. conf.find_program('tar', var='TAR', mandatory=False)