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