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