[scanner] Print error instead of throwing for unknown include
[gnome.gobject-introspection] / 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("-v", "--verbose",
91                       action="store_true", dest="verbose",
92                       help="be verbose")
93     parser.add_option("", "--noclosure",
94                       action="store_true", dest="noclosure",
95                       help="do not delete unknown types")
96     parser.add_option("", "--typelib-xml",
97                       action="store_true", dest="typelib_xml",
98                       help="Just convert GIR to typelib XML")
99     parser.add_option("", "--inject",
100                       action="store_true", dest="inject",
101                       help="Inject additional components into GIR XML")
102     parser.add_option("", "--xpath-assertions",
103                       action="store", dest="xpath_assertions",
104             help="Use given file to create assertions on GIR content")
105     parser.add_option("", "--c-include",
106                       action="append", dest="c_includes", default=[],
107                       help="headers which should be included in C programs")
108
109     group = optparse.OptionGroup(parser, "Preprocessor options")
110     group.add_option("-I", help="Pre-processor include file",
111                      action="append", dest="cpp_includes",
112                      default=[])
113     group.add_option("-D", help="Pre-processor define",
114                      action="append", dest="cpp_defines",
115                      default=[])
116     group.add_option("-U", help="Pre-processor undefine",
117                      action="append", dest="cpp_undefines",
118                      default=[])
119     group.add_option("-p", dest="", help="Ignored")
120     parser.add_option_group(group)
121
122     return parser
123
124
125 def _error(msg):
126     raise SystemExit('ERROR: %s' % (msg, ))
127
128 def typelib_xml_strip(path):
129     from giscanner.girparser import GIRParser
130     from giscanner.girwriter import GIRWriter
131     from giscanner.girparser import C_NS
132     from xml.etree.cElementTree import parse
133
134     c_ns_key = '{%s}' % (C_NS, )
135
136     tree = parse(path)
137     root = tree.getroot()
138     for node in root.getiterator():
139         for attrib in list(node.attrib):
140             if attrib.startswith(c_ns_key):
141                 del node.attrib[attrib]
142     parser = GIRParser()
143     parser.parse_tree(tree)
144
145     writer = GIRWriter(parser.get_namespace(),
146                        parser.get_shared_libraries(),
147                        parser.get_includes())
148     sys.stdout.write(writer.get_xml())
149     return 0
150
151 def inject(path, additions, outpath):
152     from giscanner.girparser import GIRParser
153     from giscanner.girwriter import GIRWriter
154     from xml.etree.cElementTree import parse
155
156     tree = parse(path)
157     root = tree.getroot()
158     injectDoc = parse(open(additions))
159     for node in injectDoc.getroot():
160         injectPath = node.attrib['path']
161         target = myxpath(root, injectPath)
162         if not target:
163             raise ValueError("Couldn't find path %r" % (injectPath, ))
164         for child in node:
165             target.append(child)
166
167     parser = GIRParser()
168     parser.parse_tree(tree)
169     writer = GIRWriter(parser.get_namespace(),
170                        parser.get_shared_libraries(),
171                        parser.get_includes())
172     outf = open(outpath, 'w')
173     outf.write(writer.get_xml())
174     outf.close()
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 len(args) <= 1:
230         _error('Need at least one filename')
231
232     if options.typelib_xml:
233         return typelib_xml_strip(args[1])
234
235     if options.inject:
236         if len(args) != 4:
237             _error('Need three filenames; e.g. g-ir-scanner '
238                    '--inject Source.gir Additions.xml SourceOut.gir')
239         return inject(*args[1:4])
240
241     if options.xpath_assertions:
242         return validate(options.xpath_assertions, args[1])
243
244     if not options.namespace_name:
245         _error('Namespace name missing')
246
247     if options.format == 'gir':
248         from giscanner.girwriter import GIRWriter as Writer
249     else:
250         _error("Unknown format: %s" % (options.format, ))
251
252     if not (options.libraries or options.program):
253         _error("Must specify --program or --library")
254     libraries = options.libraries
255
256     filenames = []
257     for arg in args:
258         if (arg.endswith('.c') or
259             arg.endswith('.h')):
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     cachestore = CacheStore()
267     transformer = Transformer(cachestore,
268                               options.namespace_name,
269                               options.namespace_version)
270     if options.strip_prefix:
271         transformer.set_strip_prefix(options.strip_prefix)
272     else:
273         transformer.set_strip_prefix(options.namespace_name)
274     transformer.set_include_paths(options.include_paths)
275     shown_include_warning = False
276     for include in options.includes:
277         if os.sep in include:
278             raise ValueError("Invalid include path %r" % (include, ))
279         try:
280             include_obj = Include.from_string(include)
281         except:
282             sys.stderr.write("Malformed include %r\n" % (include, ))
283             sys.exit(1)
284         transformer.register_include(include_obj)
285
286     packages = set(options.packages)
287     packages.update(transformer.get_pkgconfig_packages())
288     process_packages(parser, options, packages)
289
290     # Run the preprocessor, tokenize and construct simple
291     # objects representing the raw C symbols
292     ss = SourceScanner()
293     ss.set_cpp_options(options.cpp_includes,
294                        options.cpp_defines,
295                        options.cpp_undefines)
296     ss.parse_files(filenames)
297     ss.parse_macros(filenames)
298
299     # Transform the C symbols into AST nodes
300     transformer.set_source_ast(ss)
301
302     # Transform the C AST nodes into higher level
303     # GLib/GObject nodes
304     glibtransformer = GLibTransformer(transformer,
305                                       noclosure=options.noclosure)
306
307     # Do enough parsing that we have the get_type() functions to reference
308     # when creating the introspection binary
309     glibtransformer.init_parse()
310
311     if options.program:
312         args=[options.program]
313         args.extend(options.program_args)
314         binary = IntrospectionBinary(args)
315     else:
316         binary = compile_introspection_binary(options,
317                             glibtransformer.get_get_type_functions())
318
319     shlibs = resolve_shlibs(options, binary, libraries)
320
321     glibtransformer.set_introspection_binary(binary)
322
323     namespace = glibtransformer.parse()
324
325     ap = AnnotationParser(namespace, ss, transformer)
326     try:
327         ap.parse()
328     except InvalidAnnotationError, e:
329         raise SystemExit("ERROR in annotation: %s" % (str(e), ))
330
331     # Write out AST
332     if options.packages_export:
333         exported_packages = options.packages_export
334     else:
335         exported_packages = options.packages
336     writer = Writer(namespace, shlibs, transformer.get_includes(),
337                     exported_packages, options.c_includes,
338                     transformer.get_strip_prefix())
339     data = writer.get_xml()
340     if options.output:
341         fd = open(options.output, "w")
342         fd.write(data)
343     else:
344         print data
345
346     return 0