f1ad7eb27f533517b8b34ec6a9c872716dd446e3
[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     path = os.path.join(basedir, 'lib', 'python%d.%d' % sys.version_info[:2],
35                         'site-packages')
36 sys.path.insert(0, path)
37
38 from giscanner.glibtransformer import GLibTransformer
39 from giscanner.sourcescanner import SourceScanner
40 from giscanner.transformer import Transformer
41 from giscanner.ast import Include
42 from giscanner.minixpath import myxpath, xpath_assert
43
44
45 def _get_option_parser():
46     parser = optparse.OptionParser('%prog [options] sources')
47     parser.add_option("", "--format",
48                       action="store", dest="format",
49                       default="gir",
50                       help="format to use, one of gidl, gir")
51     parser.add_option("-i", "--include",
52                       action="append", dest="includes", default=[],
53                       help="include types for other gidls")
54     parser.add_option("--add-include-path",
55                       action="append", dest="include_paths", default=[],
56                       help="include paths for other GIR files")
57     parser.add_option("-l", "--library",
58                       action="append", dest="libraries", default=[],
59                       help="libraries of this unit")
60     parser.add_option("-L", "--library-path",
61                       action="append", dest="library_paths", default=[],
62                       help="directories to search for libraries")
63     parser.add_option("-n", "--namespace",
64                       action="store", dest="namespace_name",
65                       help="name of namespace for this unit, also used as --strip-prefix default")
66     parser.add_option("", "--nsversion",
67                       action="store", dest="namespace_version",
68                       help="version of namespace for this unit")
69     parser.add_option("", "--strip-prefix",
70                       action="store", dest="strip_prefix", default=None,
71                       help="remove this prefix from objects and functions")
72     parser.add_option("-o", "--output",
73                       action="store", dest="output",
74                       help="output to writeout, defaults to stdout")
75     parser.add_option("", "--pkg",
76                       action="append", dest="packages", default=[],
77                       help="pkg-config packages to get cflags from")
78     parser.add_option("-v", "--verbose",
79                       action="store_true", dest="verbose",
80                       help="be verbose")
81     parser.add_option("", "--noclosure",
82                       action="store_true", dest="noclosure",
83                       help="do not delete unknown types")
84     parser.add_option("", "--typelib-xml",
85                       action="store_true", dest="typelib_xml",
86                       help="Just convert GIR to typelib XML")
87     parser.add_option("", "--inject",
88                       action="store_true", dest="inject",
89                       help="Inject additional components into GIR XML")
90     parser.add_option("", "--xpath-assertions",
91                       action="store", dest="xpath_assertions",
92                       help="Use given file to create assertions on GIR content")
93
94     group = optparse.OptionGroup(parser, "Preprocessor options")
95     group.add_option("-I", help="Pre-processor include file",
96                      action="append", dest="cpp_includes",
97                      default=[])
98     group.add_option("-D", help="Pre-processor define",
99                      action="append", dest="cpp_defines",
100                      default=[])
101     group.add_option("-U", help="Pre-processor undefine",
102                      action="append", dest="cpp_undefines",
103                      default=[])
104     group.add_option("-p", dest="", help="Ignored")
105     parser.add_option_group(group)
106
107     return parser
108
109
110 def _error(msg):
111     raise SystemExit('ERROR: %s' % (msg, ))
112
113 def typelib_xml_strip(path):
114     from giscanner.girparser import GIRParser
115     from giscanner.girwriter import GIRWriter
116     from giscanner.girparser import C_NS
117     c_ns_key = '{%s}' % (C_NS, )
118
119     parser = GIRParser(path, initial_parse=False)
120     doc = parser.get_doc()
121     for node in doc.getiterator():
122         for attrib in list(node.attrib):
123             if attrib.startswith(c_ns_key):
124                 del node.attrib[attrib]
125     parser.parse()
126     writer = GIRWriter(parser.get_namespace(),
127                        parser.get_shared_libraries(),
128                        parser.get_includes())
129     sys.stdout.write(writer.get_xml())
130     return 0
131
132 def inject(path, additions, outpath):
133     from giscanner.girparser import GIRParser
134     from giscanner.girwriter import GIRWriter
135     from xml.etree.cElementTree import parse
136
137     parser = GIRParser(path, initial_parse=False)
138     root = parser.get_doc().getroot()
139     injectDoc = parse(open(additions))
140     for node in injectDoc.getroot():
141         injectPath = node.attrib['path']
142         target = myxpath(root, injectPath)
143         if not target:
144             raise ValueError("Couldn't find path %r" % (injectPath, ))
145         for child in node:
146             target.append(child)
147     parser.parse()
148     writer = GIRWriter(parser.get_namespace(),
149                        parser.get_shared_libraries(),
150                        parser.get_includes())
151     outf = open(outpath, 'w')
152     outf.write(writer.get_xml())
153     outf.close()
154     return 0
155
156 def validate(assertions, path):
157     from xml.etree.cElementTree import parse
158     doc = parse(open(path))
159     root = doc.getroot()
160     f = open(assertions)
161     assertions_list = f.readlines()
162     print "=== CHECKING %s (%d assertions) ===" % (assertions,
163                                                    len(assertions_list))
164     for assertion in assertions_list:
165         assertion = assertion.strip()
166         xpath_assert(root, assertion)
167     f.close()
168     print "=== PASSED %s ===" % (assertions, )
169     return 0
170
171 def main(args):
172     parser = _get_option_parser()
173     (options, args) = parser.parse_args(args)
174
175     if len(args) <= 1:
176         _error('Need at least one filename')
177
178     if options.typelib_xml:
179         return typelib_xml_strip(args[1])
180
181     if options.inject:
182         if len(args) != 4:
183             _error('Need three filenames; e.g. g-ir-scanner --inject Source.gir Additions.xml SourceOut.gir')
184         return inject(*args[1:4])
185
186     if options.xpath_assertions:
187         return validate(options.xpath_assertions, args[1])
188
189     if not options.namespace_name:
190         _error('Namespace name missing')
191
192     if options.format == 'gir':
193         from giscanner.girwriter import GIRWriter as Writer
194     else:
195         _error("Unknown format: %s" % (options.format, ))
196
197     if not options.libraries:
198         _error("Must specify --library at least one primary library")
199     libraries = options.libraries
200
201     for package in options.packages:
202         output = subprocess.Popen(['pkg-config', '--cflags', package],
203                                   stdout=subprocess.PIPE).communicate()[0]
204         # Some pkg-config files on Windows have options we don't understand,
205         # so we explicitly filter to only the ones we need.
206         options_whitelist = ['-I', '-D', '-U', '-l', '-L']
207         def filter_option(opt):
208             for optstart in options_whitelist:
209                 if opt.startswith(optstart):
210                     return True
211             return False
212         output = output.split()
213         filtered_output = filter(filter_option, output)
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         output = subprocess.Popen(['pkg-config', '--libs-only-L', package],
220                                   stdout=subprocess.PIPE).communicate()[0]
221         output = output.split()
222         filtered_output = filter(filter_option, output)
223         pkg_options, unused = parser.parse_args(filtered_output)
224         options.library_paths.extend(pkg_options.library_paths)
225
226     # FIXME: using LPATH is definitely not portable enough. Using Python's
227     # find_library for finding our shared libraries is not a portable enough
228     # anyway as it behaves differently depending on the OS
229     lpath = os.environ.get('LPATH')
230     library_path = ':'.join(options.library_paths)
231     if lpath:
232         os.environ['LPATH'] = ':'.join([lpath, library_path])
233     else:
234         os.environ['LPATH'] = library_path
235
236     filenames = []
237     for arg in args:
238         if (arg.endswith('.c') or
239             arg.endswith('.h')):
240             if not os.path.exists(arg):
241                 _error('%s: no such a file or directory' % (arg, ))
242             filenames.append(arg)
243
244     # Run the preprocessor, tokenize and construct simple
245     # objects representing the raw C symbols
246     ss = SourceScanner()
247     ss.set_cpp_options(options.cpp_includes,
248                        options.cpp_defines,
249                        options.cpp_undefines)
250     ss.parse_files(filenames)
251     ss.parse_macros(filenames)
252
253     # Transform the C symbols into AST nodes
254     transformer = Transformer(ss, options.namespace_name, options.namespace_version)
255     if options.strip_prefix:
256         transformer.set_strip_prefix(options.strip_prefix)
257     else:
258         transformer.set_strip_prefix(options.namespace_name)
259     transformer.set_include_paths(options.include_paths)
260     shown_include_warning = False
261     for include in options.includes:
262         if os.sep in include:
263             raise ValueError("Invalid include path %r" % (include, ))
264         include_obj = Include.from_string(include)
265         transformer.register_include(include_obj)
266
267     # Transform the C AST nodes into higher level
268     # GLib/GObject nodes
269     glibtransformer = GLibTransformer(transformer, noclosure=options.noclosure)
270     if options.libraries:
271         for library in options.libraries:
272             glibtransformer.add_library(library)
273
274     namespace = glibtransformer.parse()
275
276     # Write out AST
277     writer = Writer(namespace, libraries, transformer.get_includes())
278     data = writer.get_xml()
279     if options.output:
280         fd = open(options.output, "w")
281         fd.write(data)
282     else:
283         print data
284
285     return 0
286
287 sys.exit(main(sys.argv))