3 # GObject-Introspection - a framework for introspecting GObject libraries
4 # Copyright (C) 2008-2010 Johan Dahlin
5 # Copyright (C) 2009 Red Hat, Inc.
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.
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.
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
31 from giscanner import message
32 from giscanner.annotationparser import GtkDocCommentBlockParser
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, ALL_EXTS
42 from giscanner.transformer import Transformer
46 def process_cflags_begin(option, opt, value, parser):
47 cflags = getattr(parser.values, option.dest)
48 while len(parser.rargs) > 0 and parser.rargs[0] != '--cflags-end':
49 arg = parser.rargs.pop(0)
50 if arg == "-I" and parser.rargs and parser.rargs[0] != '--cflags-end':
51 # This is a special case where there's a space between -I and the path.
52 arg += parser.rargs.pop(0)
53 cflags.append(utils.cflag_real_include_path(arg))
56 def process_cflags_end(option, opt, value, parser):
60 def process_cpp_includes(option, opt, value, parser):
61 cpp_includes = getattr(parser.values, option.dest)
62 cpp_includes.append(os.path.realpath(value))
65 def get_preprocessor_option_group(parser):
66 group = optparse.OptionGroup(parser, "Preprocessor options")
67 group.add_option("", "--cflags-begin",
68 help="Start preprocessor/compiler flags",
69 dest="cflags", default=[],
70 action="callback", callback=process_cflags_begin)
71 group.add_option("", "--cflags-end",
72 help="End preprocessor/compiler flags",
73 action="callback", callback=process_cflags_end)
74 group.add_option("-I", help="Pre-processor include file",
75 dest="cpp_includes", default=[], type="string",
76 action="callback", callback=process_cpp_includes)
77 group.add_option("-D", help="Pre-processor define",
78 action="append", dest="cpp_defines",
80 group.add_option("-U", help="Pre-processor undefine",
81 action="append", dest="cpp_undefines",
83 group.add_option("-p", dest="", help="Ignored")
87 def get_windows_option_group(parser):
88 group = optparse.OptionGroup(parser, "Machine Dependent Options")
89 group.add_option("-m", help="some machine dependent option",
90 action="append", dest='m_option',
96 def _get_option_parser():
97 parser = optparse.OptionParser('%prog [options] sources')
98 parser.add_option('', "--quiet",
99 action="store_true", dest="quiet",
101 help="If passed, do not print details of normal operation")
102 parser.add_option("", "--format",
103 action="store", dest="format",
105 help="format to use, one of gidl, gir")
106 parser.add_option("-i", "--include",
107 action="append", dest="includes", default=[],
108 help="Add specified gir file as dependency")
109 parser.add_option("", "--include-uninstalled",
110 action="append", dest="includes_uninstalled", default=[],
111 help=("""A file path to a dependency; only use this "
112 "when building multiple .gir files inside a "
114 parser.add_option("", "--add-include-path",
115 action="append", dest="include_paths", default=[],
116 help="include paths for other GIR files")
117 parser.add_option("", "--program",
118 action="store", dest="program", default=None,
119 help="program to execute")
120 parser.add_option("", "--program-arg",
121 action="append", dest="program_args", default=[],
122 help="extra arguments to program")
123 parser.add_option("", "--libtool",
124 action="store", dest="libtool_path", default=None,
125 help="full path to libtool")
126 parser.add_option("", "--no-libtool",
127 action="store_true", dest="nolibtool", default=False,
128 help="do not use libtool")
129 parser.add_option("", "--external-library",
130 action="store_true", dest="external_library", default=False,
131 help=("""If true, the library is located on the system,""" +
132 """not in the current directory"""))
133 parser.add_option("-l", "--library",
134 action="append", dest="libraries", default=[],
135 help="libraries of this unit")
136 parser.add_option("-L", "--library-path",
137 action="append", dest="library_paths", default=[],
138 help="directories to search for libraries")
139 parser.add_option("", "--header-only",
140 action="store_true", dest="header_only", default=[],
141 help="If specified, just generate a GIR for the given header files")
142 parser.add_option("-n", "--namespace",
143 action="store", dest="namespace_name",
144 help=("name of namespace for this unit, also "
145 "used to compute --identifier-prefix and --symbol-prefix"))
146 parser.add_option("", "--nsversion",
147 action="store", dest="namespace_version",
148 help="version of namespace for this unit")
149 parser.add_option("", "--strip-prefix",
150 action="store", dest="strip_prefix",
151 help="""Option --strip-prefix is deprecated, please see --identifier-prefix
152 and --symbol-prefix.""")
153 parser.add_option("", "--identifier-prefix",
154 action="append", dest="identifier_prefixes", default=[],
155 help="""Remove this prefix from C identifiers (structure typedefs, etc.).
156 May be specified multiple times. This is also used as the default for --symbol-prefix if
157 the latter is not specified.""")
158 parser.add_option("", "--symbol-prefix",
159 action="append", dest="symbol_prefixes", default=[],
160 help="Remove this prefix from C symbols (function names)")
161 parser.add_option("", "--accept-unprefixed",
162 action="store_true", dest="accept_unprefixed", default=False,
163 help="""If specified, accept symbols and identifiers that do not
164 match the namespace prefix.""")
165 parser.add_option("", "--add-init-section",
166 action="append", dest="init_sections", default=[],
167 help="add extra initialization code in the introspection program")
168 parser.add_option("-o", "--output",
169 action="store", dest="output", default="-",
170 help="output filename to write to, defaults to - (stdout)")
171 parser.add_option("", "--pkg",
172 action="append", dest="packages", default=[],
173 help="pkg-config packages to get cflags from")
174 parser.add_option("", "--pkg-export",
175 action="append", dest="packages_export", default=[],
176 help="Associated pkg-config packages for this library")
177 parser.add_option('', "--warn-all",
178 action="store_true", dest="warn_all", default=False,
179 help="If true, enable all warnings for introspection")
180 parser.add_option('', "--warn-error",
181 action="store_true", dest="warn_fatal",
182 help="Turn warnings into fatal errors")
183 parser.add_option("-v", "--verbose",
184 action="store_true", dest="verbose",
186 parser.add_option("", "--c-include",
187 action="append", dest="c_includes", default=[],
188 help="headers which should be included in C programs")
189 parser.add_option("", "--filelist",
190 action="store", dest="filelist", default=[],
191 help="file containing headers and sources to be scanned")
193 group = get_preprocessor_option_group(parser)
194 parser.add_option_group(group)
196 if os.environ.get('MSYSTEM') == 'MINGW32':
197 group = get_windows_option_group(parser)
198 parser.add_option_group(group)
201 parser.add_option('', "--generate-typelib-tests",
202 action="store", dest="test_codegen", default=None,
203 help=optparse.SUPPRESS_HELP)
204 parser.add_option('', "--passthrough-gir",
205 action="store", dest="passthrough_gir", default=None,
206 help=optparse.SUPPRESS_HELP)
207 parser.add_option('', "--reparse-validate",
208 action="store_true", dest="reparse_validate_gir", default=False,
209 help=optparse.SUPPRESS_HELP)
210 parser.add_option("", "--typelib-xml",
211 action="store_true", dest="typelib_xml",
212 help=optparse.SUPPRESS_HELP)
218 raise SystemExit('ERROR: %s' % (msg, ))
221 def passthrough_gir(path, f):
225 writer = GIRWriter(parser.get_namespace())
226 f.write(writer.get_xml())
229 def test_codegen(optstring):
230 (namespace, out_h_filename, out_c_filename) = optstring.split(',')
231 if namespace == 'Everything':
232 from .testcodegen import EverythingCodeGenerator
233 gen = EverythingCodeGenerator(out_h_filename, out_c_filename)
236 _error("Invaild namespace %r" % (namespace, ))
240 def process_options(output, allowed_flags):
241 for option in output.split():
242 for flag in allowed_flags:
243 if not option.startswith(flag):
249 def process_packages(options, packages):
250 args = ['pkg-config', '--cflags']
251 args.extend(packages)
252 output = subprocess.Popen(args,
253 stdout=subprocess.PIPE).communicate()[0]
255 # the error output should have already appeared on our stderr,
258 # Some pkg-config files on Windows have options we don't understand,
259 # so we explicitly filter to only the ones we need.
260 options_whitelist = ['-I', '-D', '-U', '-l', '-L']
261 filtered_output = list(process_options(output, options_whitelist))
262 parser = _get_option_parser()
263 pkg_options, unused = parser.parse_args(filtered_output)
264 options.cpp_includes.extend([os.path.realpath(f) for f in pkg_options.cpp_includes])
265 options.cpp_defines.extend(pkg_options.cpp_defines)
266 options.cpp_undefines.extend(pkg_options.cpp_undefines)
269 def extract_filenames(args):
272 # We don't support real C++ parsing yet, but we should be able
273 # to understand C API implemented in C++ files.
274 if os.path.splitext(arg)[1] in ALL_EXTS:
275 if not os.path.exists(arg):
276 _error('%s: no such a file or directory' % (arg, ))
277 # Make absolute, because we do comparisons inside scannerparser.c
278 # against the absolute path that cpp will give us
279 filenames.append(arg)
283 def extract_filelist(options):
285 if not os.path.exists(options.filelist):
286 _error('%s: no such filelist file' % (options.filelist, ))
287 filelist_file = open(options.filelist, "r")
288 lines = filelist_file.readlines()
290 # We don't support real C++ parsing yet, but we should be able
291 # to understand C API implemented in C++ files.
292 filename = line.strip()
293 if (filename.endswith('.c') or filename.endswith('.cpp')
294 or filename.endswith('.cc') or filename.endswith('.cxx')
295 or filename.endswith('.h') or filename.endswith('.hpp')
296 or filename.endswith('.hxx')):
297 if not os.path.exists(filename):
298 _error('%s: Invalid filelist entry-no such file or directory' % (line, ))
299 # Make absolute, because we do comparisons inside scannerparser.c
300 # against the absolute path that cpp will give us
301 filenames.append(filename)
305 def create_namespace(options):
306 if options.strip_prefix:
307 print """g-ir-scanner: warning: Option --strip-prefix has been deprecated;
308 see --identifier-prefix and --symbol-prefix."""
309 options.identifier_prefixes.append(options.strip_prefix)
311 # We do this dance because the empty list has different semantics from
312 # None; if the user didn't specify the options, we want to use None so
313 # the Namespace constructor picks the defaults.
314 if options.identifier_prefixes:
315 identifier_prefixes = options.identifier_prefixes
317 identifier_prefixes = None
318 if options.symbol_prefixes:
319 for prefix in options.symbol_prefixes:
320 # See Transformer._split_c_string_for_namespace_matches() for
321 # why this check is needed
322 if prefix.lower() != prefix:
323 _error("Values for --symbol-prefix must be entirely lowercase")
324 symbol_prefixes = options.symbol_prefixes
326 symbol_prefixes = None
328 return Namespace(options.namespace_name,
329 options.namespace_version,
330 identifier_prefixes=identifier_prefixes,
331 symbol_prefixes=symbol_prefixes)
334 def create_transformer(namespace, options):
335 transformer = Transformer(namespace,
336 accept_unprefixed=options.accept_unprefixed)
337 transformer.set_include_paths(options.include_paths)
338 if options.passthrough_gir:
339 transformer.disable_cache()
340 transformer.set_passthrough_mode()
342 for include in options.includes:
343 if os.sep in include:
344 _error("Invalid include path %r" % (include, ))
346 include_obj = Include.from_string(include)
348 _error("Malformed include %r\n" % (include, ))
349 transformer.register_include(include_obj)
350 for include_path in options.includes_uninstalled:
351 transformer.register_include_uninstalled(include_path)
356 def create_binary(transformer, options, args):
357 # Transform the C AST nodes into higher level
359 gdump_parser = GDumpParser(transformer)
361 # Do enough parsing that we have the get_type() functions to reference
362 # when creating the introspection binary
363 gdump_parser.init_parse()
366 args = [options.program]
367 args.extend(options.program_args)
368 binary = IntrospectionBinary(args)
370 binary = compile_introspection_binary(options,
371 gdump_parser.get_get_type_functions(),
372 gdump_parser.get_error_quark_functions())
374 shlibs = resolve_shlibs(options, binary, options.libraries)
375 gdump_parser.set_introspection_binary(binary)
380 def create_source_scanner(options, args):
381 if hasattr(options, 'filelist') and options.filelist:
382 filenames = extract_filelist(options)
384 filenames = extract_filenames(args)
386 # Run the preprocessor, tokenize and construct simple
387 # objects representing the raw C symbols
389 ss.set_cpp_options(options.cpp_includes,
391 options.cpp_undefines,
392 cflags=options.cflags)
393 ss.parse_files(filenames)
394 ss.parse_macros(filenames)
398 def write_output(data, options):
399 if options.output == "-":
401 elif options.reparse_validate_gir:
402 main_f, main_f_name = tempfile.mkstemp(suffix='.gir')
403 main_f = os.fdopen(main_f, 'w')
407 temp_f, temp_f_name = tempfile.mkstemp(suffix='.gir')
408 temp_f = os.fdopen(temp_f, 'w')
409 passthrough_gir(main_f_name, temp_f)
411 if not utils.files_are_identical(main_f_name, temp_f_name):
412 _error("Failed to re-parse gir file; scanned=%r passthrough=%r" % (
413 main_f_name, temp_f_name))
414 os.unlink(temp_f_name)
416 shutil.move(main_f_name, options.output)
418 if e.errno == errno.EPERM:
419 os.unlink(main_f_name)
425 output = open(options.output, "w")
427 _error("opening output for writing: %s" % (e.strerror, ))
432 _error("while writing output: %s" % (e.strerror, ))
435 def scanner_main(args):
436 parser = _get_option_parser()
437 (options, args) = parser.parse_args(args)
439 if options.passthrough_gir:
440 passthrough_gir(options.passthrough_gir, sys.stdout)
441 if options.test_codegen:
442 return test_codegen(options.test_codegen)
444 if hasattr(options, 'filelist') and not options.filelist:
446 _error('Need at least one filename')
448 if not options.namespace_name:
449 _error('Namespace name missing')
451 if options.format == 'gir':
452 from giscanner.girwriter import GIRWriter as Writer
454 _error("Unknown format: %s" % (options.format, ))
456 if not (options.libraries
458 or options.header_only):
459 _error("Must specify --program or --library")
461 namespace = create_namespace(options)
462 logger = message.MessageLogger.get(namespace=namespace)
464 logger.enable_warnings((message.WARNING, message.ERROR, message.FATAL))
466 transformer = create_transformer(namespace, options)
468 packages = set(options.packages)
469 packages.update(transformer.get_pkgconfig_packages())
471 exit_code = process_packages(options, packages)
475 ss = create_source_scanner(options, args)
477 cbp = GtkDocCommentBlockParser()
478 blocks = cbp.parse_comment_blocks(ss.get_comments())
480 # Transform the C symbols into AST nodes
481 transformer.parse(ss.get_symbols())
483 if not options.header_only:
484 shlibs = create_binary(transformer, options, args)
488 transformer.namespace.shared_libraries = shlibs
490 main = MainTransformer(transformer, blocks)
493 utils.break_on_debug_flag('tree')
495 final = IntrospectablePass(transformer, blocks)
498 warning_count = logger.get_warning_count()
499 if options.warn_fatal and warning_count > 0:
500 message.fatal("warnings configured as fatal")
502 elif warning_count > 0 and options.warn_all is False:
503 print ("g-ir-scanner: %s: warning: %d warnings suppressed (use --warn-all to see them)"
504 % (transformer.namespace.name, warning_count, ))
507 if options.packages_export:
508 exported_packages = options.packages_export
510 exported_packages = options.packages
512 transformer.namespace.c_includes = options.c_includes
513 transformer.namespace.exported_packages = exported_packages
514 writer = Writer(transformer.namespace)
515 data = writer.get_xml()
517 write_output(data, options)