1 # Astxx, the Asterisk C++ API and Utility Library.
2 # Copyright (C) 2005, 2006 Matthew A. Nicholson
3 # Copyright (C) 2006 Tim Blechmann
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License version 2.1 as published by the Free Software Foundation.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # I have been fighting 4 problems in this implementation:
19 # - A Directory target will *not* call any source scanners
20 # - A Directory target will interpret the directory contents as
21 # sources not targets. This means, that if a command creates that
22 # directory plus contents, the target will never be up-to-date
23 # (since the directory contents will change with every call of
25 # - Theres a bug in SCons which will produce an error message for
26 # directory targets if dir.sources is not set explicitly
27 # - the first argument to env.Clean() must be the command line target,
28 # with which the scons was invoked. This does not help to add
29 # aditional files or directories to be cleaned if you don't know
30 # that target (it's not possible to say 'if you clean this file,
31 # also clean that one' hich is, what I had expected env.Clean to
34 # Together, these problems have produced several difficulties. I have
36 # - Adding an (empty) stamp file as a (file) target. This target will
37 # cause source scanners to be invoked
38 # - Adding the documentation directory as a target (so it will be
39 # cleaned up which env.Clean doesn't help me to do), but *only* if
40 # scons is called to with the -c option
41 # - Setting dir.sources to the known source-list to silence the error
42 # message whenever a directory is added as a target
44 # You will find all this in the DoxyEmitter
46 import os, sys, traceback
49 from fnmatch import fnmatch
51 def DoxyfileParse(file):
52 data = DoxyfileParse_(file,{})
53 for (k,v) in data.items():
54 if not v : del data[k]
55 elif k in ("INPUT", "FILE_PATTERNS", "EXCLUDE_PATTERNS", "@INCLUDE", "TAGFILES") : continue
56 elif len(v)==1 : data[k] = v[0]
59 def DoxyfileParse_(file, data):
61 Parse a Doxygen source file and return a dictionary of all the values.
62 Values will be strings and lists of strings.
65 dir = os.path.dirname(file)
68 lex = shlex.shlex(instream=open(file), posix=True)
69 lex.wordchars += "*+./-:@~"
70 lex.whitespace = lex.whitespace.replace("\n", "")
74 token = lex.get_token()
81 def append_data(data, key, new_data, token):
82 if new_data or len(data[key]) == 0:
83 data[key].append(token)
85 data[key][-1] += token
97 if token=="+=" or (token=="=" and key=="@INCLUDE"):
98 if not data.has_key(key):
103 append_data(data, key, new_data, token)
106 inc = os.path.join(dir,data['@INCLUDE'][-1])
107 if os.path.exists(inc) :
108 DoxyfileParse_(inc,data)
111 token = lex.get_token()
113 if last_token=='\\' and token!='\n':
115 append_data(data, key, new_data, '\\')
121 def DoxySourceScan(node, env, path):
123 Doxygen Doxyfile source scanner. This should scan the Doxygen file and add
124 any files used to generate docs to the list of source files.
127 '@INCLUDE', 'HTML_HEADER', 'HTML_FOOTER', 'TAGFILES'
130 default_file_patterns = (
131 '*.c', '*.cc', '*.cxx', '*.cpp', '*.c++', '*.java', '*.ii', '*.ixx',
132 '*.ipp', '*.i++', '*.inl', '*.h', '*.hh ', '*.hxx', '*.hpp', '*.h++',
133 '*.idl', '*.odl', '*.cs', '*.php', '*.php3', '*.inc', '*.m', '*.mm',
137 default_exclude_patterns = (
142 basedir = node.dir.abspath
143 data = DoxyfileParse(node.abspath)
144 recursive = data.get("RECURSIVE", "NO").upper()=="YES"
145 file_patterns = data.get("FILE_PATTERNS", default_file_patterns)
146 exclude_patterns = data.get("EXCLUDE_PATTERNS", default_exclude_patterns)
148 for i in data.get("INPUT", [ "." ]):
149 input = os.path.normpath(os.path.join(basedir,i))
150 if os.path.isfile(input):
151 sources.append(input)
152 elif os.path.isdir(input):
153 if recursive : entries = os.walk(input)
154 else : entries = [ (input, [], os.listdir(input)) ]
155 for root, dirs, files in entries:
157 filename = os.path.normpath(os.path.join(root, f))
158 if ( reduce(lambda x, y: x or fnmatch(f, y),
159 file_patterns, False)
160 and not reduce(lambda x, y: x or fnmatch(f, y),
161 exclude_patterns, False) ):
162 sources.append(filename)
164 for key in dep_add_keys:
165 if data.has_key(key):
167 if type(elt) is type ("") : elt = [ elt ]
168 sources.extend([ os.path.normpath(os.path.join(basedir,f))
171 sources = map( lambda path: env.File(path), sources )
174 def DoxySourceScanCheck(node, env):
175 """Check if we should scan this file"""
176 return os.path.isfile(node.path)
178 def DoxyEmitter(source, target, env):
179 """Doxygen Doxyfile emitter"""
180 # possible output formats and their default values and output locations
182 "HTML" : ("YES", "html"),
183 "LATEX" : ("YES", "latex"),
184 "RTF" : ("NO", "rtf"),
185 "MAN" : ("YES", "man"),
186 "XML" : ("NO", "xml"),
189 data = DoxyfileParse(source[0].abspath)
192 if data.has_key("OUTPUT_DIRECTORY"):
193 out_dir = data["OUTPUT_DIRECTORY"]
194 dir = env.Dir( os.path.join(source[0].dir.abspath, out_dir) )
196 if env.GetOption('clean'): targets.append(dir)
200 # add our output locations
201 for (k, v) in output_formats.iteritems():
202 if data.get("GENERATE_" + k, v[0]).upper() == "YES":
203 dir = env.Dir( os.path.join(source[0].dir.abspath, out_dir, data.get(k + "_OUTPUT", v[1])) )
205 node = env.File( os.path.join(dir.abspath, k.lower()+".stamp" ) )
207 if env.GetOption('clean'): targets.append(dir)
209 if data.has_key("GENERATE_TAGFILE"):
210 targets.append(env.File( os.path.join(source[0].dir.abspath, data["GENERATE_TAGFILE"]) ))
212 # don't clobber targets
216 return (targets, source)
218 def doxyNodeHtmlDir(node):
219 if not node.sources : return None
220 data = DoxyfileParse(node.sources[0].abspath)
221 if data.get("GENERATE_HTML",'YES').upper() != 'YES' : return None
222 return os.path.normpath(os.path.join( node.sources[0].dir.abspath,
223 data.get("OUTPUT_DIRECTORY","."),
224 data.get("HTML_OUTPUT","html") ))
226 def relpath(source, target):
227 source = os.path.normpath(source)
228 target = os.path.normpath(target)
229 prefix = os.path.dirname(os.path.commonprefix((source,target)))
230 prefix_len = prefix and len(prefix.split(os.sep)) or 0
231 source_elts = source.split(os.sep)
232 target_elts = target.split(os.sep)
233 if source_elts[0] == '..' or target_elts[0] == '..':
234 raise ValueError, "invalid relapth args"
235 return os.path.join(*([".."] * (len(source_elts) - prefix_len) +
236 target_elts[prefix_len:]))
238 def DoxyGenerator(source, target, env, for_signature):
240 data = DoxyfileParse(source[0].abspath)
242 actions = [ env.Action("cd ${SOURCE.dir} && ${DOXYGEN} ${SOURCE.file}") ]
244 # This will add automatic 'installdox' calls.
246 # For every referenced tagfile, the generator first checks for the
247 # existence of a construction variable '<name>_DOXY_URL' where
248 # '<name>' is the uppercased name of the tagfile sans extension
249 # (e.g. 'Utils.tag' -> 'UTILS_DOXY_URL'). If this variable exists,
250 # it must contain the url or path to the installed documentation
251 # corresponding to the tag file.
253 # Is the variable is not found and if a referenced tag file is a
254 # target within this same build, the generator will parse the
255 # 'Doxyfile' from which the tag file is built. It will
256 # automatically create the html directory from the information in
259 # If for any referenced tagfile no url can be found, 'installdox'
260 # will *not* be called and a warning about the missing url is
263 if data.get('GENERATE_HTML','YES').upper() == "YES":
264 output_dir = os.path.normpath(os.path.join( source[0].dir.abspath,
265 data.get("OUTPUT_DIRECTORY","."),
266 data.get("HTML_OUTPUT","html") ))
268 for tagfile in data.get('TAGFILES',[]):
269 url = env.get(os.path.splitext(os.path.basename(tagfile))[0].upper()+"_DOXY_URL", None)
271 url = doxyNodeHtmlDir(
272 env.File(os.path.normpath(os.path.join(str(source[0].dir), tagfile))))
273 if url : url = relpath(output_dir, url)
275 print "WARNING:",source[0].abspath, ": missing tagfile url for", tagfile
277 if args is not None and url:
278 args.append("-l %s@%s" % ( os.path.basename(tagfile), url ))
280 actions.append(env.Action('cd %s && ./installdox %s' % (output_dir, " ".join(args))))
282 actions.append(env.Action([ "touch $TARGETS" ]))
288 Add builders and construction variables for the
289 Doxygen tool. This is currently for Doxygen 1.4.6.
291 doxyfile_scanner = env.Scanner(
294 scan_check = DoxySourceScanCheck,
297 doxyfile_builder = env.Builder(
298 # scons 0.96.93 hang on the next line but I don't know hot to FIX the problem
299 generator = DoxyGenerator,
300 emitter = DoxyEmitter,
301 target_factory = env.fs.Entry,
302 single_source = True,
303 source_scanner = doxyfile_scanner
306 env.Append(BUILDERS = {
307 'Doxygen': doxyfile_builder,
316 Make sure doxygen exists.
318 return env.Detect("doxygen")