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 ext_in = [ '.py', '.c', '.h', '.java', '.pb.cc' ] 73 74 def runnable_status(self): 75 ''' 76 self.pars are populated in runnable_status - because this function is being 77 run *before* both self.pars "consumers" - scan() and run() 78 79 set output_dir (node) for the output 80 ''' 81 82 for x in self.run_after: 83 if not x.hasrun: 84 return Task.ASK_LATER 85 86 if not getattr(self, 'pars', None): 87 txt = self.inputs[0].read() 88 self.pars = parse_doxy(txt) 89 90 # Override with any parameters passed to the task generator 91 if getattr(self.generator, 'pars', None): 92 for k, v in self.generator.pars.items(): 93 self.pars[k] = v 94 95 if self.pars.get('OUTPUT_DIRECTORY'): 96 # Use the path parsed from the Doxyfile as an absolute path 97 output_node = self.inputs[0].parent.get_bld().make_node(self.pars['OUTPUT_DIRECTORY']) 98 else: 99 # If no OUTPUT_PATH was specified in the Doxyfile, build path from the Doxyfile name + '.doxy' 100 output_node = self.inputs[0].parent.get_bld().make_node(self.inputs[0].name + '.doxy') 101 output_node.mkdir() 102 self.pars['OUTPUT_DIRECTORY'] = output_node.abspath() 103 104 self.doxy_inputs = getattr(self, 'doxy_inputs', []) 105 if not self.pars.get('INPUT'): 106 self.doxy_inputs.append(self.inputs[0].parent) 107 else: 108 for i in self.pars.get('INPUT').split(): 109 if os.path.isabs(i): 110 node = self.generator.bld.root.find_node(i) 111 else: 112 node = self.inputs[0].parent.find_node(i) 113 if not node: 114 self.generator.bld.fatal('Could not find the doxygen input %r' % i) 115 self.doxy_inputs.append(node) 116 117 if not getattr(self, 'output_dir', None): 118 bld = self.generator.bld 119 # Output path is always an absolute path as it was transformed above. 120 self.output_dir = bld.root.find_dir(self.pars['OUTPUT_DIRECTORY']) 121 122 self.signature() 123 ret = Task.Task.runnable_status(self) 124 if ret == Task.SKIP_ME: 125 # in case the files were removed 126 self.add_install() 127 return ret 128 129 def scan(self): 130 exclude_patterns = self.pars.get('EXCLUDE_PATTERNS','').split() 131 exclude_patterns = [pattern.replace('*/', '**/') for pattern in exclude_patterns] 132 file_patterns = self.pars.get('FILE_PATTERNS','').split() 133 if not file_patterns: 134 file_patterns = DOXY_FILE_PATTERNS.split() 135 if self.pars.get('RECURSIVE') == 'YES': 136 file_patterns = ["**/%s" % pattern for pattern in file_patterns] 137 nodes = [] 138 names = [] 139 for node in self.doxy_inputs: 140 if os.path.isdir(node.abspath()): 141 for m in node.ant_glob(incl=file_patterns, excl=exclude_patterns): 142 nodes.append(m) 143 else: 144 nodes.append(node) 145 return (nodes, names) 146 147 def run(self): 148 dct = self.pars.copy() 149 code = '\n'.join(['%s = %s' % (x, dct[x]) for x in self.pars]) 150 code = code.encode() # for python 3 151 #fmt = DOXY_STR % (self.inputs[0].parent.abspath()) 152 cmd = Utils.subst_vars(DOXY_STR, self.env) 153 env = self.env.env or None 154 proc = Utils.subprocess.Popen(cmd, shell=True, stdin=Utils.subprocess.PIPE, env=env, cwd=self.inputs[0].parent.abspath()) 155 proc.communicate(code) 156 return proc.returncode 157 158 def post_run(self): 159 nodes = self.output_dir.ant_glob('**/*', quiet=True) 160 for x in nodes: 161 self.generator.bld.node_sigs[x] = self.uid() 162 self.add_install() 163 return Task.Task.post_run(self) 164 165 def add_install(self): 166 nodes = self.output_dir.ant_glob('**/*', quiet=True) 167 self.outputs += nodes 168 if getattr(self.generator, 'install_path', None): 169 if not getattr(self.generator, 'doxy_tar', None): 170 self.generator.add_install_files(install_to=self.generator.install_path, 171 install_from=self.outputs, 172 postpone=False, 173 cwd=self.output_dir, 174 relative_trick=True) 175 176class tar(Task.Task): 177 "quick tar creation" 178 run_str = '${TAR} ${TAROPTS} ${TGT} ${SRC}' 179 color = 'RED' 180 after = ['doxygen'] 181 def runnable_status(self): 182 for x in getattr(self, 'input_tasks', []): 183 if not x.hasrun: 184 return Task.ASK_LATER 185 186 if not getattr(self, 'tar_done_adding', None): 187 # execute this only once 188 self.tar_done_adding = True 189 for x in getattr(self, 'input_tasks', []): 190 self.set_inputs(x.outputs) 191 if not self.inputs: 192 return Task.SKIP_ME 193 return Task.Task.runnable_status(self) 194 195 def __str__(self): 196 tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs]) 197 return '%s: %s\n' % (self.__class__.__name__, tgt_str) 198 199@feature('doxygen') 200def process_doxy(self): 201 if not getattr(self, 'doxyfile', None): 202 self.bld.fatal('no doxyfile variable specified??') 203 204 node = self.doxyfile 205 if not isinstance(node, Node.Node): 206 node = self.path.find_resource(node) 207 if not node: 208 self.bld.fatal('doxygen file %s not found' % self.doxyfile) 209 210 # the task instance 211 dsk = self.create_task('doxygen', node, always_run=getattr(self, 'always', False)) 212 213 if getattr(self, 'doxy_tar', None): 214 tsk = self.create_task('tar', always_run=getattr(self, 'always', False)) 215 tsk.input_tasks = [dsk] 216 tsk.set_outputs(self.path.find_or_declare(self.doxy_tar)) 217 if self.doxy_tar.endswith('bz2'): 218 tsk.env['TAROPTS'] = ['cjf'] 219 elif self.doxy_tar.endswith('gz'): 220 tsk.env['TAROPTS'] = ['czf'] 221 else: 222 tsk.env['TAROPTS'] = ['cf'] 223 if getattr(self, 'install_path', None): 224 self.add_install_files(install_to=self.install_path, install_from=tsk.outputs) 225 226def configure(conf): 227 ''' 228 Check if doxygen and tar commands are present in the system 229 230 If the commands are present, then conf.env.DOXYGEN and conf.env.TAR 231 variables will be set. Detection can be controlled by setting DOXYGEN and 232 TAR environmental variables. 233 ''' 234 235 conf.find_program('doxygen', var='DOXYGEN', mandatory=False) 236 conf.find_program('tar', var='TAR', mandatory=False) 237