Handle multiple arguments for $CC
[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
28 # bugzilla.gnome.org/558436
29 # Compile a binary program which is then linked to a library
30 # we want to introspect, in order to call its get_type functions.
31
32 _PROGRAM_TEMPLATE = '''/* This file is generated, do not edit */
33 #include <glib.h>
34 #include <girepository.h>
35 #include <string.h>
36
37 static GOptionEntry entries[] =
38 {
39   { NULL }
40 };
41
42 int
43 main(int argc, char **argv)
44 {
45   GOptionContext *context;
46   GError *error = NULL;
47
48   g_type_init ();
49   g_thread_init (NULL);
50
51   context = g_option_context_new ("");
52   g_option_context_add_main_entries (context, entries, "girepository-1.0");
53   g_option_context_add_group (context, g_irepository_get_option_group ());
54   if (!g_option_context_parse (context, &argc, &argv, &error))
55     {
56       g_printerr ("introspect failed (%d,%d): %s\\n",
57                   error->domain, error->code,
58                   error->message);
59       return 1;
60     }
61   return 0;
62 }
63 '''
64
65
66 class CompilerError(Exception):
67     pass
68
69
70 class LinkerError(Exception):
71     pass
72
73
74 class DumpCompiler(object):
75
76     def __init__(self, options, get_type_functions):
77         self._options = options
78         self._get_type_functions = get_type_functions
79         self._tmpdir = tempfile.mkdtemp('', 'tmp-introspect')
80
81         self._compiler_cmd = os.environ.get('CC', 'gcc')
82         self._linker_cmd = os.environ.get('LD', self._compiler_cmd)
83         self._pkgconfig_cmd = os.environ.get('PKG_CONFIG', 'pkg-config')
84
85         self._uninst_srcdir = os.environ.get(
86             'UNINSTALLED_INTROSPECTION_SRCDIR')
87         self._packages = ['gio-2.0 gthread-2.0']
88         if not self._uninst_srcdir:
89             self._packages.append('gobject-introspection-1.0')
90
91     # Public API
92
93     def run(self):
94         print '  GEN   ' + self._options.output
95         c_path = self._generate_tempfile('.c')
96         f = open(c_path, 'w')
97         f.write(_PROGRAM_TEMPLATE)
98
99         # We need to reference our get_type functions to make sure they are
100         # pulled in at the linking stage if the library is a static library
101         # rather than a shared library.
102         for func in self._get_type_functions:
103             f.write("extern GType " + func + "(void);\n")
104         f.write("GType (*GI_GET_TYPE_FUNCS_[])(void) = {\n")
105         first = True
106         for func in self._get_type_functions:
107             if first:
108                 first = False
109             else:
110                 f.write(",\n")
111             f.write("  " + func)
112         f.write("\n};\n")
113
114         f.close()
115
116         o_path = self._generate_tempfile('.o')
117         bin_path = self._generate_tempfile()
118
119         try:
120             self._compile(o_path, c_path)
121         except CompilerError, e:
122             raise SystemExit('ERROR: ' + str(e))
123
124         try:
125             self._link(bin_path, o_path)
126         except LinkerError, e:
127             raise SystemExit('ERROR: ' + str(e))
128
129         os.unlink(c_path)
130
131         return IntrospectionBinary([bin_path], self._tmpdir)
132
133     # Private API
134
135     def _generate_tempfile(self, suffix=''):
136         tmpl = '%s-%s%s' % (self._options.namespace_name,
137                             self._options.namespace_version, suffix)
138         return os.path.join(self._tmpdir, tmpl)
139
140     def _run_pkgconfig(self, flag):
141         proc = subprocess.Popen(
142             [self._pkgconfig_cmd, flag] + self._packages,
143             stdout=subprocess.PIPE)
144         return proc.communicate()[0].split()
145
146     def _use_libtool_infection(self):
147         libtool_infection = not self._options.nolibtool
148         if not libtool_infection:
149             return None
150
151         libtool_path = self._options.libtool_path
152         if libtool_path:
153             # Automake by default sets:
154             # LIBTOOL = $(SHELL) $(top_builddir)/libtool
155             # To be strictly correct we would have to parse shell.  For now
156             # we simply split().
157             return libtool_path.split(' ')
158
159         try:
160             subprocess.check_call(['libtool', '--version'])
161         except subprocess.CalledProcessError, e:
162             # If libtool's not installed, assume we don't need it
163             return None
164
165         return ['libtool']
166
167     def _compile(self, output, *sources):
168         # Not strictly speaking correct, but easier than parsing shell
169         args = self._compiler_cmd.split()
170         if self._compiler_cmd == 'gcc':
171             args.append('-Wall')
172         pkgconfig_flags = self._run_pkgconfig('--cflags')
173         if self._uninst_srcdir:
174             args.append('-I' + os.path.join(self._uninst_srcdir,
175                                                'girepository'))
176         args.extend(pkgconfig_flags)
177         for include in self._options.cpp_includes:
178             args.append('-I' + include)
179         args.extend(['-c', '-o', output])
180         for source in sources:
181             if not os.path.exists(source):
182                 raise CompilerError(
183                     "Could not find c source file: %s" % (source, ))
184         args.extend(list(sources))
185         subprocess.check_call(args)
186
187     def _link(self, output, *sources):
188         args = []
189         libtool = self._use_libtool_infection()
190         if libtool:
191             args.extend(libtool)
192             args.append('--mode=link')
193             args.append('--tag=CC')
194             args.append('--silent')
195
196         args.extend([self._linker_cmd, '-o', output])
197
198         # Make sure to list the library to be introspected first since it's
199         # likely to be uninstalled yet and we want the uninstalled RPATHs have
200         # priority (or we might run with installed library that is older)
201
202         # Search the current directory first
203         args.append('-L.')
204
205         uninst_builddir = os.environ.get('UNINSTALLED_INTROSPECTION_BUILDDIR')
206         # hack for building GIRepository.gir, skip -lgirepository-1.0 since
207         # libgirepository-1.0.la is not in current directory and we refer to it
208         # explicitly below anyway
209         if (not uninst_builddir or
210             self._options.libraries[0] != 'girepository-1.0'):
211             # We only use the first library; assume others are "custom"
212             # libraries like from gir-repository.  Right now those don't define
213             # new GTypes, so we don't need to introspect them.
214             args.append('-l' + self._options.libraries[0])
215
216         # hack for building gobject-introspection itself
217         if uninst_builddir:
218             path = os.path.join(uninst_builddir, 'girepository',
219                                 'libgirepository-1.0.la')
220             args.append(path)
221
222         args.extend(self._run_pkgconfig('--libs'))
223         for source in sources:
224             if not os.path.exists(source):
225                 raise CompilerError(
226                     "Could not find object file: %s" % (source, ))
227         args.extend(list(sources))
228
229         subprocess.check_call(args)
230
231
232 def compile_introspection_binary(options, get_type_functions):
233     dc = DumpCompiler(options, get_type_functions)
234     return dc.run()