1# -*- coding: utf-8 -*- 2# 3# dsextras.py - Extra classes and utilities for distutils, 4# adding pkg-config support 5 6 7import os 8import sys 9import fnmatch 10import re 11import string 12 13from distutils import dep_util 14from distutils.command.build_ext import build_ext 15from distutils.command.install_lib import install_lib 16from distutils.command.install_data import install_data 17from distutils.extension import Extension 18from distutils.spawn import find_executable 19 20try: 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 25except ImportError: 26 template_classes_enabled = False 27else: 28 template_classes_enabled = True 29 30 31GLOBAL_INC = [] 32GLOBAL_MACROS = [] 33 34codegen_error_message=''' 35*************************************************************************** 36Codegen could not be found on your system and is required by the 37dsextras.Template and dsextras.TemplateExtension classes. 38*************************************************************************** 39''' 40 41 42def 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*(.+)\)') 45 46 if os.path.exists('configure.ac'): 47 fname = 'configure.ac' 48 elif os.path.exists('configure.in'): 49 fname = 'configure.in' 50 else: 51 raise SystemExit('ERROR: Could not find configure file') 52 53 for line in open(fname).readlines(): 54 match_obj = pattern.match(line) 55 56 if match_obj: 57 return match_obj.group(1) 58 59 return None 60 61def getoutput(cmd): 62 '''Return output (stdout or stderr) of executing cmd in a shell.''' 63 return getstatusoutput(cmd)[1] 64 65def getstatusoutput(cmd): 66 '''Return (status, output) of executing cmd in a shell.''' 67 if sys.platform == 'win32': 68 pipe = os.popen(cmd, 'r') 69 text = pipe.read() 70 sts = pipe.close() or 0 71 72 while text[-1:] in ['\n', '\r']: 73 text = text[:-1] 74 75 return sts, text 76 else: 77 from commands import getstatusoutput 78 return getstatusoutput(cmd) 79 80def have_gcc(): 81 '''Checks for the existence of gcc''' 82 if find_executable('gcc'): 83 return True 84 85def have_pkgconfig(): 86 '''Checks for the existence of pkg-config''' 87 if find_executable('pkg-config'): 88 return True 89 90def list_files(dir): 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) 97 pattern = parts[-1] 98 else: 99 pattern = dir 100 dir = '.' 101 102 dir = os.path.abspath(dir) 103 retval = [] 104 105 for file in os.listdir(dir): 106 if fnmatch.fnmatch(file, pattern): 107 retval.append(os.path.join(dir, file)) 108 109 return retval 110 111def 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) 115 116 if not is_installed: 117 return False 118 119 orig_version = pkgc_get_version(name) 120 version = map(int, orig_version.split('.')) 121 pkc_version = map(int, req_version.split('.')) 122 123 if version >= pkc_version: 124 return True 125 126 return False 127 128def pkgc_get_version(name): 129 '''return the version as return by pkg-config --modversion''' 130 return getoutput('pkg-config --modversion %s' % name) 131 132def 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() 136 137def 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() 141 142def 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() 146 147def 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) 150 return output 151 152 153class BuildExt(build_ext): 154 def init_extra_compile_args(self): 155 self.extra_compile_args = [] 156 157 if sys.platform == 'win32' and self.compiler.compiler_type == 'mingw32': 158 if not have_gcc(): 159 raise SystemExit('ERROR: Could not find gcc.') 160 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') 169 170 print ('using MinGW GCC version %s with %s option' % \ 171 (gcc_version, msnative_struct[gcc_version[0]])) 172 173 self.extra_compile_args.append(msnative_struct[gcc_version[0]]) 174 175 def modify_compiler(self): 176 if sys.platform == 'win32' and self.compiler.compiler_type == 'mingw32': 177 if not have_gcc(): 178 raise SystemExit('ERROR: Could not find gcc.') 179 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') 184 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) 192 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 196 197 # Generate eventual templates before building 198 if hasattr(ext, 'generate'): 199 ext.generate() 200 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']] 206 else: 207 save_libs = ext.libraries 208 209 # Invoke base build_extension() 210 build_ext.build_extension(self, ext) 211 212 if save_libs is not None and save_libs != ext.libraries: 213 ext.libraries = save_libs 214 215 216class InstallLib(install_lib): 217 local_outputs = [] 218 local_inputs = [] 219 220 def set_install_dir(self, install_dir): 221 self.install_dir = install_dir 222 223 def get_outputs(self): 224 return install_lib.get_outputs(self) + self.local_outputs 225 226 def get_inputs(self): 227 return install_lib.get_inputs(self) + self.local_inputs 228 229 230class InstallData(install_data): 231 local_outputs = [] 232 local_inputs = [] 233 template_options = {} 234 235 def prepare(self): 236 if os.name == 'nt': 237 self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-3]) 238 else: 239 # default: os.name == 'posix' 240 self.prefix = os.sep.join(self.install_dir.split(os.sep)[:-4]) 241 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' 247 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', '') 256 257 def set_install_dir(self, install_dir): 258 self.install_dir = install_dir 259 260 def add_template_option(self, name, value): 261 self.template_options['@%s@' % name] = value 262 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] 266 267 template = open(filename).read() 268 269 for key, value in self.template_options.items(): 270 template = template.replace(key, value) 271 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) 277 return output 278 279 def get_outputs(self): 280 return install_data.get_outputs(self) + self.local_outputs 281 282 def get_inputs(self): 283 return install_data.get_inputs(self) + self.local_inputs 284 285 286class PkgConfigExtension(Extension): 287 # Name of pygobject package extension depends on, can be None 288 pygobject_pkc = 'pygobject-2.0' 289 can_build_ok = None 290 291 def __init__(self, **kwargs): 292 name = kwargs['pkc_name'] 293 294 if 'include_dirs' in kwargs: 295 kwargs['include_dirs'] += self.get_include_dirs(name) + GLOBAL_INC 296 else: 297 kwargs['include_dirs'] = self.get_include_dirs(name) + GLOBAL_INC 298 299 kwargs['define_macros'] = GLOBAL_MACROS 300 301 if 'libraries' in kwargs: 302 kwargs['libraries'] += self.get_libraries(name) 303 else: 304 kwargs['libraries'] = self.get_libraries(name) 305 306 if 'library_dirs' in kwargs: 307 kwargs['library_dirs'] += self.get_library_dirs(name) 308 else: 309 kwargs['library_dirs'] = self.get_library_dirs(name) 310 311 if 'pygobject_pkc' in kwargs: 312 self.pygobject_pkc = kwargs.pop('pygobject_pkc') 313 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) 318 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) 324 325 def get_include_dirs(self, names): 326 if type(names) != tuple: 327 names = (names,) 328 329 retval = [] 330 331 for name in names: 332 retval.extend(pkgc_get_include_dirs(name)) 333 334 return retval 335 336 def get_libraries(self, names): 337 if type(names) != tuple: 338 names = (names,) 339 340 retval = [] 341 342 for name in names: 343 retval.extend(pkgc_get_libraries(name)) 344 345 return retval 346 347 def get_library_dirs(self, names): 348 if type(names) != tuple: 349 names = (names,) 350 351 retval = [] 352 353 for name in names: 354 retval.extend(pkgc_get_library_dirs(name)) 355 356 return retval 357 358 def can_build(self): 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 362 363 if type(self.pkc_name) != tuple: 364 reqs = [(self.pkc_name, self.pkc_version)] 365 else: 366 reqs = zip(self.pkc_name, self.pkc_version) 367 368 for package, version in reqs: 369 retval = os.system('pkg-config --exists %s' % package) 370 371 if retval: 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 375 return False 376 377 orig_version = pkgc_get_version(package) 378 379 if (map(int, orig_version.split('.')) >= 380 map(int, version.split('.'))): 381 382 self.can_build_ok = True 383 return True 384 else: 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 388 return False 389 390 def generate(self): 391 pass 392 393 394class 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)) 400 401 return object.__new__(cls) 402 403 def __init__(self, override, output, defs, prefix, 404 register=[], load_types=None, py_ssize_t_clean=False): 405 406 self.override = override 407 self.output = output 408 self.prefix = prefix 409 self.load_types = load_types 410 self.py_ssize_t_clean = py_ssize_t_clean 411 412 self.built_defs=[] 413 414 if isinstance(defs, tuple): 415 self.defs=defs[0] 416 self.built_defs.append(defs) 417 else: 418 self.defs=defs 419 420 self.register=[] 421 422 for r in register: 423 if isinstance(r, tuple): 424 self.register.append(r[0]) 425 self.built_defs.append(r) 426 else: 427 self.register.append(r) 428 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) 434 435 return not dep_util.newer_group(files, self.output) 436 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) 443 444 def generate(self): 445 # Generate defs files if necessary 446 self.generate_defs() 447 448 # ... then check the file timestamps 449 if self.check_dates(): 450 return 451 452 for item in self.register: 453 dp = DefsParser(item, dict(GLOBAL_MACROS)) 454 dp.startParsing() 455 register_types(dp) 456 457 if self.load_types: 458 globals = {} 459 execfile(self.load_types, globals) 460 461 dp = DefsParser(self.defs, dict(GLOBAL_MACROS)) 462 dp.startParsing() 463 register_types(dp) 464 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) 469 fd.close() 470 471 472class 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)) 477 478 return PkgConfigExtension.__new__(cls,*args, **kwds) 479 480 def __init__(self, **kwargs): 481 name = kwargs['name'] 482 defs = kwargs['defs'] 483 484 if isinstance(defs, tuple): 485 output = defs[0][:-5] + '.c' 486 else: 487 output = defs[:-5] + '.c' 488 489 override = kwargs['override'] 490 load_types = kwargs.get('load_types') 491 py_ssize_t_clean = kwargs.pop('py_ssize_t_clean', False) 492 self.templates = [] 493 self.templates.append(Template(override, output, defs, 'py' + name, 494 kwargs['register'], load_types, 495 py_ssize_t_clean)) 496 497 del kwargs['register'], kwargs['override'], kwargs['defs'] 498 499 if load_types: 500 del kwargs['load_types'] 501 502 if kwargs.has_key('output'): 503 kwargs['name'] = kwargs['output'] 504 del kwargs['output'] 505 506 PkgConfigExtension.__init__(self, **kwargs) 507 508 def generate(self): 509 map(lambda x: x.generate(), self.templates) 510