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