[scanner] Also parse C++ files
[platform/upstream/gobject-introspection.git] / giscanner / scannermain.py
1 #!/usr/bin/env python
2 # -*- Mode: Python -*-
3 # GObject-Introspection - a framework for introspecting GObject libraries
4 # Copyright (C) 2008  Johan Dahlin
5 # Copyright (C) 2009 Red Hat, Inc.
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 # 02110-1301, USA.
21 #
22
23 import subprocess
24 import optparse
25 import os
26 import sys
27
28 from giscanner.annotationparser import AnnotationParser, InvalidAnnotationError
29 from giscanner.ast import Include
30 from giscanner.cachestore import CacheStore
31 from giscanner.dumper import compile_introspection_binary
32 from giscanner.glibtransformer import GLibTransformer, IntrospectionBinary
33 from giscanner.minixpath import myxpath, xpath_assert
34 from giscanner.sourcescanner import SourceScanner
35 from giscanner.shlibs import resolve_shlibs
36 from giscanner.transformer import Transformer
37
38 def _get_option_parser():
39     parser = optparse.OptionParser('%prog [options] sources')
40     parser.add_option('', "--quiet",
41                       action="store_true", dest="quiet",
42                       default=False,
43                       help="If passed, do not print details of normal" \
44                           + " operation")
45     parser.add_option("", "--format",
46                       action="store", dest="format",
47                       default="gir",
48                       help="format to use, one of gidl, gir")
49     parser.add_option("-i", "--include",
50                       action="append", dest="includes", default=[],
51                       help="include types for other gidls")
52     parser.add_option("", "--add-include-path",
53                       action="append", dest="include_paths", default=[],
54                       help="include paths for other GIR files")
55     parser.add_option("", "--program",
56                       action="store", dest="program", default=None,
57                       help="program to execute")
58     parser.add_option("", "--program-arg",
59                       action="append", dest="program_args", default=[],
60                       help="extra arguments to program")
61     parser.add_option("", "--libtool",
62                       action="store", dest="libtool_path", default=None,
63                       help="full path to libtool")
64     parser.add_option("", "--no-libtool",
65                       action="store_true", dest="nolibtool", default=False,
66                       help="do not use libtool")
67     parser.add_option("-l", "--library",
68                       action="append", dest="libraries", default=[],
69                       help="libraries of this unit")
70     parser.add_option("-L", "--library-path",
71                       action="append", dest="library_paths", default=[],
72                       help="directories to search for libraries")
73     parser.add_option("-n", "--namespace",
74                       action="store", dest="namespace_name",
75                       help=("name of namespace for this unit, also "
76                             "used as --strip-prefix default"))
77     parser.add_option("", "--nsversion",
78                       action="store", dest="namespace_version",
79                       help="version of namespace for this unit")
80     parser.add_option("", "--strip-prefix",
81                       action="store", dest="strip_prefix", default=None,
82                       help="remove this prefix from objects and functions")
83     parser.add_option("", "--add-init-section",
84                       action="append", dest="init_sections", default=[],
85             help="add extra initialization code in the introspection program")
86     parser.add_option("-o", "--output",
87                       action="store", dest="output",
88                       help="output to writeout, defaults to stdout")
89     parser.add_option("", "--pkg",
90                       action="append", dest="packages", default=[],
91                       help="pkg-config packages to get cflags from")
92     parser.add_option("", "--pkg-export",
93                       action="append", dest="packages_export", default=[],
94                       help="Associated pkg-config packages for this library")
95     parser.add_option('', "--warn-all",
96                       action="store_true", dest="warn_all", default=False,
97                       help="If true, enable all warnings for introspection")
98     parser.add_option('', "--warn-error",
99                       action="store_true", dest="warn_fatal",
100                       help="Turn warnings into fatal errors")
101     parser.add_option("-v", "--verbose",
102                       action="store_true", dest="verbose",
103                       help="be verbose")
104     parser.add_option("", "--noclosure",
105                       action="store_true", dest="noclosure",
106                       help="do not delete unknown types")
107     parser.add_option("", "--typelib-xml",
108                       action="store_true", dest="typelib_xml",
109                       help="Just convert GIR to typelib XML")
110     parser.add_option("", "--inject",
111                       action="store_true", dest="inject",
112                       help="Inject additional components into GIR XML")
113     parser.add_option("", "--xpath-assertions",
114                       action="store", dest="xpath_assertions",
115             help="Use given file to create assertions on GIR content")
116     parser.add_option("", "--c-include",
117                       action="append", dest="c_includes", default=[],
118                       help="headers which should be included in C programs")
119
120     group = optparse.OptionGroup(parser, "Preprocessor options")
121     group.add_option("-I", help="Pre-processor include file",
122                      action="append", dest="cpp_includes",
123                      default=[])
124     group.add_option("-D", help="Pre-processor define",
125                      action="append", dest="cpp_defines",
126                      default=[])
127     group.add_option("-U", help="Pre-processor undefine",
128                      action="append", dest="cpp_undefines",
129                      default=[])
130     group.add_option("-p", dest="", help="Ignored")
131     parser.add_option_group(group)
132
133     return parser
134
135
136 def _error(msg):
137     raise SystemExit('ERROR: %s' % (msg, ))
138
139 def typelib_xml_strip(path):
140     from giscanner.girparser import GIRParser
141     from giscanner.girwriter import GIRWriter
142     from giscanner.girparser import C_NS
143     from xml.etree.cElementTree import parse
144
145     c_ns_key = '{%s}' % (C_NS, )
146
147     tree = parse(path)
148     root = tree.getroot()
149     for node in root.getiterator():
150         for attrib in list(node.attrib):
151             if attrib.startswith(c_ns_key):
152                 del node.attrib[attrib]
153     parser = GIRParser()
154     parser.parse_tree(tree)
155
156     writer = GIRWriter(parser.get_namespace(),
157                        parser.get_shared_libraries(),
158                        parser.get_includes())
159     sys.stdout.write(writer.get_xml())
160     return 0
161
162 def inject(path, additions, outpath):
163     from giscanner.girparser import GIRParser
164     from giscanner.girwriter import GIRWriter
165     from xml.etree.cElementTree import parse
166
167     tree = parse(path)
168     root = tree.getroot()
169     injectDoc = parse(open(additions))
170     for node in injectDoc.getroot():
171         injectPath = node.attrib['path']
172         target = myxpath(root, injectPath)
173         if not target:
174             raise ValueError("Couldn't find path %r" % (injectPath, ))
175         for child in node:
176             target.append(child)
177
178     parser = GIRParser()
179     parser.parse_tree(tree)
180     writer = GIRWriter(parser.get_namespace(),
181                        parser.get_shared_libraries(),
182                        parser.get_includes())
183     outf = open(outpath, 'w')
184     outf.write(writer.get_xml())
185     outf.close()
186     return 0
187
188 def validate(assertions, path):
189     from xml.etree.cElementTree import parse
190     doc = parse(open(path))
191     root = doc.getroot()
192     f = open(assertions)
193     assertions_list = f.readlines()
194     for assertion in assertions_list:
195         assertion = assertion.strip()
196         xpath_assert(root, assertion)
197     f.close()
198     return 0
199
200 def process_options(output, allowed_flags):
201     for option in output.split():
202         for flag in allowed_flags:
203             if not option.startswith(flag):
204                 continue
205             yield option
206             break
207
208 def process_packages(parser, options, packages):
209     args = ['pkg-config', '--cflags']
210     args.extend(packages)
211     output = subprocess.Popen(args,
212                               stdout=subprocess.PIPE).communicate()[0]
213     if output is None:
214         # the error output should have already appeared on our stderr,
215         # so we just exit
216         sys.exit(1)
217     # Some pkg-config files on Windows have options we don't understand,
218     # so we explicitly filter to only the ones we need.
219     options_whitelist = ['-I', '-D', '-U', '-l', '-L']
220     filtered_output = list(process_options(output, options_whitelist))
221     pkg_options, unused = parser.parse_args(filtered_output)
222     options.cpp_includes.extend(pkg_options.cpp_includes)
223     options.cpp_defines.extend(pkg_options.cpp_defines)
224     options.cpp_undefines.extend(pkg_options.cpp_undefines)
225
226     args = ['pkg-config', '--libs-only-L']
227     args.extend(packages)
228     output = subprocess.Popen(args,
229                               stdout=subprocess.PIPE).communicate()[0]
230     if output is None:
231         sys.exit(1)
232     filtered_output = list(process_options(output, options_whitelist))
233     pkg_options, unused = parser.parse_args(filtered_output)
234     options.library_paths.extend(pkg_options.library_paths)
235
236 def scanner_main(args):
237     parser = _get_option_parser()
238     (options, args) = parser.parse_args(args)
239
240     if len(args) <= 1:
241         _error('Need at least one filename')
242
243     if options.typelib_xml:
244         return typelib_xml_strip(args[1])
245
246     if options.inject:
247         if len(args) != 4:
248             _error('Need three filenames; e.g. g-ir-scanner '
249                    '--inject Source.gir Additions.xml SourceOut.gir')
250         return inject(*args[1:4])
251
252     if options.xpath_assertions:
253         return validate(options.xpath_assertions, args[1])
254
255     if not options.namespace_name:
256         _error('Namespace name missing')
257
258     if options.format == 'gir':
259         from giscanner.girwriter import GIRWriter as Writer
260     else:
261         _error("Unknown format: %s" % (options.format, ))
262
263     if not (options.libraries or options.program):
264         _error("Must specify --program or --library")
265     libraries = options.libraries
266
267     filenames = []
268     for arg in args:
269         # We don't support real C++ parsing yet, but we should be able
270         # to understand C API implemented in C++ files.
271         if (arg.endswith('.c') or arg.endswith('.cpp') or
272             arg.endswith('.cc') or arg.endswith('.cxx') or
273             arg.endswith('.h') or arg.endswith('.hpp') or
274             arg.endswith('.hxx')):
275             if not os.path.exists(arg):
276                 _error('%s: no such a file or directory' % (arg, ))
277             # Make absolute, because we do comparisons inside scannerparser.c
278             # against the absolute path that cpp will give us
279             filenames.append(os.path.abspath(arg))
280
281     cachestore = CacheStore()
282     transformer = Transformer(cachestore,
283                               options.namespace_name,
284                               options.namespace_version)
285     if options.strip_prefix:
286         transformer.set_strip_prefix(options.strip_prefix)
287     else:
288         transformer.set_strip_prefix(options.namespace_name)
289     if options.warn_all:
290         transformer.enable_warnings(True)
291     transformer.set_include_paths(options.include_paths)
292     shown_include_warning = False
293     for include in options.includes:
294         if os.sep in include:
295             raise ValueError("Invalid include path %r" % (include, ))
296         try:
297             include_obj = Include.from_string(include)
298         except:
299             sys.stderr.write("Malformed include %r\n" % (include, ))
300             sys.exit(1)
301         transformer.register_include(include_obj)
302
303     packages = set(options.packages)
304     packages.update(transformer.get_pkgconfig_packages())
305     process_packages(parser, options, packages)
306
307     # Run the preprocessor, tokenize and construct simple
308     # objects representing the raw C symbols
309     ss = SourceScanner()
310     ss.set_cpp_options(options.cpp_includes,
311                        options.cpp_defines,
312                        options.cpp_undefines)
313     ss.parse_files(filenames)
314     ss.parse_macros(filenames)
315
316     # Transform the C symbols into AST nodes
317     transformer.set_source_ast(ss)
318
319     # Transform the C AST nodes into higher level
320     # GLib/GObject nodes
321     glibtransformer = GLibTransformer(transformer,
322                                       noclosure=options.noclosure)
323
324     # Do enough parsing that we have the get_type() functions to reference
325     # when creating the introspection binary
326     glibtransformer.init_parse()
327
328     if options.program:
329         args=[options.program]
330         args.extend(options.program_args)
331         binary = IntrospectionBinary(args)
332     else:
333         binary = compile_introspection_binary(options,
334                             glibtransformer.get_get_type_functions())
335
336     shlibs = resolve_shlibs(options, binary, libraries)
337
338     glibtransformer.set_introspection_binary(binary)
339
340     namespace = glibtransformer.parse()
341
342     ap = AnnotationParser(namespace, ss, transformer)
343     try:
344         ap.parse()
345     except InvalidAnnotationError, e:
346         raise SystemExit("ERROR in annotation: %s" % (str(e), ))
347
348     glibtransformer.final_analyze()
349
350     if options.warn_fatal and transformer.did_warn():
351         return 1
352
353     # Write out AST
354     if options.packages_export:
355         exported_packages = options.packages_export
356     else:
357         exported_packages = options.packages
358     writer = Writer(namespace, shlibs, transformer.get_includes(),
359                     exported_packages, options.c_includes,
360                     transformer.get_strip_prefix())
361     data = writer.get_xml()
362     if options.output:
363         fd = open(options.output, "w")
364         fd.write(data)
365     else:
366         print data
367
368     return 0