Imported Upstream version 1.39.3
[platform/upstream/gobject-introspection.git] / giscanner / sourcescanner.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 #
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 as published by the Free Software Foundation; either
8 # version 2 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the
17 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 # Boston, MA 02111-1307, USA.
19 #
20
21 from __future__ import with_statement
22 import os
23 import subprocess
24 import tempfile
25
26 from .libtoolimporter import LibtoolImporter
27 from .message import Position
28
29 with LibtoolImporter(None, None):
30     if 'UNINSTALLED_INTROSPECTION_SRCDIR' in os.environ:
31         from _giscanner import SourceScanner as CSourceScanner
32     else:
33         from giscanner._giscanner import SourceScanner as CSourceScanner
34
35 HEADER_EXTS = ['.h', '.hpp', '.hxx']
36 SOURCE_EXTS = ['.c', '.cpp', '.cc', '.cxx']
37 ALL_EXTS = SOURCE_EXTS + HEADER_EXTS
38
39 (CSYMBOL_TYPE_INVALID,
40  CSYMBOL_TYPE_ELLIPSIS,
41  CSYMBOL_TYPE_CONST,
42  CSYMBOL_TYPE_OBJECT,
43  CSYMBOL_TYPE_FUNCTION,
44  CSYMBOL_TYPE_STRUCT,
45  CSYMBOL_TYPE_UNION,
46  CSYMBOL_TYPE_ENUM,
47  CSYMBOL_TYPE_TYPEDEF,
48  CSYMBOL_TYPE_MEMBER) = range(10)
49
50 (CTYPE_INVALID,
51  CTYPE_VOID,
52  CTYPE_BASIC_TYPE,
53  CTYPE_TYPEDEF,
54  CTYPE_STRUCT,
55  CTYPE_UNION,
56  CTYPE_ENUM,
57  CTYPE_POINTER,
58  CTYPE_ARRAY,
59  CTYPE_FUNCTION) = range(10)
60
61 STORAGE_CLASS_NONE = 0
62 STORAGE_CLASS_TYPEDEF = 1 << 1
63 STORAGE_CLASS_EXTERN = 1 << 2
64 STORAGE_CLASS_STATIC = 1 << 3
65 STORAGE_CLASS_AUTO = 1 << 4
66 STORAGE_CLASS_REGISTER = 1 << 5
67
68 TYPE_QUALIFIER_NONE = 0
69 TYPE_QUALIFIER_CONST = 1 << 1
70 TYPE_QUALIFIER_RESTRICT = 1 << 2
71 TYPE_QUALIFIER_VOLATILE = 1 << 3
72 TYPE_QUALIFIER_EXTENSION = 1 << 4
73
74 FUNCTION_NONE = 0
75 FUNCTION_INLINE = 1 << 1
76
77 (UNARY_ADDRESS_OF,
78  UNARY_POINTER_INDIRECTION,
79  UNARY_PLUS,
80  UNARY_MINUS,
81  UNARY_BITWISE_COMPLEMENT,
82  UNARY_LOGICAL_NEGATION) = range(6)
83
84
85 def symbol_type_name(symbol_type):
86     return {
87         CSYMBOL_TYPE_INVALID: 'invalid',
88         CSYMBOL_TYPE_ELLIPSIS: 'ellipsis',
89         CSYMBOL_TYPE_CONST: 'const',
90         CSYMBOL_TYPE_OBJECT: 'object',
91         CSYMBOL_TYPE_FUNCTION: 'function',
92         CSYMBOL_TYPE_STRUCT: 'struct',
93         CSYMBOL_TYPE_UNION: 'union',
94         CSYMBOL_TYPE_ENUM: 'enum',
95         CSYMBOL_TYPE_TYPEDEF: 'typedef',
96         CSYMBOL_TYPE_MEMBER: 'member'}.get(symbol_type)
97
98
99 def ctype_name(ctype):
100     return {
101         CTYPE_INVALID: 'invalid',
102         CTYPE_VOID: 'void',
103         CTYPE_BASIC_TYPE: 'basic',
104         CTYPE_TYPEDEF: 'typedef',
105         CTYPE_STRUCT: 'struct',
106         CTYPE_UNION: 'union',
107         CTYPE_ENUM: 'enum',
108         CTYPE_POINTER: 'pointer',
109         CTYPE_ARRAY: 'array',
110         CTYPE_FUNCTION: 'function'}.get(ctype)
111
112
113 class SourceType(object):
114     __members__ = ['type', 'base_type', 'name', 'type_qualifier',
115                    'child_list', 'is_bitfield']
116
117     def __init__(self, scanner, stype):
118         self._scanner = scanner
119         self._stype = stype
120
121     def __repr__(self):
122         return '<%s type=%r name=%r>' % (
123             self.__class__.__name__,
124             ctype_name(self.type),
125             self.name)
126
127     @property
128     def type(self):
129         return self._stype.type
130
131     @property
132     def base_type(self):
133         if self._stype.base_type is not None:
134             return SourceType(self._scanner, self._stype.base_type)
135
136     @property
137     def name(self):
138         return self._stype.name
139
140     @property
141     def type_qualifier(self):
142         return self._stype.type_qualifier
143
144     @property
145     def child_list(self):
146         for symbol in self._stype.child_list:
147             if symbol is None:
148                 continue
149             yield SourceSymbol(self._scanner, symbol)
150
151     @property
152     def is_bitfield(self):
153         return self._stype.is_bitfield
154
155
156 class SourceSymbol(object):
157     __members__ = ['const_int', 'const_double', 'const_string', 'const_boolean',
158                    'ident', 'type', 'base_type']
159
160     def __init__(self, scanner, symbol):
161         self._scanner = scanner
162         self._symbol = symbol
163
164     def __repr__(self):
165         src = self.source_filename
166         if src:
167             line = self.line
168             if line:
169                 src += ':%r' % (line, )
170         return '<%s type=%r ident=%r src=%r>' % (
171             self.__class__.__name__,
172             symbol_type_name(self.type),
173             self.ident,
174             src)
175
176     @property
177     def const_int(self):
178         return self._symbol.const_int
179
180     @property
181     def const_double(self):
182         return self._symbol.const_double
183
184     @property
185     def const_string(self):
186         return self._symbol.const_string
187
188     @property
189     def const_boolean(self):
190         return self._symbol.const_boolean
191
192     @property
193     def ident(self):
194         return self._symbol.ident
195
196     @property
197     def type(self):
198         return self._symbol.type
199
200     @property
201     def base_type(self):
202         if self._symbol.base_type is not None:
203             return SourceType(self._scanner, self._symbol.base_type)
204
205     @property
206     def source_filename(self):
207         return self._symbol.source_filename
208
209     @property
210     def line(self):
211         return self._symbol.line
212
213     @property
214     def private(self):
215         return self._symbol.private
216
217     @property
218     def position(self):
219         return Position(self._symbol.source_filename,
220                         self._symbol.line)
221
222
223 class SourceScanner(object):
224
225     def __init__(self):
226         self._scanner = CSourceScanner()
227         self._filenames = []
228         self._cpp_options = []
229
230     # Public API
231
232     def set_cpp_options(self, includes, defines, undefines, cflags=[]):
233         self._cpp_options.extend(cflags)
234         for prefix, args in [('-I', [os.path.realpath(f) for f in includes]),
235                              ('-D', defines),
236                              ('-U', undefines)]:
237             for arg in (args or []):
238                 opt = prefix + arg
239                 if not opt in self._cpp_options:
240                     self._cpp_options.append(opt)
241
242     def parse_files(self, filenames):
243         for filename in filenames:
244             # self._scanner expects file names to be canonicalized and symlinks to be resolved
245             filename = os.path.realpath(filename)
246             self._scanner.append_filename(filename)
247             self._filenames.append(filename)
248
249         headers = []
250         for filename in self._filenames:
251             if os.path.splitext(filename)[1] in SOURCE_EXTS:
252                 self._scanner.lex_filename(filename)
253             else:
254                 headers.append(filename)
255
256         self._parse(headers)
257
258     def parse_macros(self, filenames):
259         self._scanner.set_macro_scan(True)
260         # self._scanner expects file names to be canonicalized and symlinks to be resolved
261         self._scanner.parse_macros([os.path.realpath(f) for f in filenames])
262         self._scanner.set_macro_scan(False)
263
264     def get_symbols(self):
265         for symbol in self._scanner.get_symbols():
266             yield SourceSymbol(self._scanner, symbol)
267
268     def get_comments(self):
269         return self._scanner.get_comments()
270
271     def dump(self):
272         print '-' * 30
273         for symbol in self._scanner.get_symbols():
274             print symbol.ident, symbol.base_type.name, symbol.type
275
276     # Private
277
278     def _parse(self, filenames):
279         if not filenames:
280             return
281
282         defines = ['__GI_SCANNER__']
283         undefs = []
284         cpp_args = os.environ.get('CC', 'cc').split()  # support CC="ccache gcc"
285         if 'cl' in cpp_args:
286             # The Microsoft compiler/preprocessor (cl) does not accept
287             # source input from stdin (the '-' flag), so we need
288             # some help from gcc from MinGW/Cygwin or so.
289             # Note that the generated dumper program is
290             # still built and linked by Visual C++.
291             cpp_args = ['gcc']
292         cpp_args += os.environ.get('CPPFLAGS', '').split()
293         cpp_args += os.environ.get('CFLAGS', '').split()
294         cpp_args += ['-E', '-C', '-I.', '-']
295         cpp_args += self._cpp_options
296
297         proc = subprocess.Popen(cpp_args,
298                                 stdin=subprocess.PIPE,
299                                 stdout=subprocess.PIPE)
300
301         for define in defines:
302             proc.stdin.write('#ifndef %s\n' % (define, ))
303             proc.stdin.write('# define %s\n' % (define, ))
304             proc.stdin.write('#endif\n')
305         for undef in undefs:
306             proc.stdin.write('#undef %s\n' % (undef, ))
307         for filename in filenames:
308             proc.stdin.write('#include <%s>\n' % (filename, ))
309         proc.stdin.close()
310
311         tmp_fd, tmp_name = tempfile.mkstemp()
312         fp = os.fdopen(tmp_fd, 'w+b')
313         while True:
314             data = proc.stdout.read(4096)
315             if data is None:
316                 break
317             fp.write(data)
318             if len(data) < 4096:
319                 break
320         fp.seek(0, 0)
321
322         assert proc, 'Proc was none'
323         proc.wait()
324         if proc.returncode != 0:
325             raise SystemExit('Error while processing the source.')
326
327         self._scanner.parse_file(fp.fileno())
328         fp.close()
329         os.unlink(tmp_name)