TIVI-320: required by connman-test
[profile/ivi/pygobject2.git] / dsextras.py
1 #
2 # dsextras.py - Extra classes and utilities for distutils, adding
3 #               pkg-config support
4
5
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
16
17 GLOBAL_INC = []
18 GLOBAL_MACROS = []
19
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')
29
30     for line in open(fname).readlines():
31         match_obj = pattern.match(line)
32         if match_obj:
33             return match_obj.group(1)
34
35     return None
36
37 def getoutput(cmd):
38     """Return output (stdout or stderr) of executing cmd in a shell."""
39     return getstatusoutput(cmd)[1]
40
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)
53
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
62
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 = '.'
74
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
81
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
88
89     orig_version = getoutput('pkg-config --modversion %s' % name)
90     version = map(int, orig_version.split('.'))
91     pkc_version = map(int, req_version.split('.'))
92
93     if version >= pkc_version:
94         return 1
95         
96     return 0
97
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()
102
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()
107
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()
112
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]])
128
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')
136
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)
144
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'):
150             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']]
156         else:
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
162
163 class InstallLib(install_lib):
164
165     local_outputs = []
166     local_inputs = []
167
168     def set_install_dir(self, install_dir):
169         self.install_dir = install_dir
170
171     def get_outputs(self):
172         return install_lib.get_outputs(self) + self.local_outputs
173
174     def get_inputs(self):
175         return install_lib.get_inputs(self) + self.local_inputs
176
177 class InstallData(install_data):
178
179     local_outputs = []
180     local_inputs = []
181     template_options = {}
182
183     def prepare(self):
184         if os.name == "nt":
185             self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-3])
186         else:
187             # default: os.name == "posix"
188             self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-4])
189
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'
195
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', '')
204
205     def set_install_dir(self, install_dir):
206         self.install_dir = install_dir
207
208     def add_template_option(self, name, value):
209         self.template_options['@%s@' % name] = value
210
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]
214
215         template = open(filename).read()
216         for key, value in self.template_options.items():
217             template = template.replace(key, value)
218
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)
224         return output
225
226     def get_outputs(self):
227         return install_data.get_outputs(self) + self.local_outputs
228
229     def get_inputs(self):
230         return install_data.get_inputs(self) + self.local_inputs
231
232 class PkgConfigExtension(Extension):
233     # Name of pygobject package extension depends on, can be None
234     pygobject_pkc = 'pygobject-2.0'
235     can_build_ok = None
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
240         else:
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)
245         else:
246             kwargs['libraries'] = self.get_libraries(name)
247         if 'library_dirs' in kwargs:
248             kwargs['library_dirs'] += self.get_library_dirs(name)
249         else:
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)
262
263     def get_include_dirs(self, names):
264         if type(names) != tuple:
265             names = (names,)
266         retval = []
267         for name in names:
268             output = getoutput('pkg-config --cflags-only-I %s' % name)
269             retval.extend(output.replace('-I', '').split())
270         return retval
271
272     def get_libraries(self, names):
273         if type(names) != tuple:
274             names = (names,)
275         retval = []
276         for name in names:
277             output = getoutput('pkg-config --libs-only-l %s' % name)
278             retval.extend(output.replace('-l', '').split())
279         return retval
280
281     def get_library_dirs(self, names):
282         if type(names) != tuple:
283             names = (names,)
284         retval = []
285         for name in names:
286             output = getoutput('pkg-config --libs-only-L %s' % name)
287             retval.extend(output.replace('-L', '').split())
288         return retval
289
290     def can_build(self):
291         """If the pkg-config version found is good enough"""
292         if self.can_build_ok != None:
293             return self.can_build_ok
294
295         if type(self.pkc_name) != tuple:
296             reqs = [(self.pkc_name, self.pkc_version)]
297         else:
298             reqs = zip(self.pkc_name, self.pkc_version)
299
300         for package, version in reqs:
301             retval = os.system('pkg-config --exists %s' % package)
302             if retval:
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
306                 return 0
307
308             orig_version = getoutput('pkg-config --modversion %s' %
309                                      package)
310             if (map(int, orig_version.split('.')) >=
311                 map(int, version.split('.'))):
312                 self.can_build_ok = 1
313                 return 1
314             else:
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
319                 return 0
320
321     def generate(self):
322         pass
323
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.
330
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
337 install PyGTK.
338 ***************************************************************************
339 """
340 try:
341     from codegen.override import Overrides
342     from codegen.defsparser import DefsParser
343     from codegen.codegen import register_types, SourceWriter, \
344          FileOutput
345     import codegen.createdefs
346 except ImportError, e:
347     template_classes_enabled=False
348
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)
355
356     def __init__(self, override, output, defs, prefix,
357                  register=[], load_types=None, py_ssize_t_clean=False):
358         
359         self.override = override
360         self.output = output
361         self.prefix = prefix
362         self.load_types = load_types
363         self.py_ssize_t_clean = py_ssize_t_clean
364
365         self.built_defs=[]
366         if isinstance(defs,tuple):
367             self.defs=defs[0]
368             self.built_defs.append(defs)
369         else:
370             self.defs=defs
371
372         self.register=[]
373         for r in register:
374             if isinstance(r,tuple):
375                 self.register.append(r[0])
376                 self.built_defs.append(r)
377             else:
378                 self.register.append(r)
379
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)
385
386         return not distutils.dep_util.newer_group(files,self.output)
387
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)
394
395
396     def generate(self):
397         # Generate defs files if necessary
398         self.generate_defs()
399         # ... then check the file timestamps
400         if self.check_dates():
401             return
402
403         for item in self.register:
404             dp = DefsParser(item,dict(GLOBAL_MACROS))
405             dp.startParsing()
406             register_types(dp)
407
408         if self.load_types:
409             globals = {}
410             execfile(self.load_types, globals)
411
412         dp = DefsParser(self.defs,dict(GLOBAL_MACROS))
413         dp.startParsing()
414         register_types(dp)
415
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)
420         fd.close()
421
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)
428     
429     def __init__(self, **kwargs):
430         name = kwargs['name']
431         defs = kwargs['defs']
432         if isinstance(defs,tuple):
433             output = defs[0][:-5] + '.c'
434         else:
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)
439         self.templates = []
440         self.templates.append(Template(override, output, defs, 'py' + name,
441                                        kwargs['register'], load_types,
442                                        py_ssize_t_clean))
443
444         del kwargs['register'], kwargs['override'], kwargs['defs']
445         if load_types:
446             del kwargs['load_types']
447
448         if kwargs.has_key('output'):
449             kwargs['name'] = kwargs['output']
450             del kwargs['output']
451
452         PkgConfigExtension.__init__(self, **kwargs)
453
454     def generate(self):
455         map(lambda x: x.generate(), self.templates)
456
457