blob: 7b8dee42b3ff3b8742058acaad1149381d0dd03f [file] [log] [blame]
Christopher Dunnf9864232007-06-14 21:01:26 +00001# Big issue:
2# emitter depends on doxyfile which is generated from doxyfile.in.
3# build fails after cleaning and relaunching the build.
4
5import os
6import os.path
7import glob
8from fnmatch import fnmatch
9
10def DoxyfileParse(file_contents):
11 """
12 Parse a Doxygen source file and return a dictionary of all the values.
13 Values will be strings and lists of strings.
14 """
15 data = {}
16
17 import shlex
18 lex = shlex.shlex(instream = file_contents, posix = True)
19 lex.wordchars += "*+./-:"
20 lex.whitespace = lex.whitespace.replace("\n", "")
21 lex.escape = ""
22
23 lineno = lex.lineno
24 last_backslash_lineno = lineno
25 token = lex.get_token()
26 key = token # the first token should be a key
27 last_token = ""
28 key_token = False
29 next_key = False
30 new_data = True
31
32 def append_data(data, key, new_data, token):
33 if new_data or len(data[key]) == 0:
34 data[key].append(token)
35 else:
36 data[key][-1] += token
37
38 while token:
39 if token in ['\n']:
40 if last_token not in ['\\']:
41 key_token = True
42 elif token in ['\\']:
43 pass
44 elif key_token:
45 key = token
46 key_token = False
47 else:
48 if token == "+=":
49 if not data.has_key(key):
50 data[key] = list()
51 elif token == "=":
52 data[key] = list()
53 else:
54 append_data( data, key, new_data, token )
55 new_data = True
56
57 last_token = token
58 token = lex.get_token()
59
60 if last_token == '\\' and token != '\n':
61 new_data = False
62 append_data( data, key, new_data, '\\' )
63
64 # compress lists of len 1 into single strings
65 for (k, v) in data.items():
66 if len(v) == 0:
67 data.pop(k)
68
69 # items in the following list will be kept as lists and not converted to strings
70 if k in ["INPUT", "FILE_PATTERNS", "EXCLUDE_PATTERNS"]:
71 continue
72
73 if len(v) == 1:
74 data[k] = v[0]
75
76 return data
77
78def DoxySourceScan(node, env, path):
79 """
80 Doxygen Doxyfile source scanner. This should scan the Doxygen file and add
81 any files used to generate docs to the list of source files.
82 """
83 default_file_patterns = [
84 '*.c', '*.cc', '*.cxx', '*.cpp', '*.c++', '*.java', '*.ii', '*.ixx',
85 '*.ipp', '*.i++', '*.inl', '*.h', '*.hh ', '*.hxx', '*.hpp', '*.h++',
86 '*.idl', '*.odl', '*.cs', '*.php', '*.php3', '*.inc', '*.m', '*.mm',
87 '*.py',
88 ]
89
90 default_exclude_patterns = [
91 '*~',
92 ]
93
94 sources = []
95
96 data = DoxyfileParse(node.get_contents())
97
98 if data.get("RECURSIVE", "NO") == "YES":
99 recursive = True
100 else:
101 recursive = False
102
103 file_patterns = data.get("FILE_PATTERNS", default_file_patterns)
104 exclude_patterns = data.get("EXCLUDE_PATTERNS", default_exclude_patterns)
105
106 doxyfile_dir = str( node.dir )
107
108## print 'running from', os.getcwd()
109 for node in data.get("INPUT", []):
110 node_real_path = os.path.normpath( os.path.join( doxyfile_dir, node ) )
111 if os.path.isfile(node_real_path):
112## print str(node), 'is a file'
113 sources.append(node)
114 elif os.path.isdir(node_real_path):
115## print str(node), 'is a directory'
116 if recursive:
117 for root, dirs, files in os.walk(node):
118 for f in files:
119 filename = os.path.join(root, f)
120
121 pattern_check = reduce(lambda x, y: x or bool(fnmatch(filename, y)), file_patterns, False)
122 exclude_check = reduce(lambda x, y: x and fnmatch(filename, y), exclude_patterns, True)
123
124 if pattern_check and not exclude_check:
125 sources.append(filename)
126## print ' adding source', os.path.abspath( filename )
127 else:
128 for pattern in file_patterns:
129 sources.extend(glob.glob(os.path.join( node, pattern)))
130## else:
131## print str(node), 'is neither a file nor a directory'
132 sources = map( lambda path: env.File(path), sources )
133 return sources
134
135
136def DoxySourceScanCheck(node, env):
137 """Check if we should scan this file"""
138 return os.path.isfile(node.path)
139
140def DoxyEmitter(source, target, env):
141 """Doxygen Doxyfile emitter"""
142 # possible output formats and their default values and output locations
143 output_formats = {
144 "HTML": ("YES", "html"),
145 "LATEX": ("YES", "latex"),
146 "RTF": ("NO", "rtf"),
147 "MAN": ("YES", "man"),
148 "XML": ("NO", "xml"),
149 }
150
151## print '#### DoxyEmitter:', source[0].abspath, os.path.exists( source[0].abspath )
152 data = DoxyfileParse(source[0].get_contents())
153
154 targets = []
155 out_dir = data.get("OUTPUT_DIRECTORY", ".")
156
157 # add our output locations
158 for (k, v) in output_formats.items():
159 if data.get("GENERATE_" + k, v[0]) == "YES":
160 targets.append(env.Dir( os.path.join(out_dir, data.get(k + "_OUTPUT", v[1]))) )
161
162 # don't clobber targets
163 for node in targets:
164 env.Precious(node)
165
166 # set up cleaning stuff
167 for node in targets:
168 clean_cmd = env.Clean(node, node)
169 env.Depends( clean_cmd, source )
170
171 return (targets, source)
172
173def generate(env):
174 """
175 Add builders and construction variables for the
176 Doxygen tool. This is currently for Doxygen 1.4.6.
177 """
178 doxyfile_scanner = env.Scanner(
179 DoxySourceScan,
180 "DoxySourceScan",
181 scan_check = DoxySourceScanCheck,
182 )
183
184 doxyfile_builder = env.Builder(
185 action = env.Action("cd ${SOURCE.dir} && ${DOXYGEN} ${SOURCE.file}",
186 varlist=['$SOURCES']),
187 emitter = DoxyEmitter,
188 target_factory = env.fs.Entry,
189 single_source = True,
190 source_scanner = doxyfile_scanner,
191 )
192
193 env.Append(BUILDERS = {
194 'Doxygen': doxyfile_builder,
195 })
196
197 env.AppendUnique(
198 DOXYGEN = 'doxygen',
199 )
200
201def exists(env):
202 """
203 Make sure doxygen exists.
204 """
205 return env.Detect("doxygen")