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