Major rewrite
[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 tempfile
25 import optparse
26 import os
27 import sys
28
29 from giscanner.annotationparser import AnnotationParser
30 from giscanner.ast import Include
31 from giscanner.cachestore import CacheStore
32 from giscanner.dumper import compile_introspection_binary
33 from giscanner.gdumpparser import GDumpParser, IntrospectionBinary
34 from giscanner.minixpath import xpath_assert
35 from giscanner.sourcescanner import SourceScanner
36 from giscanner.shlibs import resolve_shlibs
37 from giscanner.transformer import Transformer
38 from giscanner.maintransformer import MainTransformer
39 from giscanner.introspectablepass import IntrospectablePass
40 from giscanner.girparser import GIRParser
41 from giscanner.girwriter import GIRWriter
42 from giscanner.utils import files_are_identical
43
44 def _get_option_parser():
45     parser = optparse.OptionParser('%prog [options] sources')
46     parser.add_option('', "--quiet",
47                       action="store_true", dest="quiet",
48                       default=False,
49                       help="If passed, do not print details of normal" \
50                           + " operation")
51     parser.add_option("", "--format",
52                       action="store", dest="format",
53                       default="gir",
54                       help="format to use, one of gidl, gir")
55     parser.add_option("-i", "--include",
56                       action="append", dest="includes", default=[],
57                       help="include types for other gidls")
58     parser.add_option('', "--generate-typelib-tests",
59                       action="store", dest="test_codegen", default=None,
60                       help="Generate test code for given namespace,output.h,output.c")
61     parser.add_option('', "--passthrough-gir",
62                       action="store", dest="passthrough_gir", default=None,
63                       help="Parse and re-output the specified GIR")
64     parser.add_option('', "--reparse-validate",
65                       action="store_true", dest="reparse_validate_gir", default=False,
66                       help="After generating the GIR, re-parse it to ensure validity")
67     parser.add_option("", "--add-include-path",
68                       action="append", dest="include_paths", default=[],
69                       help="include paths for other GIR files")
70     parser.add_option("", "--program",
71                       action="store", dest="program", default=None,
72                       help="program to execute")
73     parser.add_option("", "--program-arg",
74                       action="append", dest="program_args", default=[],
75                       help="extra arguments to program")
76     parser.add_option("", "--libtool",
77                       action="store", dest="libtool_path", default=None,
78                       help="full path to libtool")
79     parser.add_option("", "--no-libtool",
80                       action="store_true", dest="nolibtool", default=False,
81                       help="do not use libtool")
82     parser.add_option("-l", "--library",
83                       action="append", dest="libraries", default=[],
84                       help="libraries of this unit")
85     parser.add_option("-L", "--library-path",
86                       action="append", dest="library_paths", default=[],
87                       help="directories to search for libraries")
88     parser.add_option("-n", "--namespace",
89                       action="store", dest="namespace_name",
90                       help=("name of namespace for this unit, also "
91                             "used to compute --identifier-prefix and --symbol-prefix"))
92     parser.add_option("", "--nsversion",
93                       action="store", dest="namespace_version",
94                       help="version of namespace for this unit")
95     parser.add_option("", "--identifier-prefix",
96                       action="append", dest="identifier_prefixes", default=[],
97                       help="""Remove this prefix from C identifiers (structure typedefs, etc.).
98 May be specified multiple times.  This is also used as the default for --symbol-prefix if
99 the latter is not specified.""")
100     parser.add_option("", "--symbol-prefix",
101                       action="append", dest="symbol_prefixes", default=[],
102                       help="Remove this prefix from C symbols (function names)")
103     parser.add_option("", "--add-init-section",
104                       action="append", dest="init_sections", default=[],
105             help="add extra initialization code in the introspection program")
106     parser.add_option("-o", "--output",
107                       action="store", dest="output",
108                       help="output to writeout, defaults to stdout")
109     parser.add_option("", "--pkg",
110                       action="append", dest="packages", default=[],
111                       help="pkg-config packages to get cflags from")
112     parser.add_option("", "--pkg-export",
113                       action="append", dest="packages_export", default=[],
114                       help="Associated pkg-config packages for this library")
115     parser.add_option('', "--warn-all",
116                       action="store_true", dest="warn_all", default=False,
117                       help="If true, enable all warnings for introspection")
118     parser.add_option('', "--warn-error",
119                       action="store_true", dest="warn_fatal",
120                       help="Turn warnings into fatal errors")
121     parser.add_option("-v", "--verbose",
122                       action="store_true", dest="verbose",
123                       help="be verbose")
124     parser.add_option("", "--typelib-xml",
125                       action="store_true", dest="typelib_xml",
126                       help="Just convert GIR to typelib XML")
127     parser.add_option("", "--xpath-assertions",
128                       action="store", dest="xpath_assertions",
129             help="Use given file to create assertions on GIR content")
130     parser.add_option("", "--c-include",
131                       action="append", dest="c_includes", default=[],
132                       help="headers which should be included in C programs")
133
134     group = optparse.OptionGroup(parser, "Preprocessor options")
135     group.add_option("-I", help="Pre-processor include file",
136                      action="append", dest="cpp_includes",
137                      default=[])
138     group.add_option("-D", help="Pre-processor define",
139                      action="append", dest="cpp_defines",
140                      default=[])
141     group.add_option("-U", help="Pre-processor undefine",
142                      action="append", dest="cpp_undefines",
143                      default=[])
144     group.add_option("-p", dest="", help="Ignored")
145     parser.add_option_group(group)
146
147     return parser
148
149
150 def _error(msg):
151     raise SystemExit('ERROR: %s' % (msg, ))
152
153 def passthrough_gir(path, f):
154     parser = GIRParser()
155     parser.parse(path)
156
157     writer = GIRWriter(parser.get_namespace(),
158                        parser.get_shared_libraries(),
159                        parser.get_includes(),
160                        parser.get_pkgconfig_packages(),
161                        parser.get_c_includes())
162     f.write(writer.get_xml())
163
164 def test_codegen(optstring):
165     (namespace, out_h_filename, out_c_filename) = optstring.split(',')
166     if namespace == 'Everything':
167         from .testcodegen import EverythingCodeGenerator
168         gen = EverythingCodeGenerator(out_h_filename, out_c_filename)
169         gen.write()
170     else:
171         raise ValueError("Invaild namespace %r" % (namespace, ))
172     return 0
173
174 def validate(assertions, path):
175     from xml.etree.cElementTree import parse
176     doc = parse(open(path))
177     root = doc.getroot()
178     f = open(assertions)
179     assertions_list = f.readlines()
180     for assertion in assertions_list:
181         assertion = assertion.strip()
182         xpath_assert(root, assertion)
183     f.close()
184     return 0
185
186 def process_options(output, allowed_flags):
187     for option in output.split():
188         for flag in allowed_flags:
189             if not option.startswith(flag):
190                 continue
191             yield option
192             break
193
194 def process_packages(parser, options, packages):
195     args = ['pkg-config', '--cflags']
196     args.extend(packages)
197     output = subprocess.Popen(args,
198                               stdout=subprocess.PIPE).communicate()[0]
199     if output is None:
200         # the error output should have already appeared on our stderr,
201         # so we just exit
202         sys.exit(1)
203     # Some pkg-config files on Windows have options we don't understand,
204     # so we explicitly filter to only the ones we need.
205     options_whitelist = ['-I', '-D', '-U', '-l', '-L']
206     filtered_output = list(process_options(output, options_whitelist))
207     pkg_options, unused = parser.parse_args(filtered_output)
208     options.cpp_includes.extend(pkg_options.cpp_includes)
209     options.cpp_defines.extend(pkg_options.cpp_defines)
210     options.cpp_undefines.extend(pkg_options.cpp_undefines)
211
212     args = ['pkg-config', '--libs-only-L']
213     args.extend(packages)
214     output = subprocess.Popen(args,
215                               stdout=subprocess.PIPE).communicate()[0]
216     if output is None:
217         sys.exit(1)
218     filtered_output = list(process_options(output, options_whitelist))
219     pkg_options, unused = parser.parse_args(filtered_output)
220     options.library_paths.extend(pkg_options.library_paths)
221
222 def scanner_main(args):
223     parser = _get_option_parser()
224     (options, args) = parser.parse_args(args)
225
226     if options.passthrough_gir:
227         passthrough_gir(options.passthrough_gir, sys.stdout)
228     if options.test_codegen:
229         return test_codegen(options.test_codegen)
230
231     if len(args) <= 1:
232         _error('Need at least one filename')
233
234     if options.xpath_assertions:
235         return validate(options.xpath_assertions, args[1])
236
237     if not options.namespace_name:
238         _error('Namespace name missing')
239
240     if options.format == 'gir':
241         from giscanner.girwriter import GIRWriter as Writer
242     else:
243         _error("Unknown format: %s" % (options.format, ))
244
245     if not (options.libraries or options.program):
246         _error("Must specify --program or --library")
247     libraries = options.libraries
248
249     filenames = []
250     for arg in args:
251         # We don't support real C++ parsing yet, but we should be able
252         # to understand C API implemented in C++ files.
253         if (arg.endswith('.c') or arg.endswith('.cpp') or
254             arg.endswith('.cc') or arg.endswith('.cxx') or
255             arg.endswith('.h') or arg.endswith('.hpp') or
256             arg.endswith('.hxx')):
257             if not os.path.exists(arg):
258                 _error('%s: no such a file or directory' % (arg, ))
259             # Make absolute, because we do comparisons inside scannerparser.c
260             # against the absolute path that cpp will give us
261             filenames.append(os.path.abspath(arg))
262
263     cachestore = CacheStore()
264     transformer = Transformer(cachestore,
265                               options.namespace_name,
266                               options.namespace_version,
267                               options.identifier_prefixes,
268                               options.symbol_prefixes)
269     if options.warn_all:
270         transformer.enable_warnings(True)
271     transformer.set_include_paths(options.include_paths)
272     shown_include_warning = False
273     for include in options.includes:
274         if os.sep in include:
275             raise ValueError("Invalid include path %r" % (include, ))
276         try:
277             include_obj = Include.from_string(include)
278         except:
279             sys.stderr.write("Malformed include %r\n" % (include, ))
280             sys.exit(1)
281         transformer.register_include(include_obj)
282
283     packages = set(options.packages)
284     packages.update(transformer.get_pkgconfig_packages())
285     process_packages(parser, options, packages)
286
287     # Run the preprocessor, tokenize and construct simple
288     # objects representing the raw C symbols
289     ss = SourceScanner()
290     ss.set_cpp_options(options.cpp_includes,
291                        options.cpp_defines,
292                        options.cpp_undefines)
293     ss.parse_files(filenames)
294     ss.parse_macros(filenames)
295
296     # Transform the C symbols into AST nodes
297     transformer.set_source_ast(ss)
298
299     # Transform the C AST nodes into higher level
300     # GLib/GObject nodes
301     gdump_parser = GDumpParser(transformer)
302
303     # Do enough parsing that we have the get_type() functions to reference
304     # when creating the introspection binary
305     gdump_parser.init_parse()
306
307     if options.program:
308         args=[options.program]
309         args.extend(options.program_args)
310         binary = IntrospectionBinary(args)
311     else:
312         binary = compile_introspection_binary(options,
313                             gdump_parser.get_get_type_functions())
314
315     shlibs = resolve_shlibs(options, binary, libraries)
316
317     gdump_parser.set_introspection_binary(binary)
318     gdump_parser.parse()
319
320     ap = AnnotationParser(ss)
321     blocks = ap.parse()
322
323     main = MainTransformer(transformer, blocks)
324     main.transform()
325
326     final = IntrospectablePass(transformer)
327     final.validate()
328
329     if options.warn_fatal and transformer.did_warn():
330         transformer.log_warning("warnings configured as fatal", fatal=True)
331         # Redundant sys.exit here, just in case
332         sys.exit(1)
333
334     # Write out AST
335     if options.packages_export:
336         exported_packages = options.packages_export
337     else:
338         exported_packages = options.packages
339     writer = Writer(transformer.namespace, shlibs, transformer.get_includes(),
340                     exported_packages, options.c_includes)
341     data = writer.get_xml()
342     if options.output:
343         tempdir = os.path.dirname(options.output) or os.getcwd()
344         main_f = tempfile.NamedTemporaryFile(suffix='.gir', dir=tempdir, delete=False)
345         main_f.write(data)
346         main_f.close()
347         if options.reparse_validate_gir:
348             temp_f = tempfile.NamedTemporaryFile(suffix='.gir', dir=tempdir, delete=False)
349             passthrough_gir(main_f.name, temp_f)
350             temp_f.close()
351             if not files_are_identical(main_f.name, temp_f.name):
352                 raise SystemExit(
353 "Failed to re-parse .gir file; scanned=%r passthrough=%r" % (main_f.name, temp_f.name))
354             os.unlink(temp_f.name)
355         os.rename(main_f.name, options.output)
356     else:
357         sys.stdout.write(data)
358
359     return 0