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