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