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