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