[scannermain] Create a new option parser
[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.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 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(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     parser = _get_option_parser()
200     pkg_options, unused = parser.parse_args(filtered_output)
201     options.cpp_includes.extend(pkg_options.cpp_includes)
202     options.cpp_defines.extend(pkg_options.cpp_defines)
203     options.cpp_undefines.extend(pkg_options.cpp_undefines)
204
205     args = ['pkg-config', '--libs-only-L']
206     args.extend(packages)
207     output = subprocess.Popen(args,
208                               stdout=subprocess.PIPE).communicate()[0]
209     if output is None:
210         return 1
211     filtered_output = list(process_options(output, options_whitelist))
212     pkg_options, unused = parser.parse_args(filtered_output)
213     options.library_paths.extend(pkg_options.library_paths)
214
215 def scanner_main(args):
216     parser = _get_option_parser()
217     (options, args) = parser.parse_args(args)
218
219     if options.passthrough_gir:
220         passthrough_gir(options.passthrough_gir, sys.stdout)
221     if options.test_codegen:
222         return test_codegen(options.test_codegen)
223
224     if len(args) <= 1:
225         _error('Need at least one filename')
226
227     if not options.namespace_name:
228         _error('Namespace name missing')
229
230     if options.format == 'gir':
231         from giscanner.girwriter import GIRWriter as Writer
232     else:
233         _error("Unknown format: %s" % (options.format, ))
234
235     if not (options.libraries or options.program):
236         _error("Must specify --program or --library")
237     libraries = options.libraries
238
239     filenames = []
240     for arg in args:
241         # We don't support real C++ parsing yet, but we should be able
242         # to understand C API implemented in C++ files.
243         if (arg.endswith('.c') or arg.endswith('.cpp') or
244             arg.endswith('.cc') or arg.endswith('.cxx') or
245             arg.endswith('.h') or arg.endswith('.hpp') or
246             arg.endswith('.hxx')):
247             if not os.path.exists(arg):
248                 _error('%s: no such a file or directory' % (arg, ))
249             # Make absolute, because we do comparisons inside scannerparser.c
250             # against the absolute path that cpp will give us
251             filenames.append(os.path.abspath(arg))
252
253     # We do this dance because the empty list has different semantics from
254     # None; if the user didn't specify the options, we want to use None so
255     # the Namespace constructor picks the defaults.
256     if options.identifier_prefixes:
257         identifier_prefixes = options.identifier_prefixes
258     else:
259         identifier_prefixes = None
260     if options.symbol_prefixes:
261         symbol_prefixes = options.symbol_prefixes
262     else:
263         symbol_prefixes = None
264
265     namespace = Namespace(options.namespace_name,
266                           options.namespace_version,
267                           identifier_prefixes=identifier_prefixes,
268                           symbol_prefixes=symbol_prefixes)
269     logger = message.MessageLogger.get(namespace=namespace)
270     if options.warn_all:
271         logger.enable_warnings(True)
272     transformer = Transformer(namespace,
273                               accept_unprefixed=options.accept_unprefixed)
274     transformer.set_include_paths(options.include_paths)
275     shown_include_warning = False
276     for include in options.includes:
277         if os.sep in include:
278             _error("Invalid include path %r" % (include, ))
279         try:
280             include_obj = Include.from_string(include)
281         except:
282             _error("Malformed include %r\n" % (include, ))
283         transformer.register_include(include_obj)
284
285     packages = set(options.packages)
286     packages.update(transformer.get_pkgconfig_packages())
287     exit_code = process_packages(options, packages)
288     if exit_code:
289         return exit_code
290
291     # Run the preprocessor, tokenize and construct simple
292     # objects representing the raw C symbols
293     ss = SourceScanner()
294     ss.set_cpp_options(options.cpp_includes,
295                        options.cpp_defines,
296                        options.cpp_undefines)
297     ss.parse_files(filenames)
298     ss.parse_macros(filenames)
299
300     # Transform the C symbols into AST nodes
301     transformer.parse(ss.get_symbols())
302
303     # Transform the C AST nodes into higher level
304     # GLib/GObject nodes
305     gdump_parser = GDumpParser(transformer)
306
307     # Do enough parsing that we have the get_type() functions to reference
308     # when creating the introspection binary
309     gdump_parser.init_parse()
310
311     if options.program:
312         args=[options.program]
313         args.extend(options.program_args)
314         binary = IntrospectionBinary(args)
315     else:
316         binary = compile_introspection_binary(options,
317                             gdump_parser.get_get_type_functions())
318
319     shlibs = resolve_shlibs(options, binary, libraries)
320
321     gdump_parser.set_introspection_binary(binary)
322     gdump_parser.parse()
323
324     ap = AnnotationParser()
325     blocks = ap.parse(ss.get_comments())
326
327     main = MainTransformer(transformer, blocks)
328     main.transform()
329
330     final = IntrospectablePass(transformer)
331     final.validate()
332
333     if options.warn_fatal and logger.did_warn():
334         message.fatal("warnings configured as fatal")
335         return 1
336
337     # Write out AST
338     if options.packages_export:
339         exported_packages = options.packages_export
340     else:
341         exported_packages = options.packages
342     writer = Writer(transformer.namespace, shlibs, transformer.get_includes(),
343                     exported_packages, options.c_includes)
344     data = writer.get_xml()
345
346     if options.output == "-":
347         output = sys.stdout
348     elif options.reparse_validate_gir:
349         main_f = tempfile.NamedTemporaryFile(suffix='.gir', delete=False)
350         main_f.write(data)
351         main_f.close()
352
353         temp_f = tempfile.NamedTemporaryFile(suffix='.gir', delete=False)
354         passthrough_gir(main_f.name, temp_f)
355         temp_f.close()
356         if not files_are_identical(main_f.name, temp_f.name):
357             _error("Failed to re-parse gir file; scanned=%r passthrough=%r" % (
358                 main_f.name, temp_f.name))
359         os.unlink(temp_f.name)
360         try:
361             shutil.move(main_f.name, options.output)
362         except OSError, e:
363             if e.errno == errno.EPERM:
364                 os.unlink(main_f.name)
365                 return 0
366             raise
367         return 0
368     else:
369         try:
370             output = open(options.output, "w")
371         except IOError, e:
372             _error("opening output for writing: %s" % (e.strerror, ))
373
374     try:
375         output.write(data)
376     except IOError, e:
377         _error("while writing output: %s" % (e.strerror, ))
378
379     return 0