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 import os, sys, traceback
21 from fnmatch import fnmatch
23 def DoxyfileParse(file_contents, dir, data = None):
25 Parse a Doxygen source file and return a dictionary of all the values.
26 Values will be strings and lists of strings.
29 if data is None : data = {}
32 lex = shlex.shlex(instream = file_contents, posix = True)
33 lex.wordchars += "*+./-:"
34 lex.whitespace = lex.whitespace.replace("\n", "")
38 token = lex.get_token()
45 def append_data(data, key, new_data, token):
46 if new_data or len(data[key]) == 0:
47 data[key].append(token)
49 data[key][-1] += token
53 if last_token not in ['\\']:
58 if key == '@' : key += token
60 if token != '@' : key_token = False
62 if token == "+=" or (token == "=" and key == "@INCLUDE"):
63 if not data.has_key(key):
68 append_data( data, key, new_data, token )
71 inc = os.path.join(dir,data['@INCLUDE'][-1])
72 if os.path.exists(inc) :
73 DoxyfileParse(open(inc).read(),dir,data)
76 token = lex.get_token()
78 if last_token == '\\' and token != '\n':
80 append_data( data, key, new_data, '\\' )
82 # compress lists of len 1 into single strings
83 for (k, v) in data.items():
87 # items in the following list will be kept as lists and not converted to strings
88 if k in ["INPUT", "FILE_PATTERNS", "EXCLUDE_PATTERNS", "@INCLUDE", "TAGFILES"]:
99 def DoxySourceScan(node, env, path):
101 Doxygen Doxyfile source scanner. This should scan the Doxygen file and add
102 any files used to generate docs to the list of source files.
105 '@INCLUDE', 'HTML_HEADER', 'HTML_FOOTER', 'TAGFILES'
108 default_file_patterns = [
109 '*.c', '*.cc', '*.cxx', '*.cpp', '*.c++', '*.java', '*.ii', '*.ixx',
110 '*.ipp', '*.i++', '*.inl', '*.h', '*.hh ', '*.hxx', '*.hpp', '*.h++',
111 '*.idl', '*.odl', '*.cs', '*.php', '*.php3', '*.inc', '*.m', '*.mm',
115 default_exclude_patterns = [
120 basedir = str(node.dir)
121 data = DoxyfileParse(node.get_contents(), basedir)
122 recursive = ( data.get("RECURSIVE", "NO") == "YES" )
123 file_patterns = data.get("FILE_PATTERNS", default_file_patterns)
124 exclude_patterns = data.get("EXCLUDE_PATTERNS", default_exclude_patterns)
126 for i in data.get("INPUT", [ "." ]):
127 input = os.path.normpath(os.path.join(basedir,i))
128 if os.path.isfile(input):
129 sources.append(input)
130 elif os.path.isdir(input):
131 if recursive : entries = os.walk(input)
132 else : entries = [ (input, [], os.listdir(input)) ]
133 for root, dirs, files in entries:
135 filename = os.path.normpath(os.path.join(root, f))
136 if ( reduce(lambda x, y: x or fnmatch(filename, y),
137 file_patterns, False)
138 and not reduce(lambda x, y: x or fnmatch(filename, y),
139 exclude_patterns, False) ):
140 sources.append(filename)
142 for key in dep_add_keys:
143 if data.has_key(key):
145 if type(elt) is type ("") : elt = [ elt ]
146 sources.extend([ os.path.normpath(os.path.join(basedir,f))
149 sources = map( lambda path: env.File(path), sources )
152 def DoxySourceScanCheck(node, env):
153 """Check if we should scan this file"""
154 return os.path.isfile(node.path)
156 def DoxyEmitter(source, target, env):
157 """Doxygen Doxyfile emitter"""
158 # possible output formats and their default values and output locations
160 "HTML": ("YES", "html"),
161 "LATEX": ("YES", "latex"),
162 "RTF": ("NO", "rtf"),
163 "MAN": ("YES", "man"),
164 "XML": ("NO", "xml"),
167 data = DoxyfileParse(source[0].get_contents(), str(source[0].dir))
170 out_dir = data.get("OUTPUT_DIRECTORY", ".")
172 # add our output locations
173 for (k, v) in output_formats.items():
174 if data.get("GENERATE_" + k, v[0]) == "YES":
175 # Grmpf ... need to use a File object here. The problem is, that
176 # Dir.scan() is implemented to just return the directory entries
177 # and does *not* invoke the source-file scanners .. ARGH !!
178 dir = env.Dir( os.path.join(str(source[0].dir), out_dir, data.get(k + "_OUTPUT", v[1])) )
179 node = env.File( os.path.join(str(dir), ".stamp" ) )
181 targets.append( node )
183 if data.has_key("GENERATE_TAGFILE"):
184 targets.append(env.File( os.path.join(str(source[0].dir), data["GENERATE_TAGFILE"]) ))
186 # don't clobber targets
190 return (targets, source)
192 def doxyNodeHtmlDir(node):
193 if not node.sources : return None
194 data = DoxyfileParse(node.sources[0].get_contents(), str(node.sources[0].dir))
195 if data.get("GENERATE_HTML",'YES') != 'YES' : return None
196 return os.path.normpath(os.path.join( str(node.sources[0].dir),
197 data.get("OUTPUT_DIRECTORY","."),
198 data.get("HTML_OUTPUT","html") ))
200 def relpath(source, target):
201 source = os.path.normpath(source)
202 target = os.path.normpath(target)
203 prefix = os.path.dirname(os.path.commonprefix((source,target)))
204 prefix_len = prefix and len(prefix.split(os.sep)) or 0
205 source_elts = source.split(os.sep)
206 target_elts = target.split(os.sep)
207 if source_elts[0] == '..' or target_elts[0] == '..':
208 raise ValueError, "invalid relapth args"
209 return os.path.join(*([".."] * (len(source_elts) - prefix_len) +
210 target_elts[prefix_len:]))
212 def DoxyGenerator(source, target, env, for_signature):
214 data = DoxyfileParse(source[0].get_contents(), str(source[0].dir))
216 actions = [ env.Action("cd ${SOURCE.dir} && ${DOXYGEN} ${SOURCE.file}") ]
218 # This will add automatic 'installdox' calls.
220 # For every referenced tagfile, the generator first checks for the
221 # existence of a construction variable '<name>_DOXY_URL' where
222 # '<name>' is the uppercased name of the tagfile sans extension
223 # (e.g. 'Utils.tag' -> 'UTILS_DOXY_URL'). If this variable exists,
224 # it must contain the url or path to the installed documentation
225 # corresponding to the tag file.
227 # Is the variable is not found and if a referenced tag file is a
228 # target within this same build, the generator will parse the
229 # 'Doxyfile' from which the tag file is built. It will
230 # automatically create the html directory from the information in
233 # If for any referenced tagfile no url can be found, 'installdox'
234 # will *not* be called and a warning about the missing url is
237 if data.get('GENERATE_HTML','YES') == "YES":
238 output_dir = os.path.normpath(os.path.join( str(source[0].dir),
239 data.get("OUTPUT_DIRECTORY","."),
240 data.get("HTML_OUTPUT","html") ))
242 for tagfile in data.get('TAGFILES',[]):
243 url = env.get(os.path.splitext(os.path.basename(tagfile))[0].upper()+"_DOXY_URL", None)
245 url = doxyNodeHtmlDir(
246 env.File(os.path.normpath(os.path.join( str(source[0].dir), tagfile ))))
247 if url : url = relpath(output_dir, url)
249 print "WARNING:",str(node.sources[0]),": missing tagfile url for",tagfile
251 if args is not None and url:
252 args.append("-l %s@%s" % ( os.path.basename(tagfile), url ))
254 actions.append(env.Action('cd %s && ./installdox %s' % (output_dir, " ".join(args))))
256 actions.append(env.Action([ "touch $TARGETS" ]))
262 Add builders and construction variables for the
263 Doxygen tool. This is currently for Doxygen 1.4.6.
265 doxyfile_scanner = env.Scanner(
268 scan_check = DoxySourceScanCheck,
271 doxyfile_builder = env.Builder(
272 # scons 0.96.93 hang on the next line but I don't know hot to FIX the problem
273 generator = DoxyGenerator,
274 emitter = DoxyEmitter,
275 target_factory = env.fs.Entry,
276 single_source = True,
277 source_scanner = doxyfile_scanner
280 env.Append(BUILDERS = {
281 'Doxygen': doxyfile_builder,
290 Make sure doxygen exists.
292 return env.Detect("doxygen")