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