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