| 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 | 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]]) |
|---|
| 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 | |
|---|
| 148 | class 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 | |
|---|
| 162 | class 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 | |
|---|
| 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) |
|---|
| 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 | |
|---|
| 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 | *************************************************************************** |
|---|
| 315 | """ |
|---|
| 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 |
|---|
| 324 | |
|---|
| 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) |
|---|
| 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 | |
|---|
| 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) |
|---|
| 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 | |
|---|