eae10f776629abd58ea1d9c451b81dc11dde512a
[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.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     namespace = Namespace(options.namespace_name,
265                           options.namespace_version,
266                           identifier_prefixes=identifier_prefixes,
267                           symbol_prefixes=symbol_prefixes)
268     message.MessageLogger.get(namespace=namespace,
269                               enable_warnings=options.warn_all)
270     transformer = Transformer(namespace,
271                               accept_unprefixed=options.accept_unprefixed)
272     transformer.set_include_paths(options.include_paths)
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
283     packages = set(options.packages)
284     packages.update(transformer.get_pkgconfig_packages())
285     exit_code = process_packages(parser, options, packages)
286     if exit_code:
287         return exit_code
288
289     # Run the preprocessor, tokenize and construct simple
290     # objects representing the raw C symbols
291     ss = SourceScanner()
292     ss.set_cpp_options(options.cpp_includes,
293                        options.cpp_defines,
294                        options.cpp_undefines)
295     ss.parse_files(filenames)
296     ss.parse_macros(filenames)
297
298     # Transform the C symbols into AST nodes
299     transformer.parse(ss.get_symbols())
300
301     # Transform the C AST nodes into higher level
302     # GLib/GObject nodes
303     gdump_parser = GDumpParser(transformer)
304
305     # Do enough parsing that we have the get_type() functions to reference
306     # when creating the introspection binary
307     gdump_parser.init_parse()
308
309     if options.program:
310         args=[options.program]
311         args.extend(options.program_args)
312         binary = IntrospectionBinary(args)
313     else:
314         binary = compile_introspection_binary(options,
315                             gdump_parser.get_get_type_functions())
316
317     shlibs = resolve_shlibs(options, binary, libraries)
318
319     gdump_parser.set_introspection_binary(binary)
320     gdump_parser.parse()
321
322     ap = AnnotationParser()
323     blocks = ap.parse(ss.get_comments())
324
325     main = MainTransformer(transformer, blocks)
326     main.transform()
327
328     final = IntrospectablePass(transformer)
329     final.validate()
330
331     if options.warn_fatal and transformer.did_warn():
332         transformer.log_warning("warnings configured as fatal", fatal=True)
333         return 1
334
335     # Write out AST
336     if options.packages_export:
337         exported_packages = options.packages_export
338     else:
339         exported_packages = options.packages
340     writer = Writer(transformer.namespace, shlibs, transformer.get_includes(),
341                     exported_packages, options.c_includes)
342     data = writer.get_xml()
343
344     if options.output == "-":
345         output = sys.stdout
346     elif options.reparse_validate_gir:
347         main_f = tempfile.NamedTemporaryFile(suffix='.gir', delete=False)
348         main_f.write(data)
349         main_f.close()
350
351         temp_f = tempfile.NamedTemporaryFile(suffix='.gir', delete=False)
352         passthrough_gir(main_f.name, temp_f)
353         temp_f.close()
354         if not files_are_identical(main_f.name, temp_f.name):
355             _error("Failed to re-parse gir file; scanned=%r passthrough=%r" % (
356                 main_f.name, temp_f.name))
357         os.unlink(temp_f.name)
358         try:
359             shutil.move(main_f.name, options.output)
360         except OSError, e:
361             if e.errno == errno.EPERM:
362                 os.unlink(main_f.name)
363                 return 0
364             raise
365         return 0
366     else:
367         try:
368             output = open(options.output, "w")
369         except IOError, e:
370             _error("opening output for writing: %s" % (e.strerror, ))
371
372     try:
373         output.write(data)
374     except IOError, e:
375         _error("while writing output: %s" % (e.strerror, ))
376
377     return 0