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