Support introspectable=no attribute, add warnings framework
[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 optparse
25 import os
26 import sys
27
28 from giscanner.annotationparser import AnnotationParser, InvalidAnnotationError
29 from giscanner.ast import Include
30 from giscanner.cachestore import CacheStore
31 from giscanner.dumper import compile_introspection_binary
32 from giscanner.glibtransformer import GLibTransformer, IntrospectionBinary
33 from giscanner.minixpath import myxpath, xpath_assert
34 from giscanner.sourcescanner import SourceScanner
35 from giscanner.shlibs import resolve_shlibs
36 from giscanner.transformer import Transformer
37
38 def _get_option_parser():
39     parser = optparse.OptionParser('%prog [options] sources')
40     parser.add_option("", "--format",
41                       action="store", dest="format",
42                       default="gir",
43                       help="format to use, one of gidl, gir")
44     parser.add_option("-i", "--include",
45                       action="append", dest="includes", default=[],
46                       help="include types for other gidls")
47     parser.add_option("", "--add-include-path",
48                       action="append", dest="include_paths", default=[],
49                       help="include paths for other GIR files")
50     parser.add_option("", "--program",
51                       action="store", dest="program", default=None,
52                       help="program to execute")
53     parser.add_option("", "--program-arg",
54                       action="append", dest="program_args", default=[],
55                       help="extra arguments to program")
56     parser.add_option("", "--libtool",
57                       action="store", dest="libtool_path", default=None,
58                       help="full path to libtool")
59     parser.add_option("", "--no-libtool",
60                       action="store_true", dest="nolibtool", default=False,
61                       help="do not use libtool")
62     parser.add_option("-l", "--library",
63                       action="append", dest="libraries", default=[],
64                       help="libraries of this unit")
65     parser.add_option("-L", "--library-path",
66                       action="append", dest="library_paths", default=[],
67                       help="directories to search for libraries")
68     parser.add_option("-n", "--namespace",
69                       action="store", dest="namespace_name",
70                       help=("name of namespace for this unit, also "
71                             "used as --strip-prefix default"))
72     parser.add_option("", "--nsversion",
73                       action="store", dest="namespace_version",
74                       help="version of namespace for this unit")
75     parser.add_option("", "--strip-prefix",
76                       action="store", dest="strip_prefix", default=None,
77                       help="remove this prefix from objects and functions")
78     parser.add_option("", "--add-init-section",
79                       action="append", dest="init_sections", default=[],
80             help="add extra initialization code in the introspection program")
81     parser.add_option("-o", "--output",
82                       action="store", dest="output",
83                       help="output to writeout, defaults to stdout")
84     parser.add_option("", "--pkg",
85                       action="append", dest="packages", default=[],
86                       help="pkg-config packages to get cflags from")
87     parser.add_option("", "--pkg-export",
88                       action="append", dest="packages_export", default=[],
89                       help="Associated pkg-config packages for this library")
90     parser.add_option('', "--Wall",
91                       action="store_true", dest="warn_all", default=False,
92                       help="If true, enable all warnings for introspection")
93     parser.add_option('', "--Werror",
94                       action="store_true", dest="warn_fatal",
95                       help="Turn warnings into fatal errors")
96     parser.add_option("-v", "--verbose",
97                       action="store_true", dest="verbose",
98                       help="be verbose")
99     parser.add_option("", "--noclosure",
100                       action="store_true", dest="noclosure",
101                       help="do not delete unknown types")
102     parser.add_option("", "--typelib-xml",
103                       action="store_true", dest="typelib_xml",
104                       help="Just convert GIR to typelib XML")
105     parser.add_option("", "--inject",
106                       action="store_true", dest="inject",
107                       help="Inject additional components into GIR XML")
108     parser.add_option("", "--xpath-assertions",
109                       action="store", dest="xpath_assertions",
110             help="Use given file to create assertions on GIR content")
111     parser.add_option("", "--c-include",
112                       action="append", dest="c_includes", default=[],
113                       help="headers which should be included in C programs")
114
115     group = optparse.OptionGroup(parser, "Preprocessor options")
116     group.add_option("-I", help="Pre-processor include file",
117                      action="append", dest="cpp_includes",
118                      default=[])
119     group.add_option("-D", help="Pre-processor define",
120                      action="append", dest="cpp_defines",
121                      default=[])
122     group.add_option("-U", help="Pre-processor undefine",
123                      action="append", dest="cpp_undefines",
124                      default=[])
125     group.add_option("-p", dest="", help="Ignored")
126     parser.add_option_group(group)
127
128     return parser
129
130
131 def _error(msg):
132     raise SystemExit('ERROR: %s' % (msg, ))
133
134 def typelib_xml_strip(path):
135     from giscanner.girparser import GIRParser
136     from giscanner.girwriter import GIRWriter
137     from giscanner.girparser import C_NS
138     from xml.etree.cElementTree import parse
139
140     c_ns_key = '{%s}' % (C_NS, )
141
142     tree = parse(path)
143     root = tree.getroot()
144     for node in root.getiterator():
145         for attrib in list(node.attrib):
146             if attrib.startswith(c_ns_key):
147                 del node.attrib[attrib]
148     parser = GIRParser()
149     parser.parse_tree(tree)
150
151     writer = GIRWriter(parser.get_namespace(),
152                        parser.get_shared_libraries(),
153                        parser.get_includes())
154     sys.stdout.write(writer.get_xml())
155     return 0
156
157 def inject(path, additions, outpath):
158     from giscanner.girparser import GIRParser
159     from giscanner.girwriter import GIRWriter
160     from xml.etree.cElementTree import parse
161
162     tree = parse(path)
163     root = tree.getroot()
164     injectDoc = parse(open(additions))
165     for node in injectDoc.getroot():
166         injectPath = node.attrib['path']
167         target = myxpath(root, injectPath)
168         if not target:
169             raise ValueError("Couldn't find path %r" % (injectPath, ))
170         for child in node:
171             target.append(child)
172
173     parser = GIRParser()
174     parser.parse_tree(tree)
175     writer = GIRWriter(parser.get_namespace(),
176                        parser.get_shared_libraries(),
177                        parser.get_includes())
178     outf = open(outpath, 'w')
179     outf.write(writer.get_xml())
180     outf.close()
181     return 0
182
183 def validate(assertions, path):
184     from xml.etree.cElementTree import parse
185     doc = parse(open(path))
186     root = doc.getroot()
187     f = open(assertions)
188     assertions_list = f.readlines()
189     for assertion in assertions_list:
190         assertion = assertion.strip()
191         xpath_assert(root, assertion)
192     f.close()
193     return 0
194
195 def process_options(output, allowed_flags):
196     for option in output.split():
197         for flag in allowed_flags:
198             if not option.startswith(flag):
199                 continue
200             yield option
201             break
202
203 def process_packages(parser, options, packages):
204     args = ['pkg-config', '--cflags']
205     args.extend(packages)
206     output = subprocess.Popen(args,
207                               stdout=subprocess.PIPE).communicate()[0]
208     if output is None:
209         # the error output should have already appeared on our stderr,
210         # so we just exit
211         sys.exit(1)
212     # Some pkg-config files on Windows have options we don't understand,
213     # so we explicitly filter to only the ones we need.
214     options_whitelist = ['-I', '-D', '-U', '-l', '-L']
215     filtered_output = list(process_options(output, options_whitelist))
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)
220
221     args = ['pkg-config', '--libs-only-L']
222     args.extend(packages)
223     output = subprocess.Popen(args,
224                               stdout=subprocess.PIPE).communicate()[0]
225     if output is None:
226         sys.exit(1)
227     filtered_output = list(process_options(output, options_whitelist))
228     pkg_options, unused = parser.parse_args(filtered_output)
229     options.library_paths.extend(pkg_options.library_paths)
230
231 def scanner_main(args):
232     parser = _get_option_parser()
233     (options, args) = parser.parse_args(args)
234
235     if len(args) <= 1:
236         _error('Need at least one filename')
237
238     if options.typelib_xml:
239         return typelib_xml_strip(args[1])
240
241     if options.inject:
242         if len(args) != 4:
243             _error('Need three filenames; e.g. g-ir-scanner '
244                    '--inject Source.gir Additions.xml SourceOut.gir')
245         return inject(*args[1:4])
246
247     if options.xpath_assertions:
248         return validate(options.xpath_assertions, args[1])
249
250     if not options.namespace_name:
251         _error('Namespace name missing')
252
253     if options.format == 'gir':
254         from giscanner.girwriter import GIRWriter as Writer
255     else:
256         _error("Unknown format: %s" % (options.format, ))
257
258     if not (options.libraries or options.program):
259         _error("Must specify --program or --library")
260     libraries = options.libraries
261
262     filenames = []
263     for arg in args:
264         if (arg.endswith('.c') or
265             arg.endswith('.h')):
266             if not os.path.exists(arg):
267                 _error('%s: no such a file or directory' % (arg, ))
268             # Make absolute, because we do comparisons inside scannerparser.c
269             # against the absolute path that cpp will give us
270             filenames.append(os.path.abspath(arg))
271
272     cachestore = CacheStore()
273     transformer = Transformer(cachestore,
274                               options.namespace_name,
275                               options.namespace_version)
276     if options.strip_prefix:
277         transformer.set_strip_prefix(options.strip_prefix)
278     else:
279         transformer.set_strip_prefix(options.namespace_name)
280     if options.warn_all:
281         transformer.enable_warnings(True)
282     transformer.set_include_paths(options.include_paths)
283     shown_include_warning = False
284     for include in options.includes:
285         if os.sep in include:
286             raise ValueError("Invalid include path %r" % (include, ))
287         try:
288             include_obj = Include.from_string(include)
289         except:
290             sys.stderr.write("Malformed include %r\n" % (include, ))
291             sys.exit(1)
292         transformer.register_include(include_obj)
293
294     packages = set(options.packages)
295     packages.update(transformer.get_pkgconfig_packages())
296     process_packages(parser, options, packages)
297
298     # Run the preprocessor, tokenize and construct simple
299     # objects representing the raw C symbols
300     ss = SourceScanner()
301     ss.set_cpp_options(options.cpp_includes,
302                        options.cpp_defines,
303                        options.cpp_undefines)
304     ss.parse_files(filenames)
305     ss.parse_macros(filenames)
306
307     # Transform the C symbols into AST nodes
308     transformer.set_source_ast(ss)
309
310     # Transform the C AST nodes into higher level
311     # GLib/GObject nodes
312     glibtransformer = GLibTransformer(transformer,
313                                       noclosure=options.noclosure)
314
315     # Do enough parsing that we have the get_type() functions to reference
316     # when creating the introspection binary
317     glibtransformer.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                             glibtransformer.get_get_type_functions())
326
327     shlibs = resolve_shlibs(options, binary, libraries)
328
329     glibtransformer.set_introspection_binary(binary)
330
331     namespace = glibtransformer.parse()
332
333     ap = AnnotationParser(namespace, ss, transformer)
334     try:
335         ap.parse()
336     except InvalidAnnotationError, e:
337         raise SystemExit("ERROR in annotation: %s" % (str(e), ))
338
339     glibtransformer.final_analyze()
340
341     if options.warn_fatal and transformer.did_warn():
342         return 1
343
344     # Write out AST
345     if options.packages_export:
346         exported_packages = options.packages_export
347     else:
348         exported_packages = options.packages
349     writer = Writer(namespace, shlibs, transformer.get_includes(),
350                     exported_packages, options.c_includes,
351                     transformer.get_strip_prefix())
352     data = writer.get_xml()
353     if options.output:
354         fd = open(options.output, "w")
355         fd.write(data)
356     else:
357         print data
358
359     return 0