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