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