Update Enums and Flags with new API
[pygobject.git] / dsextras.py
blob9fc79c1bbc2183989b6ca2fbf8e0fafef0b69e65
2 # dsextras.py - Extra classes and utilities for distutils, adding
3 # pkg-config support
6 from distutils.command.build_ext import build_ext
7 from distutils.command.install_lib import install_lib
8 from distutils.command.install_data import install_data
9 from distutils.extension import Extension
10 import distutils.dep_util
11 import fnmatch
12 import os
13 import re
14 import string
15 import sys
17 GLOBAL_INC = []
18 GLOBAL_MACROS = []
20 def get_m4_define(varname):
21 """Return the value of a m4_define variable as set in configure.in."""
22 pattern = re.compile("m4_define\(" + varname + "\,\s*(.+)\)")
23 if os.path.exists('configure.ac'):
24 fname = 'configure.ac'
25 elif os.path.exists('configure.in'):
26 fname = 'configure.in'
27 else:
28 raise SystemExit('could not find configure file')
30 for line in open(fname).readlines():
31 match_obj = pattern.match(line)
32 if match_obj:
33 return match_obj.group(1)
35 return None
37 def getoutput(cmd):
38 """Return output (stdout or stderr) of executing cmd in a shell."""
39 return getstatusoutput(cmd)[1]
41 def getstatusoutput(cmd):
42 """Return (status, output) of executing cmd in a shell."""
43 if sys.platform == 'win32':
44 pipe = os.popen(cmd, 'r')
45 text = pipe.read()
46 sts = pipe.close() or 0
47 if text[-1:] == '\n':
48 text = text[:-1]
49 return sts, text
50 else:
51 from commands import getstatusoutput
52 return getstatusoutput(cmd)
54 def have_pkgconfig():
55 """Checks for the existence of pkg-config"""
56 if (sys.platform == 'win32' and
57 os.system('pkg-config --version > NUL') == 0):
58 return 1
59 else:
60 if getstatusoutput('pkg-config')[0] == 256:
61 return 1
63 def list_files(dir):
64 """List all files in a dir, with filename match support:
65 for example: glade/*.glade will return all files in the glade directory
66 that matches *.glade. It also looks up the full path"""
67 if dir.find(os.sep) != -1:
68 parts = dir.split(os.sep)
69 dir = string.join(parts[:-1], os.sep)
70 pattern = parts[-1]
71 else:
72 pattern = dir
73 dir = '.'
75 dir = os.path.abspath(dir)
76 retval = []
77 for file in os.listdir(dir):
78 if fnmatch.fnmatch(file, pattern):
79 retval.append(os.path.join(dir, file))
80 return retval
82 def pkgc_version_check(name, req_version):
83 """Check the existence and version number of a package:
84 returns 0 if not installed or too old, 1 otherwise."""
85 is_installed = not os.system('pkg-config --exists %s' % name)
86 if not is_installed:
87 return 0
89 orig_version = getoutput('pkg-config --modversion %s' % name)
90 version = map(int, orig_version.split('.'))
91 pkc_version = map(int, req_version.split('.'))
93 if version >= pkc_version:
94 return 1
96 return 0
98 class BuildExt(build_ext):
99 def init_extra_compile_args(self):
100 self.extra_compile_args = []
101 if sys.platform == 'win32' and \
102 self.compiler.compiler_type == 'mingw32':
103 # MSVC compatible struct packing is required.
104 # Note gcc2 uses -fnative-struct while gcc3
105 # uses -mms-bitfields. Based on the version
106 # the proper flag is used below.
107 msnative_struct = { '2' : '-fnative-struct',
108 '3' : '-mms-bitfields' }
109 gcc_version = getoutput('gcc -dumpversion')
110 print 'using MinGW GCC version %s with %s option' % \
111 (gcc_version, msnative_struct[gcc_version[0]])
112 self.extra_compile_args.append(msnative_struct[gcc_version[0]])
114 def modify_compiler(self):
115 if sys.platform == 'win32' and \
116 self.compiler.compiler_type == 'mingw32':
117 # Remove '-static' linker option to prevent MinGW ld
118 # from trying to link with MSVC import libraries.
119 if self.compiler.linker_so.count('-static'):
120 self.compiler.linker_so.remove('-static')
122 def build_extensions(self):
123 # Init self.extra_compile_args
124 self.init_extra_compile_args()
125 # Modify default compiler settings
126 self.modify_compiler()
127 # Invoke base build_extensions()
128 build_ext.build_extensions(self)
130 def build_extension(self, ext):
131 # Add self.extra_compile_args to ext.extra_compile_args
132 ext.extra_compile_args += self.extra_compile_args
133 # Generate eventual templates before building
134 if hasattr(ext, 'generate'):
135 ext.generate()
136 # Filter out 'c' and 'm' libs when compilic w/ msvc
137 if sys.platform == 'win32' and self.compiler.compiler_type == 'msvc':
138 save_libs = ext.libraries
139 ext.libraries = [lib for lib in ext.libraries
140 if lib not in ['c', 'm']]
141 else:
142 save_libs = ext.libraries
143 # Invoke base build_extension()
144 build_ext.build_extension(self, ext)
145 if save_libs != None and save_libs != ext.libraries:
146 ext.libraries = save_libs
148 class InstallLib(install_lib):
150 local_outputs = []
151 local_inputs = []
153 def set_install_dir(self, install_dir):
154 self.install_dir = install_dir
156 def get_outputs(self):
157 return install_lib.get_outputs(self) + self.local_outputs
159 def get_inputs(self):
160 return install_lib.get_inputs(self) + self.local_inputs
162 class InstallData(install_data):
164 local_outputs = []
165 local_inputs = []
166 template_options = {}
168 def prepare(self):
169 if os.name == "nt":
170 self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-3])
171 else:
172 # default: os.name == "posix"
173 self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-4])
175 self.exec_prefix = '${prefix}/bin'
176 self.includedir = '${prefix}/include'
177 self.libdir = '${prefix}/lib'
178 self.datarootdir = '${prefix}/share'
179 self.datadir = '${prefix}/share'
181 self.add_template_option('prefix', self.prefix)
182 self.add_template_option('exec_prefix', self.exec_prefix)
183 self.add_template_option('includedir', self.includedir)
184 self.add_template_option('libdir', self.libdir)
185 self.add_template_option('datarootdir', self.datarootdir)
186 self.add_template_option('datadir', self.datadir)
187 self.add_template_option('PYTHON', sys.executable)
188 self.add_template_option('THREADING_CFLAGS', '')
190 def set_install_dir(self, install_dir):
191 self.install_dir = install_dir
193 def add_template_option(self, name, value):
194 self.template_options['@%s@' % name] = value
196 def install_template(self, filename, install_dir):
197 """Install template filename into target directory install_dir."""
198 output_file = os.path.split(filename)[-1][:-3]
200 template = open(filename).read()
201 for key, value in self.template_options.items():
202 template = template.replace(key, value)
204 output = os.path.join(install_dir, output_file)
205 self.mkpath(install_dir)
206 open(output, 'w').write(template)
207 self.local_inputs.append(filename)
208 self.local_outputs.append(output)
209 return output
211 def get_outputs(self):
212 return install_data.get_outputs(self) + self.local_outputs
214 def get_inputs(self):
215 return install_data.get_inputs(self) + self.local_inputs
217 class PkgConfigExtension(Extension):
218 # Name of pygobject package extension depends on, can be None
219 pygobject_pkc = 'pygobject-2.0'
220 can_build_ok = None
221 def __init__(self, **kwargs):
222 name = kwargs['pkc_name']
223 kwargs['include_dirs'] = self.get_include_dirs(name) + GLOBAL_INC
224 kwargs['define_macros'] = GLOBAL_MACROS
225 kwargs['libraries'] = self.get_libraries(name)
226 kwargs['library_dirs'] = self.get_library_dirs(name)
227 if 'pygobject_pkc' in kwargs:
228 self.pygobject_pkc = kwargs.pop('pygobject_pkc')
229 if self.pygobject_pkc:
230 kwargs['include_dirs'] += self.get_include_dirs(self.pygobject_pkc)
231 kwargs['libraries'] += self.get_libraries(self.pygobject_pkc)
232 kwargs['library_dirs'] += self.get_library_dirs(self.pygobject_pkc)
233 self.name = kwargs['name']
234 self.pkc_name = kwargs['pkc_name']
235 self.pkc_version = kwargs['pkc_version']
236 del kwargs['pkc_name'], kwargs['pkc_version']
237 Extension.__init__(self, **kwargs)
239 def get_include_dirs(self, names):
240 if type(names) != tuple:
241 names = (names,)
242 retval = []
243 for name in names:
244 output = getoutput('pkg-config --cflags-only-I %s' % name)
245 retval.extend(output.replace('-I', '').split())
246 return retval
248 def get_libraries(self, names):
249 if type(names) != tuple:
250 names = (names,)
251 retval = []
252 for name in names:
253 output = getoutput('pkg-config --libs-only-l %s' % name)
254 retval.extend(output.replace('-l', '').split())
255 return retval
257 def get_library_dirs(self, names):
258 if type(names) != tuple:
259 names = (names,)
260 retval = []
261 for name in names:
262 output = getoutput('pkg-config --libs-only-L %s' % name)
263 retval.extend(output.replace('-L', '').split())
264 return retval
266 def can_build(self):
267 """If the pkg-config version found is good enough"""
268 if self.can_build_ok != None:
269 return self.can_build_ok
271 if type(self.pkc_name) != tuple:
272 reqs = [(self.pkc_name, self.pkc_version)]
273 else:
274 reqs = zip(self.pkc_name, self.pkc_version)
276 for package, version in reqs:
277 retval = os.system('pkg-config --exists %s' % package)
278 if retval:
279 print ("* %s.pc could not be found, bindings for %s"
280 " will not be built." % (package, self.name))
281 self.can_build_ok = 0
282 return 0
284 orig_version = getoutput('pkg-config --modversion %s' %
285 package)
286 if (map(int, orig_version.split('.')) >=
287 map(int, version.split('.'))):
288 self.can_build_ok = 1
289 return 1
290 else:
291 print "Warning: Too old version of %s" % self.pkc_name
292 print " Need %s, but %s is installed" % \
293 (package, orig_version)
294 self.can_build_ok = 0
295 return 0
297 def generate(self):
298 pass
300 # The Template and TemplateExtension classes require codegen which is
301 # currently part of the pygtk distribution. While codegen might ultimately
302 # be moved to pygobject, it was decided (bug #353849) to keep the Template
303 # and TemplateExtension code in dsextras. In the meantime, we check for the
304 # availability of codegen and redirect the user to the pygtk installer if
305 # he/she wants to get access to Template and TemplateExtension.
307 template_classes_enabled=True
308 codegen_error_message="""
309 ***************************************************************************
310 Codegen could not be found on your system and is required by the
311 dsextras.Template and dsextras.TemplateExtension classes. codegen is part
312 of PyGTK. To use either Template or TemplateExtension, you should also
313 install PyGTK.
314 ***************************************************************************
316 try:
317 from codegen.override import Overrides
318 from codegen.defsparser import DefsParser
319 from codegen.codegen import register_types, SourceWriter, \
320 FileOutput
321 import codegen.createdefs
322 except ImportError, e:
323 template_classes_enabled=False
325 class Template(object):
326 def __new__(cls,*args, **kwds):
327 if not template_classes_enabled:
328 raise NameError("'%s' is not defined\n" % cls.__name__
329 + codegen_error_message)
330 return object.__new__(cls,*args, **kwds)
332 def __init__(self, override, output, defs, prefix,
333 register=[], load_types=None, py_ssize_t_clean=False):
335 self.override = override
336 self.output = output
337 self.prefix = prefix
338 self.load_types = load_types
339 self.py_ssize_t_clean = py_ssize_t_clean
341 self.built_defs=[]
342 if isinstance(defs,tuple):
343 self.defs=defs[0]
344 self.built_defs.append(defs)
345 else:
346 self.defs=defs
348 self.register=[]
349 for r in register:
350 if isinstance(r,tuple):
351 self.register.append(r[0])
352 self.built_defs.append(r)
353 else:
354 self.register.append(r)
356 def check_dates(self):
357 # Return True if files are up-to-date
358 files=self.register[:]
359 files.append(self.override)
360 files.append(self.defs)
362 return not distutils.dep_util.newer_group(files,self.output)
364 def generate_defs(self):
365 for (target,sources) in self.built_defs:
366 if distutils.dep_util.newer_group(sources,target):
367 # createdefs is mostly called from the CLI !
368 args=['dummy',target]+sources
369 codegen.createdefs.main(args)
372 def generate(self):
373 # Generate defs files if necessary
374 self.generate_defs()
375 # ... then check the file timestamps
376 if self.check_dates():
377 return
379 for item in self.register:
380 dp = DefsParser(item,dict(GLOBAL_MACROS))
381 dp.startParsing()
382 register_types(dp)
384 if self.load_types:
385 globals = {}
386 execfile(self.load_types, globals)
388 dp = DefsParser(self.defs,dict(GLOBAL_MACROS))
389 dp.startParsing()
390 register_types(dp)
392 fd = open(self.output, 'w')
393 sw = SourceWriter(dp,Overrides(self.override),
394 self.prefix,FileOutput(fd,self.output))
395 sw.write(self.py_ssize_t_clean)
396 fd.close()
398 class TemplateExtension(PkgConfigExtension):
399 def __new__(cls,*args, **kwds):
400 if not template_classes_enabled:
401 raise NameError("'%s' is not defined\n" % cls.__name__
402 + codegen_error_message)
403 return PkgConfigExtension.__new__(cls,*args, **kwds)
405 def __init__(self, **kwargs):
406 name = kwargs['name']
407 defs = kwargs['defs']
408 if isinstance(defs,tuple):
409 output = defs[0][:-5] + '.c'
410 else:
411 output = defs[:-5] + '.c'
412 override = kwargs['override']
413 load_types = kwargs.get('load_types')
414 py_ssize_t_clean = kwargs.pop('py_ssize_t_clean',False)
415 self.templates = []
416 self.templates.append(Template(override, output, defs, 'py' + name,
417 kwargs['register'], load_types,
418 py_ssize_t_clean))
420 del kwargs['register'], kwargs['override'], kwargs['defs']
421 if load_types:
422 del kwargs['load_types']
424 if kwargs.has_key('output'):
425 kwargs['name'] = kwargs['output']
426 del kwargs['output']
428 PkgConfigExtension.__init__(self, **kwargs)
430 def generate(self):
431 map(lambda x: x.generate(), self.templates)