source: pkg/main/pygobject/trunk/dsextras.py @ 69

Revision 24, 15.0 KB checked in by alanbach-guest, 6 years ago (diff)

[svn-inject] Installing original source of pygobject

Line 
1#
2# dsextras.py - Extra classes and utilities for distutils, adding
3#               pkg-config support
4
5
6from distutils.command.build_ext import build_ext
7from distutils.command.install_lib import install_lib
8from distutils.command.install_data import install_data
9from distutils.extension import Extension
10import distutils.dep_util
11import fnmatch
12import os
13import re
14import string
15import sys
16
17GLOBAL_INC = []
18GLOBAL_MACROS = []
19
20def 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
37def getoutput(cmd):
38    """Return output (stdout or stderr) of executing cmd in a shell."""
39    return getstatusoutput(cmd)[1]
40
41def 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
54def 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
63def 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
82def 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
98class 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]])
113
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')
121
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)
129
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
147
148class InstallLib(install_lib):
149
150    local_outputs = []
151    local_inputs = []
152
153    def set_install_dir(self, install_dir):
154        self.install_dir = install_dir
155
156    def get_outputs(self):
157        return install_lib.get_outputs(self) + self.local_outputs
158
159    def get_inputs(self):
160        return install_lib.get_inputs(self) + self.local_inputs
161
162class InstallData(install_data):
163
164    local_outputs = []
165    local_inputs = []
166    template_options = {}
167
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])
174
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'
180
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', '')
189
190    def set_install_dir(self, install_dir):
191        self.install_dir = install_dir
192
193    def add_template_option(self, name, value):
194        self.template_options['@%s@' % name] = value
195
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]
199
200        template = open(filename).read()
201        for key, value in self.template_options.items():
202            template = template.replace(key, value)
203
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
210
211    def get_outputs(self):
212        return install_data.get_outputs(self) + self.local_outputs
213
214    def get_inputs(self):
215        return install_data.get_inputs(self) + self.local_inputs
216
217class 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)
238
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
247
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
256
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
265
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
270
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)
275
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
283
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
296
297    def generate(self):
298        pass
299
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.
306
307template_classes_enabled=True
308codegen_error_message="""
309***************************************************************************
310Codegen could not be found on your system and is required by the
311dsextras.Template and dsextras.TemplateExtension classes. codegen is part
312of PyGTK. To use either Template or TemplateExtension, you should also
313install PyGTK.
314***************************************************************************
315"""
316try:
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
322except ImportError, e:
323    template_classes_enabled=False
324
325class 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)
331
332    def __init__(self, override, output, defs, prefix,
333                 register=[], load_types=None, py_ssize_t_clean=False):
334       
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
340
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
347
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)
355
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)
361
362        return not distutils.dep_util.newer_group(files,self.output)
363
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)
370
371
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
378
379        for item in self.register:
380            dp = DefsParser(item,dict(GLOBAL_MACROS))
381            dp.startParsing()
382            register_types(dp)
383
384        if self.load_types:
385            globals = {}
386            execfile(self.load_types, globals)
387
388        dp = DefsParser(self.defs,dict(GLOBAL_MACROS))
389        dp.startParsing()
390        register_types(dp)
391
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()
397
398class 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)
404   
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))
419
420        del kwargs['register'], kwargs['override'], kwargs['defs']
421        if load_types:
422            del kwargs['load_types']
423
424        if kwargs.has_key('output'):
425            kwargs['name'] = kwargs['output']
426            del kwargs['output']
427
428        PkgConfigExtension.__init__(self, **kwargs)
429
430    def generate(self):
431        map(lambda x: x.generate(), self.templates)
432
433
Note: See TracBrowser for help on using the repository browser.