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