Bug 592055 - Build/test fixes
[gnome.gobject-introspection] / giscanner / dumper.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008 Colin Walters
4 # Copyright (C) 2008 Johan Dahlin
5 #
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2 of the License, or (at your option) any later version.
10 #
11 # This library 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 GNU
14 # Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this library; if not, write to the
18 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 # Boston, MA 02111-1307, USA.
20 #
21
22 import os
23 import subprocess
24 import tempfile
25
26 from .glibtransformer import IntrospectionBinary
27 from .utils import get_libtool_command
28
29 # bugzilla.gnome.org/558436
30 # Compile a binary program which is then linked to a library
31 # we want to introspect, in order to call its get_type functions.
32
33 _PROGRAM_TEMPLATE = '''/* This file is generated, do not edit */
34 #include <glib.h>
35 #include <girepository.h>
36 #include <string.h>
37
38 static GOptionEntry entries[] =
39 {
40   { NULL }
41 };
42
43 int
44 main(int argc, char **argv)
45 {
46   GOptionContext *context;
47   GError *error = NULL;
48
49   g_type_init ();
50   g_thread_init (NULL);
51
52   context = g_option_context_new ("");
53   g_option_context_add_main_entries (context, entries, "girepository-1.0");
54   g_option_context_add_group (context, g_irepository_get_option_group ());
55   if (!g_option_context_parse (context, &argc, &argv, &error))
56     {
57       g_printerr ("introspect failed (%d,%d): %s\\n",
58                   error->domain, error->code,
59                   error->message);
60       return 1;
61     }
62   return 0;
63 }
64 '''
65
66
67 class CompilerError(Exception):
68     pass
69
70
71 class LinkerError(Exception):
72     pass
73
74
75 class DumpCompiler(object):
76
77     def __init__(self, options, get_type_functions):
78         self._options = options
79         self._get_type_functions = get_type_functions
80         # We have to use the current directory to work around Unix
81         # sysadmins who mount /tmp noexec
82         self._tmpdir = tempfile.mkdtemp('', 'tmp-introspect', dir=os.getcwd())
83
84         self._compiler_cmd = os.environ.get('CC', 'gcc')
85         self._linker_cmd = os.environ.get('LD', self._compiler_cmd)
86         self._pkgconfig_cmd = os.environ.get('PKG_CONFIG', 'pkg-config')
87
88         self._uninst_srcdir = os.environ.get(
89             'UNINSTALLED_INTROSPECTION_SRCDIR')
90         self._packages = ['gio-2.0 gthread-2.0']
91         if not self._uninst_srcdir:
92             self._packages.append('gobject-introspection-1.0')
93
94     # Public API
95
96     def run(self):
97         c_path = self._generate_tempfile('.c')
98         f = open(c_path, 'w')
99         f.write(_PROGRAM_TEMPLATE)
100
101         # We need to reference our get_type functions to make sure they are
102         # pulled in at the linking stage if the library is a static library
103         # rather than a shared library.
104         if len(self._get_type_functions) > 0:
105             for func in self._get_type_functions:
106                 f.write("extern GType " + func + "(void);\n")
107             f.write("GType (*GI_GET_TYPE_FUNCS_[])(void) = {\n")
108             first = True
109             for func in self._get_type_functions:
110                 if first:
111                     first = False
112                 else:
113                     f.write(",\n")
114                 f.write("  " + func)
115             f.write("\n};\n")
116         f.close()
117
118         o_path = self._generate_tempfile('.o')
119         bin_path = self._generate_tempfile()
120
121         try:
122             self._compile(o_path, c_path)
123         except CompilerError, e:
124             raise SystemExit('ERROR: ' + str(e))
125
126         try:
127             self._link(bin_path, o_path)
128         except LinkerError, e:
129             raise SystemExit('ERROR: ' + str(e))
130
131         os.unlink(c_path)
132
133         return IntrospectionBinary([bin_path], self._tmpdir)
134
135     # Private API
136
137     def _generate_tempfile(self, suffix=''):
138         tmpl = '%s-%s%s' % (self._options.namespace_name,
139                             self._options.namespace_version, suffix)
140         return os.path.join(self._tmpdir, tmpl)
141
142     def _run_pkgconfig(self, flag):
143         proc = subprocess.Popen(
144             [self._pkgconfig_cmd, flag] + self._packages,
145             stdout=subprocess.PIPE)
146         return proc.communicate()[0].split()
147
148     def _compile(self, output, *sources):
149         # Not strictly speaking correct, but easier than parsing shell
150         args = self._compiler_cmd.split()
151         if self._compiler_cmd == 'gcc':
152             args.append('-Wall')
153         pkgconfig_flags = self._run_pkgconfig('--cflags')
154         if self._uninst_srcdir:
155             args.append('-I' + os.path.join(self._uninst_srcdir,
156                                                'girepository'))
157         args.extend(pkgconfig_flags)
158         for include in self._options.cpp_includes:
159             args.append('-I' + include)
160         args.extend(['-c', '-o', output])
161         for source in sources:
162             if not os.path.exists(source):
163                 raise CompilerError(
164                     "Could not find c source file: %s" % (source, ))
165         args.extend(list(sources))
166         subprocess.check_call(args)
167
168     def _link(self, output, *sources):
169         args = []
170         libtool = get_libtool_command(self._options)
171         if libtool:
172             args.extend(libtool)
173             args.append('--mode=link')
174             args.append('--tag=CC')
175             args.append('--silent')
176
177         args.extend([self._linker_cmd, '-o', output])
178
179         # Make sure to list the library to be introspected first since it's
180         # likely to be uninstalled yet and we want the uninstalled RPATHs have
181         # priority (or we might run with installed library that is older)
182
183         # Search the current directory first
184         args.append('-L.')
185
186         uninst_builddir = os.environ.get('UNINSTALLED_INTROSPECTION_BUILDDIR')
187         # hack for building GIRepository.gir, skip -lgirepository-1.0 since
188         # libgirepository-1.0.la is not in current directory and we refer to it
189         # explicitly below anyway
190         for library in self._options.libraries:
191             if (uninst_builddir and
192                 self._options.libraries[0] == 'girepository-1.0'):
193                 continue
194             if library.endswith(".la"): # explicitly specified libtool library
195                 args.append(library)
196             else:
197                 args.append('-l' + library)
198
199         # hack for building gobject-introspection itself
200         if uninst_builddir:
201             path = os.path.join(uninst_builddir, 'girepository',
202                                 'libgirepository-1.0.la')
203             args.append(path)
204
205         args.extend(self._run_pkgconfig('--libs'))
206         for source in sources:
207             if not os.path.exists(source):
208                 raise CompilerError(
209                     "Could not find object file: %s" % (source, ))
210         args.extend(list(sources))
211
212         subprocess.check_call(args)
213
214
215 def compile_introspection_binary(options, get_type_functions):
216     dc = DumpCompiler(options, get_type_functions)
217     return dc.run()