Bug 572075 - Make the scanner work with static and convenience libraries
[gnome.gobject-introspection] / 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.annotationparser import AnnotationParser, InvalidAnnotationError
45 from giscanner.ast import Include
46 from giscanner.cachestore import CacheStore
47 from giscanner.dumper import compile_introspection_binary
48 from giscanner.glibtransformer import GLibTransformer, IntrospectionBinary
49 from giscanner.minixpath import myxpath, xpath_assert
50 from giscanner.sourcescanner import SourceScanner
51 from giscanner.transformer import Transformer
52
53 def _get_option_parser():
54     parser = optparse.OptionParser('%prog [options] sources')
55     parser.add_option("", "--format",
56                       action="store", dest="format",
57                       default="gir",
58                       help="format to use, one of gidl, gir")
59     parser.add_option("-i", "--include",
60                       action="append", dest="includes", default=[],
61                       help="include types for other gidls")
62     parser.add_option("", "--add-include-path",
63                       action="append", dest="include_paths", default=[],
64                       help="include paths for other GIR files")
65     parser.add_option("", "--program",
66                       action="store", dest="program", default=None,
67                       help="program to execute")
68     parser.add_option("", "--program-arg",
69                       action="append", dest="program_args", default=[],
70                       help="extra arguments to program")
71     parser.add_option("", "--libtool",
72                       action="store", dest="libtool_path", default=None,
73                       help="full path to libtool")
74     parser.add_option("", "--no-libtool",
75                       action="store_true", dest="nolibtool", default=False,
76                       help="use libtool")
77     parser.add_option("-l", "--library",
78                       action="append", dest="libraries", default=[],
79                       help="libraries of this unit")
80     parser.add_option("-L", "--library-path",
81                       action="append", dest="library_paths", default=[],
82                       help="directories to search for libraries")
83     parser.add_option("-n", "--namespace",
84                       action="store", dest="namespace_name",
85                       help=("name of namespace for this unit, also "
86                             "used as --strip-prefix default"))
87     parser.add_option("", "--nsversion",
88                       action="store", dest="namespace_version",
89                       help="version of namespace for this unit")
90     parser.add_option("", "--strip-prefix",
91                       action="store", dest="strip_prefix", default=None,
92                       help="remove this prefix from objects and functions")
93     parser.add_option("-o", "--output",
94                       action="store", dest="output",
95                       help="output to writeout, defaults to stdout")
96     parser.add_option("", "--pkg",
97                       action="append", dest="packages", default=[],
98                       help="pkg-config packages to get cflags from")
99     parser.add_option("-v", "--verbose",
100                       action="store_true", dest="verbose",
101                       help="be verbose")
102     parser.add_option("", "--noclosure",
103                       action="store_true", dest="noclosure",
104                       help="do not delete unknown types")
105     parser.add_option("", "--typelib-xml",
106                       action="store_true", dest="typelib_xml",
107                       help="Just convert GIR to typelib XML")
108     parser.add_option("", "--inject",
109                       action="store_true", dest="inject",
110                       help="Inject additional components into GIR XML")
111     parser.add_option("", "--xpath-assertions",
112                       action="store", dest="xpath_assertions",
113                       help="Use given file to create assertions on GIR content")
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     print "=== CHECKING %s (%d assertions) ===" % (assertions,
190                                                    len(assertions_list))
191     for assertion in assertions_list:
192         assertion = assertion.strip()
193         xpath_assert(root, assertion)
194     f.close()
195     print "=== PASSED %s ===" % (assertions, )
196     return 0
197     
198 def process_options(output, allowed_flags):
199     for option in output.split():
200         for flag in allowed_flags:
201             if not option.startswith(flag):
202                 continue
203             yield option
204             break    
205
206 def process_packages(parser, options, packages):
207     args = ['pkg-config', '--cflags']
208     args.extend(packages)
209     output = subprocess.Popen(args,
210                               stdout=subprocess.PIPE).communicate()[0]
211     if output is None:
212         # the error output should have already appeared on our stderr,
213         # so we just exit
214         sys.exit(1)
215     # Some pkg-config files on Windows have options we don't understand,
216     # so we explicitly filter to only the ones we need.
217     options_whitelist = ['-I', '-D', '-U', '-l', '-L']        
218     filtered_output = list(process_options(output, options_whitelist))
219     pkg_options, unused = parser.parse_args(filtered_output)
220     options.cpp_includes.extend(pkg_options.cpp_includes)
221     options.cpp_defines.extend(pkg_options.cpp_defines)
222     options.cpp_undefines.extend(pkg_options.cpp_undefines)
223
224     args = ['pkg-config', '--libs-only-L']
225     args.extend(packages)
226     output = subprocess.Popen(args,
227                               stdout=subprocess.PIPE).communicate()[0]
228     if output is None:
229         sys.exit(1)
230     filtered_output = list(process_options(output, options_whitelist))
231     pkg_options, unused = parser.parse_args(filtered_output)
232     options.library_paths.extend(pkg_options.library_paths)     
233
234 def main(args):
235     parser = _get_option_parser()
236     (options, args) = parser.parse_args(args)
237
238     if len(args) <= 1:
239         _error('Need at least one filename')
240
241     if options.typelib_xml:
242         return typelib_xml_strip(args[1])
243
244     if options.inject:
245         if len(args) != 4:
246             _error('Need three filenames; e.g. g-ir-scanner '
247                    '--inject Source.gir Additions.xml SourceOut.gir')
248         return inject(*args[1:4])
249
250     if options.xpath_assertions:
251         return validate(options.xpath_assertions, args[1])
252
253     if not options.namespace_name:
254         _error('Namespace name missing')
255
256     if options.format == 'gir':
257         from giscanner.girwriter import GIRWriter as Writer
258     else:
259         _error("Unknown format: %s" % (options.format, ))
260
261     if not (options.libraries or options.program):
262         _error("Must specify --program or --library")
263     libraries = options.libraries
264
265     # FIXME: using LPATH is definitely not portable enough. Using Python's
266     # find_library for finding our shared libraries is not a portable enough
267     # anyway as it behaves differently depending on the OS
268     lpath = os.environ.get('LPATH')
269     library_path = ':'.join(options.library_paths)
270
271     ld_library_path = os.environ.get('LD_LIBRARY_PATH')
272     if ld_library_path:
273         library_path = ':'.join([ld_library_path, library_path])
274
275     if lpath:
276         os.environ['LPATH'] = ':'.join([lpath, library_path])
277     else:
278         os.environ['LPATH'] = library_path
279     filenames = []
280     for arg in args:
281         if (arg.endswith('.c') or
282             arg.endswith('.h')):
283             if not os.path.exists(arg):
284                 _error('%s: no such a file or directory' % (arg, ))
285             filenames.append(arg)
286             
287     cachestore = CacheStore()            
288     transformer = Transformer(cachestore,
289                               options.namespace_name,
290                               options.namespace_version)
291     if options.strip_prefix:
292         transformer.set_strip_prefix(options.strip_prefix)
293     else:
294         transformer.set_strip_prefix(options.namespace_name)
295     transformer.set_include_paths(options.include_paths)
296     shown_include_warning = False
297     for include in options.includes:
298         if os.sep in include:
299             raise ValueError("Invalid include path %r" % (include, ))
300         include_obj = Include.from_string(include)
301         transformer.register_include(include_obj)
302         
303     packages = set(options.packages)
304     packages.update(transformer.get_pkgconfig_packages())
305     process_packages(parser, options, packages)
306
307     # Run the preprocessor, tokenize and construct simple
308     # objects representing the raw C symbols
309     ss = SourceScanner()
310     ss.set_cpp_options(options.cpp_includes,
311                        options.cpp_defines,
312                        options.cpp_undefines)
313     ss.parse_files(filenames)
314     ss.parse_macros(filenames)
315
316     # Transform the C symbols into AST nodes
317     transformer.set_source_ast(ss)
318
319     # Transform the C AST nodes into higher level
320     # GLib/GObject nodes
321     glibtransformer = GLibTransformer(transformer,
322                                       noclosure=options.noclosure)
323
324     # Do enough parsing that we have the get_type() functions to reference
325     # when creating the introspection binary
326     glibtransformer.init_parse()
327
328     if options.program:
329         args=[options.program]
330         args.extend(options.program_args)
331         binary = IntrospectionBinary(args)
332     else:
333         binary = compile_introspection_binary(options,
334                                               glibtransformer.get_get_type_functions())
335
336     glibtransformer.set_introspection_binary(binary)
337
338     namespace = glibtransformer.parse()
339
340     ap = AnnotationParser(namespace, ss, transformer)
341     try:
342         ap.parse()
343     except InvalidAnnotationError, e:
344         raise SystemExit("ERROR in annotation: %s" % (str(e), ))
345
346     # Write out AST
347     writer = Writer(namespace, libraries, transformer.get_includes(),
348                     options.packages)
349     data = writer.get_xml()
350     if options.output:
351         fd = open(options.output, "w")
352         fd.write(data)
353     else:
354         print data
355
356     return 0
357
358 sys.exit(main(sys.argv))