[scanner] Remove support for xpath assertions
[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-2010 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 errno
24 import optparse
25 import os
26 import shutil
27 import subprocess
28 import sys
29 import tempfile
30
31 from giscanner.annotationparser import AnnotationParser
32 from giscanner.ast import Include
33 from giscanner.cachestore import CacheStore
34 from giscanner.dumper import compile_introspection_binary
35 from giscanner.gdumpparser import GDumpParser, IntrospectionBinary
36 from giscanner.maintransformer import MainTransformer
37 from giscanner.introspectablepass import IntrospectablePass
38 from giscanner.girparser import GIRParser
39 from giscanner.girwriter import GIRWriter
40 from giscanner.shlibs import resolve_shlibs
41 from giscanner.sourcescanner import SourceScanner
42 from giscanner.transformer import Transformer
43 from giscanner.utils import files_are_identical
44
45 def _get_option_parser():
46     parser = optparse.OptionParser('%prog [options] sources')
47     parser.add_option('', "--quiet",
48                       action="store_true", dest="quiet",
49                       default=False,
50                       help="If passed, do not print details of normal" \
51                           + " operation")
52     parser.add_option("", "--format",
53                       action="store", dest="format",
54                       default="gir",
55                       help="format to use, one of gidl, gir")
56     parser.add_option("-i", "--include",
57                       action="append", dest="includes", default=[],
58                       help="include types for other gidls")
59     parser.add_option("", "--add-include-path",
60                       action="append", dest="include_paths", default=[],
61                       help="include paths for other GIR files")
62     parser.add_option("", "--program",
63                       action="store", dest="program", default=None,
64                       help="program to execute")
65     parser.add_option("", "--program-arg",
66                       action="append", dest="program_args", default=[],
67                       help="extra arguments to program")
68     parser.add_option("", "--libtool",
69                       action="store", dest="libtool_path", default=None,
70                       help="full path to libtool")
71     parser.add_option("", "--no-libtool",
72                       action="store_true", dest="nolibtool", default=False,
73                       help="do not use libtool")
74     parser.add_option("-l", "--library",
75                       action="append", dest="libraries", default=[],
76                       help="libraries of this unit")
77     parser.add_option("-L", "--library-path",
78                       action="append", dest="library_paths", default=[],
79                       help="directories to search for libraries")
80     parser.add_option("-n", "--namespace",
81                       action="store", dest="namespace_name",
82                       help=("name of namespace for this unit, also "
83                             "used to compute --identifier-prefix and --symbol-prefix"))
84     parser.add_option("", "--nsversion",
85                       action="store", dest="namespace_version",
86                       help="version of namespace for this unit")
87     parser.add_option("", "--identifier-prefix",
88                       action="append", dest="identifier_prefixes", default=[],
89                       help="""Remove this prefix from C identifiers (structure typedefs, etc.).
90 May be specified multiple times.  This is also used as the default for --symbol-prefix if
91 the latter is not specified.""")
92     parser.add_option("", "--symbol-prefix",
93                       action="append", dest="symbol_prefixes", default=[],
94                       help="Remove this prefix from C symbols (function names)")
95     parser.add_option("", "--accept-unprefixed",
96                       action="store_true", dest="accept_unprefixed", default=False,
97                       help="""If specified, accept symbols and identifiers that do not
98 match the namespace prefix.""")
99     parser.add_option("", "--add-init-section",
100                       action="append", dest="init_sections", default=[],
101             help="add extra initialization code in the introspection program")
102     parser.add_option("-o", "--output",
103                       action="store", dest="output", default="-",
104                       help="output filename to write to, defaults to - (stdout)")
105     parser.add_option("", "--pkg",
106                       action="append", dest="packages", default=[],
107                       help="pkg-config packages to get cflags from")
108     parser.add_option("", "--pkg-export",
109                       action="append", dest="packages_export", default=[],
110                       help="Associated pkg-config packages for this library")
111     parser.add_option('', "--warn-all",
112                       action="store_true", dest="warn_all", default=False,
113                       help="If true, enable all warnings for introspection")
114     parser.add_option('', "--warn-error",
115                       action="store_true", dest="warn_fatal",
116                       help="Turn warnings into fatal errors")
117     parser.add_option("-v", "--verbose",
118                       action="store_true", dest="verbose",
119                       help="be verbose")
120     parser.add_option("", "--c-include",
121                       action="append", dest="c_includes", default=[],
122                       help="headers which should be included in C programs")
123
124     group = optparse.OptionGroup(parser, "Preprocessor options")
125     group.add_option("-I", help="Pre-processor include file",
126                      action="append", dest="cpp_includes",
127                      default=[])
128     group.add_option("-D", help="Pre-processor define",
129                      action="append", dest="cpp_defines",
130                      default=[])
131     group.add_option("-U", help="Pre-processor undefine",
132                      action="append", dest="cpp_undefines",
133                      default=[])
134     group.add_option("-p", dest="", help="Ignored")
135     parser.add_option_group(group)
136
137     # Private options
138     parser.add_option('', "--generate-typelib-tests",
139                       action="store", dest="test_codegen", default=None,
140                       help=optparse.SUPPRESS_HELP)
141     parser.add_option('', "--passthrough-gir",
142                       action="store", dest="passthrough_gir", default=None,
143                       help=optparse.SUPPRESS_HELP)
144     parser.add_option('', "--reparse-validate",
145                       action="store_true", dest="reparse_validate_gir", default=False,
146                       help=optparse.SUPPRESS_HELP)
147     parser.add_option("", "--typelib-xml",
148                       action="store_true", dest="typelib_xml",
149                       help=optparse.SUPPRESS_HELP)
150
151     return parser
152
153
154 def _error(msg):
155     raise SystemExit('ERROR: %s' % (msg, ))
156
157 def passthrough_gir(path, f):
158     parser = GIRParser()
159     parser.parse(path)
160
161     writer = GIRWriter(parser.get_namespace(),
162                        parser.get_shared_libraries(),
163                        parser.get_includes(),
164                        parser.get_pkgconfig_packages(),
165                        parser.get_c_includes())
166     f.write(writer.get_xml())
167
168 def test_codegen(optstring):
169     (namespace, out_h_filename, out_c_filename) = optstring.split(',')
170     if namespace == 'Everything':
171         from .testcodegen import EverythingCodeGenerator
172         gen = EverythingCodeGenerator(out_h_filename, out_c_filename)
173         gen.write()
174     else:
175         _error("Invaild namespace %r" % (namespace, ))
176     return 0
177
178 def process_options(output, allowed_flags):
179     for option in output.split():
180         for flag in allowed_flags:
181             if not option.startswith(flag):
182                 continue
183             yield option
184             break
185
186 def process_packages(parser, options, packages):
187     args = ['pkg-config', '--cflags']
188     args.extend(packages)
189     output = subprocess.Popen(args,
190                               stdout=subprocess.PIPE).communicate()[0]
191     if output is None:
192         # the error output should have already appeared on our stderr,
193         # so we just exit
194         return 1
195     # Some pkg-config files on Windows have options we don't understand,
196     # so we explicitly filter to only the ones we need.
197     options_whitelist = ['-I', '-D', '-U', '-l', '-L']
198     filtered_output = list(process_options(output, options_whitelist))
199     pkg_options, unused = parser.parse_args(filtered_output)
200     options.cpp_includes.extend(pkg_options.cpp_includes)
201     options.cpp_defines.extend(pkg_options.cpp_defines)
202     options.cpp_undefines.extend(pkg_options.cpp_undefines)
203
204     args = ['pkg-config', '--libs-only-L']
205     args.extend(packages)
206     output = subprocess.Popen(args,
207                               stdout=subprocess.PIPE).communicate()[0]
208     if output is None:
209         return 1
210     filtered_output = list(process_options(output, options_whitelist))
211     pkg_options, unused = parser.parse_args(filtered_output)
212     options.library_paths.extend(pkg_options.library_paths)
213
214 def scanner_main(args):
215     parser = _get_option_parser()
216     (options, args) = parser.parse_args(args)
217
218     if options.passthrough_gir:
219         passthrough_gir(options.passthrough_gir, sys.stdout)
220     if options.test_codegen:
221         return test_codegen(options.test_codegen)
222
223     if len(args) <= 1:
224         _error('Need at least one filename')
225
226     if not options.namespace_name:
227         _error('Namespace name missing')
228
229     if options.format == 'gir':
230         from giscanner.girwriter import GIRWriter as Writer
231     else:
232         _error("Unknown format: %s" % (options.format, ))
233
234     if not (options.libraries or options.program):
235         _error("Must specify --program or --library")
236     libraries = options.libraries
237
238     filenames = []
239     for arg in args:
240         # We don't support real C++ parsing yet, but we should be able
241         # to understand C API implemented in C++ files.
242         if (arg.endswith('.c') or arg.endswith('.cpp') or
243             arg.endswith('.cc') or arg.endswith('.cxx') or
244             arg.endswith('.h') or arg.endswith('.hpp') or
245             arg.endswith('.hxx')):
246             if not os.path.exists(arg):
247                 _error('%s: no such a file or directory' % (arg, ))
248             # Make absolute, because we do comparisons inside scannerparser.c
249             # against the absolute path that cpp will give us
250             filenames.append(os.path.abspath(arg))
251
252     # We do this dance because the empty list has different semantics from
253     # None; if the user didn't specify the options, we want to use None so
254     # the Namespace constructor picks the defaults.
255     if options.identifier_prefixes:
256         identifier_prefixes = options.identifier_prefixes
257     else:
258         identifier_prefixes = None
259     if options.symbol_prefixes:
260         symbol_prefixes = options.symbol_prefixes
261     else:
262         symbol_prefixes = None
263
264     cachestore = CacheStore()
265     transformer = Transformer(cachestore,
266                               options.namespace_name,
267                               options.namespace_version,
268                               identifier_prefixes=identifier_prefixes,
269                               symbol_prefixes=symbol_prefixes,
270                               accept_unprefixed=options.accept_unprefixed)
271     if options.warn_all:
272         transformer.enable_warnings(True)
273     transformer.set_include_paths(options.include_paths)
274     shown_include_warning = False
275     for include in options.includes:
276         if os.sep in include:
277             _error("Invalid include path %r" % (include, ))
278         try:
279             include_obj = Include.from_string(include)
280         except:
281             _error("Malformed include %r\n" % (include, ))
282         transformer.register_include(include_obj)
283
284     packages = set(options.packages)
285     packages.update(transformer.get_pkgconfig_packages())
286     exit_code = process_packages(parser, options, packages)
287     if exit_code:
288         return exit_code
289
290     # Run the preprocessor, tokenize and construct simple
291     # objects representing the raw C symbols
292     ss = SourceScanner()
293     ss.set_cpp_options(options.cpp_includes,
294                        options.cpp_defines,
295                        options.cpp_undefines)
296     ss.parse_files(filenames)
297     ss.parse_macros(filenames)
298
299     # Transform the C symbols into AST nodes
300     transformer.set_source_ast(ss)
301
302     # Transform the C AST nodes into higher level
303     # GLib/GObject nodes
304     gdump_parser = GDumpParser(transformer)
305
306     # Do enough parsing that we have the get_type() functions to reference
307     # when creating the introspection binary
308     gdump_parser.init_parse()
309
310     if options.program:
311         args=[options.program]
312         args.extend(options.program_args)
313         binary = IntrospectionBinary(args)
314     else:
315         binary = compile_introspection_binary(options,
316                             gdump_parser.get_get_type_functions())
317
318     shlibs = resolve_shlibs(options, binary, libraries)
319
320     gdump_parser.set_introspection_binary(binary)
321     gdump_parser.parse()
322
323     ap = AnnotationParser(ss)
324     blocks = ap.parse()
325
326     main = MainTransformer(transformer, blocks)
327     main.transform()
328
329     final = IntrospectablePass(transformer)
330     final.validate()
331
332     if options.warn_fatal and transformer.did_warn():
333         transformer.log_warning("warnings configured as fatal", fatal=True)
334         return 1
335
336     # Write out AST
337     if options.packages_export:
338         exported_packages = options.packages_export
339     else:
340         exported_packages = options.packages
341     writer = Writer(transformer.namespace, shlibs, transformer.get_includes(),
342                     exported_packages, options.c_includes)
343     data = writer.get_xml()
344
345     if options.output == "-":
346         output = sys.stdout
347     elif options.reparse_validate_gir:
348         main_f = tempfile.NamedTemporaryFile(suffix='.gir', delete=False)
349         main_f.write(data)
350         main_f.close()
351
352         temp_f = tempfile.NamedTemporaryFile(suffix='.gir', delete=False)
353         passthrough_gir(main_f.name, temp_f)
354         temp_f.close()
355         if not files_are_identical(main_f.name, temp_f.name):
356             _error("Failed to re-parse gir file; scanned=%r passthrough=%r" % (
357                 main_f.name, temp_f.name))
358         os.unlink(temp_f.name)
359         try:
360             shutil.move(main_f.name, options.output)
361         except OSError, e:
362             if e.errno == errno.EPERM:
363                 os.unlink(main_f.name)
364                 return 0
365             raise
366         return 0
367     else:
368         try:
369             output = open(options.output, "w")
370         except IOError, e:
371             _error("opening output for writing: %s" % (e.strerror, ))
372
373     try:
374         output.write(data)
375     except IOError, e:
376         _error("while writing output: %s" % (e.strerror, ))
377
378     return 0