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 AnnotationParser
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
42 from giscanner.transformer import Transformer
45 def get_preprocessor_option_group(parser):
46 group = optparse.OptionGroup(parser, "Preprocessor options")
47 group.add_option("-I", help="Pre-processor include file",
48 action="append", dest="cpp_includes",
50 group.add_option("-D", help="Pre-processor define",
51 action="append", dest="cpp_defines",
53 group.add_option("-U", help="Pre-processor undefine",
54 action="append", dest="cpp_undefines",
56 group.add_option("-p", dest="", help="Ignored")
59 def _get_option_parser():
60 parser = optparse.OptionParser('%prog [options] sources')
61 parser.add_option('', "--quiet",
62 action="store_true", dest="quiet",
64 help="If passed, do not print details of normal" \
66 parser.add_option("", "--format",
67 action="store", dest="format",
69 help="format to use, one of gidl, gir")
70 parser.add_option("-i", "--include",
71 action="append", dest="includes", default=[],
72 help="Add specified gir file as dependency")
73 parser.add_option("", "--include-uninstalled",
74 action="append", dest="includes_uninstalled", default=[],
75 help=("""A file path to a dependency; only use this "
76 "when building multiple .gir files inside a "
78 parser.add_option("", "--add-include-path",
79 action="append", dest="include_paths", default=[],
80 help="include paths for other GIR files")
81 parser.add_option("", "--program",
82 action="store", dest="program", default=None,
83 help="program to execute")
84 parser.add_option("", "--program-arg",
85 action="append", dest="program_args", default=[],
86 help="extra arguments to program")
87 parser.add_option("", "--libtool",
88 action="store", dest="libtool_path", default=None,
89 help="full path to libtool")
90 parser.add_option("", "--no-libtool",
91 action="store_true", dest="nolibtool", default=False,
92 help="do not use libtool")
93 parser.add_option("", "--external-library",
94 action="store_true", dest="external_library", default=False,
95 help="If true, the library is located on the system, not in the current directory")
96 parser.add_option("-l", "--library",
97 action="append", dest="libraries", default=[],
98 help="libraries of this unit")
99 parser.add_option("-L", "--library-path",
100 action="append", dest="library_paths", default=[],
101 help="directories to search for libraries")
102 parser.add_option("-n", "--namespace",
103 action="store", dest="namespace_name",
104 help=("name of namespace for this unit, also "
105 "used to compute --identifier-prefix and --symbol-prefix"))
106 parser.add_option("", "--nsversion",
107 action="store", dest="namespace_version",
108 help="version of namespace for this unit")
109 parser.add_option("", "--strip-prefix",
110 action="store", dest="strip_prefix",
111 help="""Option --strip-prefix is deprecated, please see --identifier-prefix
112 and --symbol-prefix.""")
113 parser.add_option("", "--identifier-prefix",
114 action="append", dest="identifier_prefixes", default=[],
115 help="""Remove this prefix from C identifiers (structure typedefs, etc.).
116 May be specified multiple times. This is also used as the default for --symbol-prefix if
117 the latter is not specified.""")
118 parser.add_option("", "--symbol-prefix",
119 action="append", dest="symbol_prefixes", default=[],
120 help="Remove this prefix from C symbols (function names)")
121 parser.add_option("", "--accept-unprefixed",
122 action="store_true", dest="accept_unprefixed", default=False,
123 help="""If specified, accept symbols and identifiers that do not
124 match the namespace prefix.""")
125 parser.add_option("", "--add-init-section",
126 action="append", dest="init_sections", default=[],
127 help="add extra initialization code in the introspection program")
128 parser.add_option("-o", "--output",
129 action="store", dest="output", default="-",
130 help="output filename to write to, defaults to - (stdout)")
131 parser.add_option("", "--pkg",
132 action="append", dest="packages", default=[],
133 help="pkg-config packages to get cflags from")
134 parser.add_option("", "--pkg-export",
135 action="append", dest="packages_export", default=[],
136 help="Associated pkg-config packages for this library")
137 parser.add_option('', "--warn-all",
138 action="store_true", dest="warn_all", default=False,
139 help="If true, enable all warnings for introspection")
140 parser.add_option('', "--warn-error",
141 action="store_true", dest="warn_fatal",
142 help="Turn warnings into fatal errors")
143 parser.add_option("-v", "--verbose",
144 action="store_true", dest="verbose",
146 parser.add_option("", "--c-include",
147 action="append", dest="c_includes", default=[],
148 help="headers which should be included in C programs")
150 group = get_preprocessor_option_group(parser)
151 parser.add_option_group(group)
154 parser.add_option('', "--generate-typelib-tests",
155 action="store", dest="test_codegen", default=None,
156 help=optparse.SUPPRESS_HELP)
157 parser.add_option('', "--passthrough-gir",
158 action="store", dest="passthrough_gir", default=None,
159 help=optparse.SUPPRESS_HELP)
160 parser.add_option('', "--reparse-validate",
161 action="store_true", dest="reparse_validate_gir", default=False,
162 help=optparse.SUPPRESS_HELP)
163 parser.add_option("", "--typelib-xml",
164 action="store_true", dest="typelib_xml",
165 help=optparse.SUPPRESS_HELP)
171 raise SystemExit('ERROR: %s' % (msg, ))
173 def passthrough_gir(path, f):
177 writer = GIRWriter(parser.get_namespace(),
178 parser.get_shared_libraries(),
179 parser.get_includes(),
180 parser.get_pkgconfig_packages(),
181 parser.get_c_includes())
182 f.write(writer.get_xml())
184 def test_codegen(optstring):
185 (namespace, out_h_filename, out_c_filename) = optstring.split(',')
186 if namespace == 'Everything':
187 from .testcodegen import EverythingCodeGenerator
188 gen = EverythingCodeGenerator(out_h_filename, out_c_filename)
191 _error("Invaild namespace %r" % (namespace, ))
194 def process_options(output, allowed_flags):
195 for option in output.split():
196 for flag in allowed_flags:
197 if not option.startswith(flag):
202 def process_packages(options, packages):
203 args = ['pkg-config', '--cflags']
204 args.extend(packages)
205 output = subprocess.Popen(args,
206 stdout=subprocess.PIPE).communicate()[0]
208 # the error output should have already appeared on our stderr,
211 # Some pkg-config files on Windows have options we don't understand,
212 # so we explicitly filter to only the ones we need.
213 options_whitelist = ['-I', '-D', '-U', '-l', '-L']
214 filtered_output = list(process_options(output, options_whitelist))
215 parser = _get_option_parser()
216 pkg_options, unused = parser.parse_args(filtered_output)
217 options.cpp_includes.extend(pkg_options.cpp_includes)
218 options.cpp_defines.extend(pkg_options.cpp_defines)
219 options.cpp_undefines.extend(pkg_options.cpp_undefines)
221 def extract_filenames(args):
224 # We don't support real C++ parsing yet, but we should be able
225 # to understand C API implemented in C++ files.
226 if (arg.endswith('.c') or arg.endswith('.cpp') or
227 arg.endswith('.cc') or arg.endswith('.cxx') or
228 arg.endswith('.h') or arg.endswith('.hpp') or
229 arg.endswith('.hxx')):
230 if not os.path.exists(arg):
231 _error('%s: no such a file or directory' % (arg, ))
232 # Make absolute, because we do comparisons inside scannerparser.c
233 # against the absolute path that cpp will give us
234 filenames.append(os.path.abspath(arg))
237 def create_namespace(options):
238 if options.strip_prefix:
239 print """g-ir-scanner: warning: Option --strip-prefix has been deprecated;
240 see --identifier-prefix and --symbol-prefix."""
241 options.identifier_prefixes.append(options.strip_prefix)
243 # We do this dance because the empty list has different semantics from
244 # None; if the user didn't specify the options, we want to use None so
245 # the Namespace constructor picks the defaults.
246 if options.identifier_prefixes:
247 identifier_prefixes = options.identifier_prefixes
249 identifier_prefixes = None
250 if options.symbol_prefixes:
251 for prefix in options.symbol_prefixes:
252 # See Transformer._split_c_string_for_namespace_matches() for
253 # why this check is needed
254 if prefix.lower() != prefix:
255 _error("Values for --symbol-prefix must be entirely lowercase")
256 symbol_prefixes = options.symbol_prefixes
258 symbol_prefixes = None
260 return Namespace(options.namespace_name,
261 options.namespace_version,
262 identifier_prefixes=identifier_prefixes,
263 symbol_prefixes=symbol_prefixes)
265 def create_transformer(namespace, options):
266 transformer = Transformer(namespace,
267 accept_unprefixed=options.accept_unprefixed)
268 transformer.set_include_paths(options.include_paths)
269 if options.passthrough_gir:
270 transformer.disable_cache()
271 transformer.set_passthrough_mode()
273 shown_include_warning = False
274 for include in options.includes:
275 if os.sep in include:
276 _error("Invalid include path %r" % (include, ))
278 include_obj = Include.from_string(include)
280 _error("Malformed include %r\n" % (include, ))
281 transformer.register_include(include_obj)
282 for include_path in options.includes_uninstalled:
283 transformer.register_include_uninstalled(include_path)
287 def create_binary(transformer, options, args):
288 # Transform the C AST nodes into higher level
290 gdump_parser = GDumpParser(transformer)
292 # Do enough parsing that we have the get_type() functions to reference
293 # when creating the introspection binary
294 gdump_parser.init_parse()
297 args=[options.program]
298 args.extend(options.program_args)
299 binary = IntrospectionBinary(args)
301 binary = compile_introspection_binary(options,
302 gdump_parser.get_get_type_functions())
304 shlibs = resolve_shlibs(options, binary, options.libraries)
305 gdump_parser.set_introspection_binary(binary)
309 def create_source_scanner(options, args):
310 filenames = extract_filenames(args)
312 # Run the preprocessor, tokenize and construct simple
313 # objects representing the raw C symbols
315 ss.set_cpp_options(options.cpp_includes,
317 options.cpp_undefines)
318 ss.parse_files(filenames)
319 ss.parse_macros(filenames)
322 def write_output(data, options):
323 if options.output == "-":
325 elif options.reparse_validate_gir:
326 main_f, main_f_name = tempfile.mkstemp(suffix='.gir')
327 main_f = os.fdopen(main_f, 'w')
331 temp_f, temp_f_name = tempfile.mkstemp(suffix='.gir')
332 temp_f = os.fdopen(temp_f, 'w')
333 passthrough_gir(main_f_name, temp_f)
335 if not utils.files_are_identical(main_f_name, temp_f_name):
336 _error("Failed to re-parse gir file; scanned=%r passthrough=%r" % (
337 main_f_name, temp_f_name))
338 os.unlink(temp_f_name)
340 shutil.move(main_f_name, options.output)
342 if e.errno == errno.EPERM:
343 os.unlink(main_f_name)
349 output = open(options.output, "w")
351 _error("opening output for writing: %s" % (e.strerror, ))
356 _error("while writing output: %s" % (e.strerror, ))
358 def scanner_main(args):
359 parser = _get_option_parser()
360 (options, args) = parser.parse_args(args)
362 if options.passthrough_gir:
363 passthrough_gir(options.passthrough_gir, sys.stdout)
364 if options.test_codegen:
365 return test_codegen(options.test_codegen)
368 _error('Need at least one filename')
370 if not options.namespace_name:
371 _error('Namespace name missing')
373 if options.format == 'gir':
374 from giscanner.girwriter import GIRWriter as Writer
376 _error("Unknown format: %s" % (options.format, ))
378 if not (options.libraries or options.program):
379 _error("Must specify --program or --library")
381 namespace = create_namespace(options)
382 logger = message.MessageLogger.get(namespace=namespace)
384 logger.enable_warnings(True)
385 transformer = create_transformer(namespace, options)
387 packages = set(options.packages)
388 packages.update(transformer.get_pkgconfig_packages())
389 exit_code = process_packages(options, packages)
393 ss = create_source_scanner(options, args)
395 # Transform the C symbols into AST nodes
396 transformer.parse(ss.get_symbols())
398 shlibs = create_binary(transformer, options, args)
400 ap = AnnotationParser()
401 blocks = ap.parse(ss.get_comments())
403 main = MainTransformer(transformer, blocks)
406 utils.break_on_debug_flag('tree')
408 final = IntrospectablePass(transformer, blocks)
411 warning_count = logger.get_warning_count()
412 if options.warn_fatal and warning_count > 0:
413 message.fatal("warnings configured as fatal")
415 elif warning_count > 0 and options.warn_all is False:
416 print ("g-ir-scanner: %s: warning: %d warnings suppressed (use --warn-all to see them)"
417 % (transformer.namespace.name, warning_count, ))
420 if options.packages_export:
421 exported_packages = options.packages_export
423 exported_packages = options.packages
425 writer = Writer(transformer.namespace, shlibs, transformer.get_includes(),
426 exported_packages, options.c_includes)
427 data = writer.get_xml()
429 write_output(data, options)