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