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