1 # -*- coding: utf-8 -*-
3 # dsextras.py - Extra classes and utilities for distutils,
4 # adding pkg-config support
13 from distutils import dep_util
14 from distutils.command.build_ext import build_ext
15 from distutils.command.install_lib import install_lib
16 from distutils.command.install_data import install_data
17 from distutils.extension import Extension
18 from distutils.spawn import find_executable
21 import codegen.createdefs
22 from codegen.override import Overrides
23 from codegen.defsparser import DefsParser
24 from codegen.codegen import register_types, SourceWriter, FileOutput
26 template_classes_enabled = False
28 template_classes_enabled = True
34 codegen_error_message='''
35 ***************************************************************************
36 Codegen could not be found on your system and is required by the
37 dsextras.Template and dsextras.TemplateExtension classes.
38 ***************************************************************************
42 def get_m4_define(varname):
43 '''Return the value of a m4_define variable as set in configure.in.'''
44 pattern = re.compile('m4_define\(' + varname + '\,\s*(.+)\)')
46 if os.path.exists('configure.ac'):
47 fname = 'configure.ac'
48 elif os.path.exists('configure.in'):
49 fname = 'configure.in'
51 raise SystemExit('ERROR: Could not find configure file')
53 for line in open(fname).readlines():
54 match_obj = pattern.match(line)
57 return match_obj.group(1)
62 '''Return output (stdout or stderr) of executing cmd in a shell.'''
63 return getstatusoutput(cmd)[1]
65 def getstatusoutput(cmd):
66 '''Return (status, output) of executing cmd in a shell.'''
67 if sys.platform == 'win32':
68 pipe = os.popen(cmd, 'r')
70 sts = pipe.close() or 0
72 while text[-1:] in ['\n', '\r']:
77 from commands import getstatusoutput
78 return getstatusoutput(cmd)
81 '''Checks for the existence of gcc'''
82 if find_executable('gcc'):
86 '''Checks for the existence of pkg-config'''
87 if find_executable('pkg-config'):
91 '''List all files in a dir, with filename match support:
92 for example: glade/*.glade will return all files in the glade directory
93 that matches *.glade. It also looks up the full path'''
94 if dir.find(os.sep) != -1:
95 parts = dir.split(os.sep)
96 dir = string.join(parts[:-1], os.sep)
102 dir = os.path.abspath(dir)
105 for file in os.listdir(dir):
106 if fnmatch.fnmatch(file, pattern):
107 retval.append(os.path.join(dir, file))
111 def pkgc_version_check(name, req_version):
112 '''Check the existence and version number of a package:
113 returns False if not installed or too old, True otherwise.'''
114 is_installed = not os.system('pkg-config --exists %s' % name)
119 orig_version = pkgc_get_version(name)
120 version = map(int, orig_version.split('.'))
121 pkc_version = map(int, req_version.split('.'))
123 if version >= pkc_version:
128 def pkgc_get_version(name):
129 '''return the version as return by pkg-config --modversion'''
130 return getoutput('pkg-config --modversion %s' % name)
132 def pkgc_get_libraries(name):
133 '''returns a list of libraries as returned by pkg-config --libs-only-l'''
134 output = getoutput('pkg-config --libs-only-l %s' % name)
135 return output.replace('-l', '').split()
137 def pkgc_get_library_dirs(name):
138 '''returns a list of library dirs as returned by pkg-config --libs-only-L'''
139 output = getoutput('pkg-config --libs-only-L %s' % name)
140 return output.replace('-L', '').split()
142 def pkgc_get_include_dirs(name):
143 '''returns a list of include dirs as returned by pkg-config --cflags-only-I'''
144 output = getoutput('pkg-config --cflags-only-I %s' % name)
145 return output.replace('-I', '').split()
147 def pkgc_get_defs_dir(name):
148 '''returns the defs dir as returned by pkg-config --variable=defsdir'''
149 output = getoutput('pkg-config --variable=defsdir %s' % name)
153 class BuildExt(build_ext):
154 def init_extra_compile_args(self):
155 self.extra_compile_args = []
157 if sys.platform == 'win32' and self.compiler.compiler_type == 'mingw32':
159 raise SystemExit('ERROR: Could not find gcc.')
161 # MSVC compatible struct packing is required.
162 # Note gcc2 uses -fnative-struct while gcc3
163 # and gcc4 use -mms-bitfields. Based on the
164 # version the proper flag is used below.
165 msnative_struct = {'2': '-fnative-struct',
166 '3': '-mms-bitfields',
167 '4': '-mms-bitfields'}
168 gcc_version = getoutput('gcc -dumpversion')
170 print ('using MinGW GCC version %s with %s option' % \
171 (gcc_version, msnative_struct[gcc_version[0]]))
173 self.extra_compile_args.append(msnative_struct[gcc_version[0]])
175 def modify_compiler(self):
176 if sys.platform == 'win32' and self.compiler.compiler_type == 'mingw32':
178 raise SystemExit('ERROR: Could not find gcc.')
180 # Remove '-static' linker option to prevent MinGW ld
181 # from trying to link with MSVC import libraries.
182 if self.compiler.linker_so.count('-static'):
183 self.compiler.linker_so.remove('-static')
185 def build_extensions(self):
186 # Init self.extra_compile_args
187 self.init_extra_compile_args()
188 # Modify default compiler settings
189 self.modify_compiler()
190 # Invoke base build_extensions()
191 build_ext.build_extensions(self)
193 def build_extension(self, ext):
194 # Add self.extra_compile_args to ext.extra_compile_args
195 ext.extra_compile_args += self.extra_compile_args
197 # Generate eventual templates before building
198 if hasattr(ext, 'generate'):
201 # Filter out 'c' and 'm' libs when compilic w/ msvc
202 if sys.platform == 'win32' and self.compiler.compiler_type == 'msvc':
203 save_libs = ext.libraries
204 ext.libraries = [lib for lib in ext.libraries
205 if lib not in ['c', 'm']]
207 save_libs = ext.libraries
209 # Invoke base build_extension()
210 build_ext.build_extension(self, ext)
212 if save_libs is not None and save_libs != ext.libraries:
213 ext.libraries = save_libs
216 class InstallLib(install_lib):
220 def set_install_dir(self, install_dir):
221 self.install_dir = install_dir
223 def get_outputs(self):
224 return install_lib.get_outputs(self) + self.local_outputs
226 def get_inputs(self):
227 return install_lib.get_inputs(self) + self.local_inputs
230 class InstallData(install_data):
233 template_options = {}
237 self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-3])
239 # default: os.name == 'posix'
240 self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-4])
242 self.exec_prefix = '${prefix}/bin'
243 self.includedir = '${prefix}/include'
244 self.libdir = '${prefix}/lib'
245 self.datarootdir = '${prefix}/share'
246 self.datadir = '${prefix}/share'
248 self.add_template_option('prefix', self.prefix)
249 self.add_template_option('exec_prefix', self.exec_prefix)
250 self.add_template_option('includedir', self.includedir)
251 self.add_template_option('libdir', self.libdir)
252 self.add_template_option('datarootdir', self.datarootdir)
253 self.add_template_option('datadir', self.datadir)
254 self.add_template_option('PYTHON', sys.executable)
255 self.add_template_option('THREADING_CFLAGS', '')
257 def set_install_dir(self, install_dir):
258 self.install_dir = install_dir
260 def add_template_option(self, name, value):
261 self.template_options['@%s@' % name] = value
263 def install_template(self, filename, install_dir):
264 '''Install template filename into target directory install_dir.'''
265 output_file = os.path.split(filename)[-1][:-3]
267 template = open(filename).read()
269 for key, value in self.template_options.items():
270 template = template.replace(key, value)
272 output = os.path.join(install_dir, output_file)
273 self.mkpath(install_dir)
274 open(output, 'wb').write(template)
275 self.local_inputs.append(filename)
276 self.local_outputs.append(output)
279 def get_outputs(self):
280 return install_data.get_outputs(self) + self.local_outputs
282 def get_inputs(self):
283 return install_data.get_inputs(self) + self.local_inputs
286 class PkgConfigExtension(Extension):
287 # Name of pygobject package extension depends on, can be None
288 pygobject_pkc = 'pygobject-2.0'
291 def __init__(self, **kwargs):
292 name = kwargs['pkc_name']
294 if 'include_dirs' in kwargs:
295 kwargs['include_dirs'] += self.get_include_dirs(name) + GLOBAL_INC
297 kwargs['include_dirs'] = self.get_include_dirs(name) + GLOBAL_INC
299 kwargs['define_macros'] = GLOBAL_MACROS
301 if 'libraries' in kwargs:
302 kwargs['libraries'] += self.get_libraries(name)
304 kwargs['libraries'] = self.get_libraries(name)
306 if 'library_dirs' in kwargs:
307 kwargs['library_dirs'] += self.get_library_dirs(name)
309 kwargs['library_dirs'] = self.get_library_dirs(name)
311 if 'pygobject_pkc' in kwargs:
312 self.pygobject_pkc = kwargs.pop('pygobject_pkc')
314 if self.pygobject_pkc:
315 kwargs['include_dirs'] += self.get_include_dirs(self.pygobject_pkc)
316 kwargs['libraries'] += self.get_libraries(self.pygobject_pkc)
317 kwargs['library_dirs'] += self.get_library_dirs(self.pygobject_pkc)
319 self.name = kwargs['name']
320 self.pkc_name = kwargs['pkc_name']
321 self.pkc_version = kwargs['pkc_version']
322 del kwargs['pkc_name'], kwargs['pkc_version']
323 Extension.__init__(self, **kwargs)
325 def get_include_dirs(self, names):
326 if type(names) != tuple:
332 retval.extend(pkgc_get_include_dirs(name))
336 def get_libraries(self, names):
337 if type(names) != tuple:
343 retval.extend(pkgc_get_libraries(name))
347 def get_library_dirs(self, names):
348 if type(names) != tuple:
354 retval.extend(pkgc_get_library_dirs(name))
359 '''If the pkg-config version found is good enough'''
360 if self.can_build_ok is not None:
361 return self.can_build_ok
363 if type(self.pkc_name) != tuple:
364 reqs = [(self.pkc_name, self.pkc_version)]
366 reqs = zip(self.pkc_name, self.pkc_version)
368 for package, version in reqs:
369 retval = os.system('pkg-config --exists %s' % package)
372 print ('* %s.pc could not be found, bindings for %s'
373 ' will not be built.' % (package, self.name))
374 self.can_build_ok = False
377 orig_version = pkgc_get_version(package)
379 if (map(int, orig_version.split('.')) >=
380 map(int, version.split('.'))):
382 self.can_build_ok = True
385 print ('Warning: Too old version of %s' % package)
386 print (' Need %s, but %s is installed' % (version, orig_version))
387 self.can_build_ok = False
394 class Template(object):
395 def __new__(cls, *args, **kwds):
396 # The Template and TemplateExtension classes require codegen
397 if not template_classes_enabled:
398 raise NameError('\'%s\' is not defined\n%s' % (cls.__name__,
399 codegen_error_message))
401 return object.__new__(cls)
403 def __init__(self, override, output, defs, prefix,
404 register=[], load_types=None, py_ssize_t_clean=False):
406 self.override = override
409 self.load_types = load_types
410 self.py_ssize_t_clean = py_ssize_t_clean
414 if isinstance(defs, tuple):
416 self.built_defs.append(defs)
423 if isinstance(r, tuple):
424 self.register.append(r[0])
425 self.built_defs.append(r)
427 self.register.append(r)
429 def check_dates(self):
430 # Return True if files are up-to-date
431 files=self.register[:]
432 files.append(self.override)
433 files.append(self.defs)
435 return not dep_util.newer_group(files, self.output)
437 def generate_defs(self):
438 for (target, sources) in self.built_defs:
439 if dep_util.newer_group(sources, target):
440 # createdefs is mostly called from the CLI !
441 args=['dummy', target] + sources
442 codegen.createdefs.main(args)
445 # Generate defs files if necessary
448 # ... then check the file timestamps
449 if self.check_dates():
452 for item in self.register:
453 dp = DefsParser(item, dict(GLOBAL_MACROS))
459 execfile(self.load_types, globals)
461 dp = DefsParser(self.defs, dict(GLOBAL_MACROS))
465 fd = open(self.output, 'w')
466 sw = SourceWriter(dp, Overrides(self.override), self.prefix,
467 FileOutput(fd, self.output))
468 sw.write(self.py_ssize_t_clean)
472 class TemplateExtension(PkgConfigExtension):
473 def __new__(cls,*args, **kwds):
474 if not template_classes_enabled:
475 raise NameError('\'%s\' is not defined\n%s' % (cls.__name__,
476 codegen_error_message))
478 return PkgConfigExtension.__new__(cls,*args, **kwds)
480 def __init__(self, **kwargs):
481 name = kwargs['name']
482 defs = kwargs['defs']
484 if isinstance(defs, tuple):
485 output = defs[0][:-5] + '.c'
487 output = defs[:-5] + '.c'
489 override = kwargs['override']
490 load_types = kwargs.get('load_types')
491 py_ssize_t_clean = kwargs.pop('py_ssize_t_clean', False)
493 self.templates.append(Template(override, output, defs, 'py' + name,
494 kwargs['register'], load_types,
497 del kwargs['register'], kwargs['override'], kwargs['defs']
500 del kwargs['load_types']
502 if kwargs.has_key('output'):
503 kwargs['name'] = kwargs['output']
506 PkgConfigExtension.__init__(self, **kwargs)
509 map(lambda x: x.generate(), self.templates)