Execute the dumper program through libtool if it's installed, so we avoid
[platform/upstream/gobject-introspection.git] / tools / g-ir-scanner
1 #!/usr/bin/env python
2 # -*- Mode: Python -*-
3 # GObject-Introspection - a framework for introspecting GObject libraries
4 # Copyright (C) 2008  Johan Dahlin
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20 #
21
22 import subprocess
23 import optparse
24 import os
25 import sys
26
27 # This only works on unix systems
28 currentdir = os.path.dirname(os.path.abspath(sys.argv[0]))
29 basedir = os.path.abspath(os.path.join(currentdir, '..'))
30 if (os.path.exists(os.path.join(basedir, '.svn')) or
31     os.path.exists(os.path.join(basedir, '.git'))):
32     path = basedir
33 else:
34     libdir = 'lib'
35     for p in sys.path:
36         if 'lib64' in p:
37             libdir = 'lib64'
38             break
39
40     path = os.path.join(basedir, libdir, 'python%d.%d' % sys.version_info[:2],
41                         'site-packages')
42 sys.path.insert(0, path)
43
44 from giscanner.ast import Include
45 from giscanner.cachestore import CacheStore
46 from giscanner.dumper import compile_introspection_binary
47 from giscanner.glibtransformer import GLibTransformer, IntrospectionBinary
48 from giscanner.minixpath import myxpath, xpath_assert
49 from giscanner.sourcescanner import SourceScanner
50 from giscanner.transformer import Transformer
51
52 def _get_option_parser():
53     parser = optparse.OptionParser('%prog [options] sources')
54     parser.add_option("", "--format",
55                       action="store", dest="format",
56                       default="gir",
57                       help="format to use, one of gidl, gir")
58     parser.add_option("-i", "--include",
59                       action="append", dest="includes", default=[],
60                       help="include types for other gidls")
61     parser.add_option("", "--add-include-path",
62                       action="append", dest="include_paths", default=[],
63                       help="include paths for other GIR files")
64     parser.add_option("", "--program",
65                       action="store", dest="program", default=None,
66                       help="program to execute")
67     parser.add_option("", "--program-arg",
68                       action="append", dest="program_args", default=[],
69                       help="extra arguments to program")
70     parser.add_option("", "--no-libtool",
71                       action="store_true", dest="nolibtool", default=False,
72                       help="use libtool")
73     parser.add_option("-l", "--library",
74                       action="append", dest="libraries", default=[],
75                       help="libraries of this unit")
76     parser.add_option("-L", "--library-path",
77                       action="append", dest="library_paths", default=[],
78                       help="directories to search for libraries")
79     parser.add_option("-n", "--namespace",
80                       action="store", dest="namespace_name",
81                       help=("name of namespace for this unit, also "
82                             "used as --strip-prefix default"))
83     parser.add_option("", "--nsversion",
84                       action="store", dest="namespace_version",
85                       help="version of namespace for this unit")
86     parser.add_option("", "--strip-prefix",
87                       action="store", dest="strip_prefix", default=None,
88                       help="remove this prefix from objects and functions")
89     parser.add_option("-o", "--output",
90                       action="store", dest="output",
91                       help="output to writeout, defaults to stdout")
92     parser.add_option("", "--pkg",
93                       action="append", dest="packages", default=[],
94                       help="pkg-config packages to get cflags from")
95     parser.add_option("-v", "--verbose",
96                       action="store_true", dest="verbose",
97                       help="be verbose")
98     parser.add_option("", "--noclosure",
99                       action="store_true", dest="noclosure",
100                       help="do not delete unknown types")
101     parser.add_option("", "--typelib-xml",
102                       action="store_true", dest="typelib_xml",
103                       help="Just convert GIR to typelib XML")
104     parser.add_option("", "--inject",
105                       action="store_true", dest="inject",
106                       help="Inject additional components into GIR XML")
107     parser.add_option("", "--xpath-assertions",
108                       action="store", dest="xpath_assertions",
109                       help="Use given file to create assertions on GIR content")
110
111     group = optparse.OptionGroup(parser, "Preprocessor options")
112     group.add_option("-I", help="Pre-processor include file",
113                      action="append", dest="cpp_includes",
114                      default=[])
115     group.add_option("-D", help="Pre-processor define",
116                      action="append", dest="cpp_defines",
117                      default=[])
118     group.add_option("-U", help="Pre-processor undefine",
119                      action="append", dest="cpp_undefines",
120                      default=[])
121     group.add_option("-p", dest="", help="Ignored")
122     parser.add_option_group(group)
123
124     return parser
125
126
127 def _error(msg):
128     raise SystemExit('ERROR: %s' % (msg, ))
129
130 def typelib_xml_strip(path):
131     from giscanner.girparser import GIRParser
132     from giscanner.girwriter import GIRWriter
133     from giscanner.girparser import C_NS
134     from xml.etree.cElementTree import parse
135
136     c_ns_key = '{%s}' % (C_NS, )
137
138     tree = parse(path)
139     root = tree.getroot()
140     for node in root.getiterator():
141         for attrib in list(node.attrib):
142             if attrib.startswith(c_ns_key):
143                 del node.attrib[attrib]
144     parser = GIRParser()
145     parser.parse_tree(tree)
146
147     writer = GIRWriter(parser.get_namespace(),
148                        parser.get_shared_libraries(),
149                        parser.get_includes())
150     sys.stdout.write(writer.get_xml())
151     return 0
152
153 def inject(path, additions, outpath):
154     from giscanner.girparser import GIRParser
155     from giscanner.girwriter import GIRWriter
156     from xml.etree.cElementTree import parse
157
158     tree = parse(path)
159     root = tree.getroot()
160     injectDoc = parse(open(additions))
161     for node in injectDoc.getroot():
162         injectPath = node.attrib['path']
163         target = myxpath(root, injectPath)
164         if not target:
165             raise ValueError("Couldn't find path %r" % (injectPath, ))
166         for child in node:
167             target.append(child)
168
169     parser = GIRParser()
170     parser.parse_tree(tree)
171     writer = GIRWriter(parser.get_namespace(),
172                        parser.get_shared_libraries(),
173                        parser.get_includes())
174     outf = open(outpath, 'w')
175     outf.write(writer.get_xml())
176     outf.close()
177     return 0
178
179 def validate(assertions, path):
180     from xml.etree.cElementTree import parse
181     doc = parse(open(path))
182     root = doc.getroot()
183     f = open(assertions)
184     assertions_list = f.readlines()
185     print "=== CHECKING %s (%d assertions) ===" % (assertions,
186                                                    len(assertions_list))
187     for assertion in assertions_list:
188         assertion = assertion.strip()
189         xpath_assert(root, assertion)
190     f.close()
191     print "=== PASSED %s ===" % (assertions, )
192     return 0
193
194 def main(args):
195     parser = _get_option_parser()
196     (options, args) = parser.parse_args(args)
197
198     if len(args) <= 1:
199         _error('Need at least one filename')
200
201     if options.typelib_xml:
202         return typelib_xml_strip(args[1])
203
204     if options.inject:
205         if len(args) != 4:
206             _error('Need three filenames; e.g. g-ir-scanner '
207                    '--inject Source.gir Additions.xml SourceOut.gir')
208         return inject(*args[1:4])
209
210     if options.xpath_assertions:
211         return validate(options.xpath_assertions, args[1])
212
213     if not options.namespace_name:
214         _error('Namespace name missing')
215
216     if options.format == 'gir':
217         from giscanner.girwriter import GIRWriter as Writer
218     else:
219         _error("Unknown format: %s" % (options.format, ))
220
221     if not (options.libraries or options.program):
222         _error("Must specify --program or --library")
223     libraries = options.libraries
224
225     for package in options.packages:
226         output = subprocess.Popen(['pkg-config', '--cflags', package],
227                                   stdout=subprocess.PIPE).communicate()[0]
228         # Some pkg-config files on Windows have options we don't understand,
229         # so we explicitly filter to only the ones we need.
230         options_whitelist = ['-I', '-D', '-U', '-l', '-L']
231         def filter_option(opt):
232             for optstart in options_whitelist:
233                 if opt.startswith(optstart):
234                     return True
235             return False
236         output = output.split()
237         filtered_output = filter(filter_option, output)
238         pkg_options, unused = parser.parse_args(filtered_output)
239         options.cpp_includes.extend(pkg_options.cpp_includes)
240         options.cpp_defines.extend(pkg_options.cpp_defines)
241         options.cpp_undefines.extend(pkg_options.cpp_undefines)
242
243         output = subprocess.Popen(['pkg-config', '--libs-only-L', package],
244                                   stdout=subprocess.PIPE).communicate()[0]
245         output = output.split()
246         filtered_output = filter(filter_option, output)
247         pkg_options, unused = parser.parse_args(filtered_output)
248         options.library_paths.extend(pkg_options.library_paths)
249
250     # FIXME: using LPATH is definitely not portable enough. Using Python's
251     # find_library for finding our shared libraries is not a portable enough
252     # anyway as it behaves differently depending on the OS
253     lpath = os.environ.get('LPATH')
254     library_path = ':'.join(options.library_paths)
255
256     ld_library_path = os.environ.get('LD_LIBRARY_PATH')
257     if ld_library_path:
258         library_path = ':'.join([ld_library_path, library_path])
259
260     if lpath:
261         os.environ['LPATH'] = ':'.join([lpath, library_path])
262     else:
263         os.environ['LPATH'] = library_path
264     filenames = []
265     for arg in args:
266         if (arg.endswith('.c') or
267             arg.endswith('.h')):
268             if not os.path.exists(arg):
269                 _error('%s: no such a file or directory' % (arg, ))
270             filenames.append(arg)
271
272     cachestore = CacheStore()
273     # Run the preprocessor, tokenize and construct simple
274     # objects representing the raw C symbols
275     ss = SourceScanner()
276     ss.set_cpp_options(options.cpp_includes,
277                        options.cpp_defines,
278                        options.cpp_undefines)
279     ss.parse_files(filenames)
280     ss.parse_macros(filenames)
281
282     # Transform the C symbols into AST nodes
283     transformer = Transformer(cachestore, ss,
284                               options.namespace_name,
285                               options.namespace_version)
286     if options.strip_prefix:
287         transformer.set_strip_prefix(options.strip_prefix)
288     else:
289         transformer.set_strip_prefix(options.namespace_name)
290     transformer.set_include_paths(options.include_paths)
291     shown_include_warning = False
292     for include in options.includes:
293         if os.sep in include:
294             raise ValueError("Invalid include path %r" % (include, ))
295         include_obj = Include.from_string(include)
296         transformer.register_include(include_obj)
297
298     if options.program:
299         args=[options.program]
300         args.extend(options.program_args)
301         binary = IntrospectionBinary(args)
302     else:
303         binary = compile_introspection_binary(options)
304
305     # Transform the C AST nodes into higher level
306     # GLib/GObject nodes
307     glibtransformer = GLibTransformer(transformer,
308                                       noclosure=options.noclosure,
309                                       nolibtool=options.nolibtool)
310     glibtransformer.set_introspection_binary(binary)
311
312     namespace = glibtransformer.parse()
313
314     # Write out AST
315     writer = Writer(namespace, libraries, transformer.get_includes())
316     data = writer.get_xml()
317     if options.output:
318         fd = open(options.output, "w")
319         fd.write(data)
320     else:
321         print data
322
323     return 0
324
325 sys.exit(main(sys.argv))