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