2 # dsextras.py - Extra classes and utilities for distutils, adding
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
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'
28 raise SystemExit('could not find configure file')
30 for line in open(fname).readlines():
31 match_obj = pattern.match(line)
33 return match_obj.group(1)
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')
46 sts = pipe.close() or 0
51 from commands import getstatusoutput
52 return getstatusoutput(cmd)
55 """Checks for the existence of pkg-config"""
56 if (sys.platform == 'win32' and
57 os.system('pkg-config --version > NUL') == 0):
60 if getstatusoutput('pkg-config')[0] == 256:
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)
75 dir = os.path.abspath(dir)
77 for file in os.listdir(dir):
78 if fnmatch.fnmatch(file, pattern):
79 retval.append(os.path.join(dir, file))
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)
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:
98 def pkgc_get_libraries(name):
99 """returns a list of libraries as returned by pkg-config --libs-only-l"""
100 output = getoutput('pkg-config --libs-only-l %s' % name)
101 return output.replace('-l', '').split()
103 def pkgc_get_library_dirs(name):
104 """returns a list of library dirs as returned by pkg-config --libs-only-L"""
105 output = getoutput('pkg-config --libs-only-L %s' % name)
106 return output.replace('-L', '').split()
108 def pkgc_get_include_dirs(name):
109 """returns a list of include dirs as returned by pkg-config --cflags-only-I"""
110 output = getoutput('pkg-config --cflags-only-I %s' % name)
111 return output.replace('-I', '').split()
113 class BuildExt(build_ext):
114 def init_extra_compile_args(self):
115 self.extra_compile_args = []
116 if sys.platform == 'win32' and \
117 self.compiler.compiler_type == 'mingw32':
118 # MSVC compatible struct packing is required.
119 # Note gcc2 uses -fnative-struct while gcc3
120 # uses -mms-bitfields. Based on the version
121 # the proper flag is used below.
122 msnative_struct = { '2' : '-fnative-struct',
123 '3' : '-mms-bitfields' }
124 gcc_version = getoutput('gcc -dumpversion')
125 print 'using MinGW GCC version %s with %s option' % \
126 (gcc_version, msnative_struct[gcc_version[0]])
127 self.extra_compile_args.append(msnative_struct[gcc_version[0]])
129 def modify_compiler(self):
130 if sys.platform == 'win32' and \
131 self.compiler.compiler_type == 'mingw32':
132 # Remove '-static' linker option to prevent MinGW ld
133 # from trying to link with MSVC import libraries.
134 if self.compiler.linker_so.count('-static'):
135 self.compiler.linker_so.remove('-static')
137 def build_extensions(self):
138 # Init self.extra_compile_args
139 self.init_extra_compile_args()
140 # Modify default compiler settings
141 self.modify_compiler()
142 # Invoke base build_extensions()
143 build_ext.build_extensions(self)
145 def build_extension(self, ext):
146 # Add self.extra_compile_args to ext.extra_compile_args
147 ext.extra_compile_args += self.extra_compile_args
148 # Generate eventual templates before building
149 if hasattr(ext, 'generate'):
151 # Filter out 'c' and 'm' libs when compilic w/ msvc
152 if sys.platform == 'win32' and self.compiler.compiler_type == 'msvc':
153 save_libs = ext.libraries
154 ext.libraries = [lib for lib in ext.libraries
155 if lib not in ['c', 'm']]
157 save_libs = ext.libraries
158 # Invoke base build_extension()
159 build_ext.build_extension(self, ext)
160 if save_libs != None and save_libs != ext.libraries:
161 ext.libraries = save_libs
163 class InstallLib(install_lib):
168 def set_install_dir(self, install_dir):
169 self.install_dir = install_dir
171 def get_outputs(self):
172 return install_lib.get_outputs(self) + self.local_outputs
174 def get_inputs(self):
175 return install_lib.get_inputs(self) + self.local_inputs
177 class InstallData(install_data):
181 template_options = {}
185 self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-3])
187 # default: os.name == "posix"
188 self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-4])
190 self.exec_prefix = '${prefix}/bin'
191 self.includedir = '${prefix}/include'
192 self.libdir = '${prefix}/lib'
193 self.datarootdir = '${prefix}/share'
194 self.datadir = '${prefix}/share'
196 self.add_template_option('prefix', self.prefix)
197 self.add_template_option('exec_prefix', self.exec_prefix)
198 self.add_template_option('includedir', self.includedir)
199 self.add_template_option('libdir', self.libdir)
200 self.add_template_option('datarootdir', self.datarootdir)
201 self.add_template_option('datadir', self.datadir)
202 self.add_template_option('PYTHON', sys.executable)
203 self.add_template_option('THREADING_CFLAGS', '')
205 def set_install_dir(self, install_dir):
206 self.install_dir = install_dir
208 def add_template_option(self, name, value):
209 self.template_options['@%s@' % name] = value
211 def install_template(self, filename, install_dir):
212 """Install template filename into target directory install_dir."""
213 output_file = os.path.split(filename)[-1][:-3]
215 template = open(filename).read()
216 for key, value in self.template_options.items():
217 template = template.replace(key, value)
219 output = os.path.join(install_dir, output_file)
220 self.mkpath(install_dir)
221 open(output, 'w').write(template)
222 self.local_inputs.append(filename)
223 self.local_outputs.append(output)
226 def get_outputs(self):
227 return install_data.get_outputs(self) + self.local_outputs
229 def get_inputs(self):
230 return install_data.get_inputs(self) + self.local_inputs
232 class PkgConfigExtension(Extension):
233 # Name of pygobject package extension depends on, can be None
234 pygobject_pkc = 'pygobject-2.0'
236 def __init__(self, **kwargs):
237 name = kwargs['pkc_name']
238 if 'include_dirs' in kwargs:
239 kwargs['include_dirs'] += self.get_include_dirs(name) + GLOBAL_INC
241 kwargs['include_dirs'] = self.get_include_dirs(name) + GLOBAL_INC
242 kwargs['define_macros'] = GLOBAL_MACROS
243 if 'libraries' in kwargs:
244 kwargs['libraries'] += self.get_libraries(name)
246 kwargs['libraries'] = self.get_libraries(name)
247 if 'library_dirs' in kwargs:
248 kwargs['library_dirs'] += self.get_library_dirs(name)
250 kwargs['library_dirs'] = self.get_library_dirs(name)
251 if 'pygobject_pkc' in kwargs:
252 self.pygobject_pkc = kwargs.pop('pygobject_pkc')
253 if self.pygobject_pkc:
254 kwargs['include_dirs'] += self.get_include_dirs(self.pygobject_pkc)
255 kwargs['libraries'] += self.get_libraries(self.pygobject_pkc)
256 kwargs['library_dirs'] += self.get_library_dirs(self.pygobject_pkc)
257 self.name = kwargs['name']
258 self.pkc_name = kwargs['pkc_name']
259 self.pkc_version = kwargs['pkc_version']
260 del kwargs['pkc_name'], kwargs['pkc_version']
261 Extension.__init__(self, **kwargs)
263 def get_include_dirs(self, names):
264 if type(names) != tuple:
268 output = getoutput('pkg-config --cflags-only-I %s' % name)
269 retval.extend(output.replace('-I', '').split())
272 def get_libraries(self, names):
273 if type(names) != tuple:
277 output = getoutput('pkg-config --libs-only-l %s' % name)
278 retval.extend(output.replace('-l', '').split())
281 def get_library_dirs(self, names):
282 if type(names) != tuple:
286 output = getoutput('pkg-config --libs-only-L %s' % name)
287 retval.extend(output.replace('-L', '').split())
291 """If the pkg-config version found is good enough"""
292 if self.can_build_ok != None:
293 return self.can_build_ok
295 if type(self.pkc_name) != tuple:
296 reqs = [(self.pkc_name, self.pkc_version)]
298 reqs = zip(self.pkc_name, self.pkc_version)
300 for package, version in reqs:
301 retval = os.system('pkg-config --exists %s' % package)
303 print ("* %s.pc could not be found, bindings for %s"
304 " will not be built." % (package, self.name))
305 self.can_build_ok = 0
308 orig_version = getoutput('pkg-config --modversion %s' %
310 if (map(int, orig_version.split('.')) >=
311 map(int, version.split('.'))):
312 self.can_build_ok = 1
315 print "Warning: Too old version of %s" % self.pkc_name
316 print " Need %s, but %s is installed" % \
317 (version, orig_version)
318 self.can_build_ok = 0
324 # The Template and TemplateExtension classes require codegen which is
325 # currently part of the pygtk distribution. While codegen might ultimately
326 # be moved to pygobject, it was decided (bug #353849) to keep the Template
327 # and TemplateExtension code in dsextras. In the meantime, we check for the
328 # availability of codegen and redirect the user to the pygtk installer if
329 # he/she wants to get access to Template and TemplateExtension.
331 template_classes_enabled=True
332 codegen_error_message="""
333 ***************************************************************************
334 Codegen could not be found on your system and is required by the
335 dsextras.Template and dsextras.TemplateExtension classes. codegen is part
336 of PyGTK. To use either Template or TemplateExtension, you should also
338 ***************************************************************************
341 from codegen.override import Overrides
342 from codegen.defsparser import DefsParser
343 from codegen.codegen import register_types, SourceWriter, \
345 import codegen.createdefs
346 except ImportError, e:
347 template_classes_enabled=False
349 class Template(object):
350 def __new__(cls,*args, **kwds):
351 if not template_classes_enabled:
352 raise NameError("'%s' is not defined\n" % cls.__name__
353 + codegen_error_message)
354 return object.__new__(cls,*args, **kwds)
356 def __init__(self, override, output, defs, prefix,
357 register=[], load_types=None, py_ssize_t_clean=False):
359 self.override = override
362 self.load_types = load_types
363 self.py_ssize_t_clean = py_ssize_t_clean
366 if isinstance(defs,tuple):
368 self.built_defs.append(defs)
374 if isinstance(r,tuple):
375 self.register.append(r[0])
376 self.built_defs.append(r)
378 self.register.append(r)
380 def check_dates(self):
381 # Return True if files are up-to-date
382 files=self.register[:]
383 files.append(self.override)
384 files.append(self.defs)
386 return not distutils.dep_util.newer_group(files,self.output)
388 def generate_defs(self):
389 for (target,sources) in self.built_defs:
390 if distutils.dep_util.newer_group(sources,target):
391 # createdefs is mostly called from the CLI !
392 args=['dummy',target]+sources
393 codegen.createdefs.main(args)
397 # Generate defs files if necessary
399 # ... then check the file timestamps
400 if self.check_dates():
403 for item in self.register:
404 dp = DefsParser(item,dict(GLOBAL_MACROS))
410 execfile(self.load_types, globals)
412 dp = DefsParser(self.defs,dict(GLOBAL_MACROS))
416 fd = open(self.output, 'w')
417 sw = SourceWriter(dp,Overrides(self.override),
418 self.prefix,FileOutput(fd,self.output))
419 sw.write(self.py_ssize_t_clean)
422 class TemplateExtension(PkgConfigExtension):
423 def __new__(cls,*args, **kwds):
424 if not template_classes_enabled:
425 raise NameError("'%s' is not defined\n" % cls.__name__
426 + codegen_error_message)
427 return PkgConfigExtension.__new__(cls,*args, **kwds)
429 def __init__(self, **kwargs):
430 name = kwargs['name']
431 defs = kwargs['defs']
432 if isinstance(defs,tuple):
433 output = defs[0][:-5] + '.c'
435 output = defs[:-5] + '.c'
436 override = kwargs['override']
437 load_types = kwargs.get('load_types')
438 py_ssize_t_clean = kwargs.pop('py_ssize_t_clean',False)
440 self.templates.append(Template(override, output, defs, 'py' + name,
441 kwargs['register'], load_types,
444 del kwargs['register'], kwargs['override'], kwargs['defs']
446 del kwargs['load_types']
448 if kwargs.has_key('output'):
449 kwargs['name'] = kwargs['output']
452 PkgConfigExtension.__init__(self, **kwargs)
455 map(lambda x: x.generate(), self.templates)