python.py (23264B)
1 #!/usr/bin/env python 2 # encoding: utf-8 3 # Thomas Nagy, 2007-2015 (ita) 4 # Gustavo Carneiro (gjc), 2007 5 6 """ 7 Support for Python, detect the headers and libraries and provide 8 *use* variables to link C/C++ programs against them:: 9 10 def options(opt): 11 opt.load('compiler_c python') 12 def configure(conf): 13 conf.load('compiler_c python') 14 conf.check_python_version((2,4,2)) 15 conf.check_python_headers() 16 def build(bld): 17 bld.program(features='pyembed', source='a.c', target='myprog') 18 bld.shlib(features='pyext', source='b.c', target='mylib') 19 """ 20 21 import os, sys 22 from waflib import Errors, Logs, Node, Options, Task, Utils 23 from waflib.TaskGen import extension, before_method, after_method, feature 24 from waflib.Configure import conf 25 26 FRAG = ''' 27 #include <Python.h> 28 #ifdef __cplusplus 29 extern "C" { 30 #endif 31 void Py_Initialize(void); 32 void Py_Finalize(void); 33 #ifdef __cplusplus 34 } 35 #endif 36 int main(int argc, char **argv) 37 { 38 (void)argc; (void)argv; 39 Py_Initialize(); 40 Py_Finalize(); 41 return 0; 42 } 43 ''' 44 """ 45 Piece of C/C++ code used in :py:func:`waflib.Tools.python.check_python_headers` 46 """ 47 48 INST = ''' 49 import sys, py_compile 50 py_compile.compile(sys.argv[1], sys.argv[2], sys.argv[3], True) 51 ''' 52 """ 53 Piece of Python code used in :py:class:`waflib.Tools.python.pyo` and :py:class:`waflib.Tools.python.pyc` for byte-compiling python files 54 """ 55 56 DISTUTILS_IMP = """ 57 try: 58 from distutils.sysconfig import get_config_var, get_python_lib 59 except ImportError: 60 from sysconfig import get_config_var, get_path 61 def get_python_lib(*k, **kw): 62 keyword='platlib' if kw.get('plat_specific') else 'purelib' 63 if 'prefix' in kw: 64 return get_path(keyword, vars={'installed_base': kw['prefix'], 'platbase': kw['prefix']}) 65 return get_path(keyword) 66 """.splitlines() 67 68 @before_method('process_source') 69 @feature('py') 70 def feature_py(self): 71 """ 72 Create tasks to byte-compile .py files and install them, if requested 73 """ 74 self.install_path = getattr(self, 'install_path', '${PYTHONDIR}') 75 install_from = getattr(self, 'install_from', None) 76 if install_from and not isinstance(install_from, Node.Node): 77 install_from = self.path.find_dir(install_from) 78 self.install_from = install_from 79 80 ver = self.env.PYTHON_VERSION 81 if not ver: 82 self.bld.fatal('Installing python files requires PYTHON_VERSION, try conf.check_python_version') 83 84 if int(ver.replace('.', '')) > 31: 85 self.install_32 = True 86 87 @extension('.py') 88 def process_py(self, node): 89 """ 90 Add signature of .py file, so it will be byte-compiled when necessary 91 """ 92 assert(hasattr(self, 'install_path')), 'add features="py" for target "%s" in "%s/wscript".' % (self.target, self.path.nice_path()) 93 self.install_from = getattr(self, 'install_from', None) 94 relative_trick = getattr(self, 'relative_trick', True) 95 if self.install_from: 96 assert isinstance(self.install_from, Node.Node), \ 97 'add features="py" for target "%s" in "%s/wscript" (%s).' % (self.target, self.path.nice_path(), type(self.install_from)) 98 99 # where to install the python file 100 if self.install_path: 101 if self.install_from: 102 self.add_install_files(install_to=self.install_path, install_from=node, cwd=self.install_from, relative_trick=relative_trick) 103 else: 104 self.add_install_files(install_to=self.install_path, install_from=node, relative_trick=relative_trick) 105 106 lst = [] 107 if self.env.PYC: 108 lst.append('pyc') 109 if self.env.PYO: 110 lst.append('pyo') 111 112 if self.install_path: 113 if self.install_from: 114 target_dir = node.path_from(self.install_from) if relative_trick else node.name 115 pyd = Utils.subst_vars("%s/%s" % (self.install_path, target_dir), self.env) 116 else: 117 target_dir = node.path_from(self.path) if relative_trick else node.name 118 pyd = Utils.subst_vars("%s/%s" % (self.install_path, target_dir), self.env) 119 else: 120 pyd = node.abspath() 121 122 for ext in lst: 123 if self.env.PYTAG and not self.env.NOPYCACHE: 124 # __pycache__ installation for python 3.2 - PEP 3147 125 name = node.name[:-3] 126 pyobj = node.parent.get_bld().make_node('__pycache__').make_node("%s.%s.%s" % (name, self.env.PYTAG, ext)) 127 pyobj.parent.mkdir() 128 else: 129 pyobj = node.change_ext(".%s" % ext) 130 131 tsk = self.create_task(ext, node, pyobj) 132 tsk.pyd = pyd 133 134 if self.install_path: 135 self.add_install_files(install_to=os.path.dirname(pyd), install_from=pyobj, cwd=node.parent.get_bld(), relative_trick=relative_trick) 136 137 class pyc(Task.Task): 138 """ 139 Byte-compiling python files 140 """ 141 color = 'PINK' 142 def __str__(self): 143 node = self.outputs[0] 144 return node.path_from(node.ctx.launch_node()) 145 def run(self): 146 cmd = [Utils.subst_vars('${PYTHON}', self.env), '-c', INST, self.inputs[0].abspath(), self.outputs[0].abspath(), self.pyd] 147 ret = self.generator.bld.exec_command(cmd) 148 return ret 149 150 class pyo(Task.Task): 151 """ 152 Byte-compiling python files 153 """ 154 color = 'PINK' 155 def __str__(self): 156 node = self.outputs[0] 157 return node.path_from(node.ctx.launch_node()) 158 def run(self): 159 cmd = [Utils.subst_vars('${PYTHON}', self.env), Utils.subst_vars('${PYFLAGS_OPT}', self.env), '-c', INST, self.inputs[0].abspath(), self.outputs[0].abspath(), self.pyd] 160 ret = self.generator.bld.exec_command(cmd) 161 return ret 162 163 @feature('pyext') 164 @before_method('propagate_uselib_vars', 'apply_link') 165 @after_method('apply_bundle') 166 def init_pyext(self): 167 """ 168 Change the values of *cshlib_PATTERN* and *cxxshlib_PATTERN* to remove the 169 *lib* prefix from library names. 170 """ 171 self.uselib = self.to_list(getattr(self, 'uselib', [])) 172 if not 'PYEXT' in self.uselib: 173 self.uselib.append('PYEXT') 174 # override shlib_PATTERN set by the osx module 175 self.env.cshlib_PATTERN = self.env.cxxshlib_PATTERN = self.env.macbundle_PATTERN = self.env.pyext_PATTERN 176 self.env.fcshlib_PATTERN = self.env.dshlib_PATTERN = self.env.pyext_PATTERN 177 178 try: 179 if not self.install_path: 180 return 181 except AttributeError: 182 self.install_path = '${PYTHONARCHDIR}' 183 184 @feature('pyext') 185 @before_method('apply_link', 'apply_bundle') 186 def set_bundle(self): 187 """Mac-specific pyext extension that enables bundles from c_osx.py""" 188 if Utils.unversioned_sys_platform() == 'darwin': 189 self.mac_bundle = True 190 191 @before_method('propagate_uselib_vars') 192 @feature('pyembed') 193 def init_pyembed(self): 194 """ 195 Add the PYEMBED variable. 196 """ 197 self.uselib = self.to_list(getattr(self, 'uselib', [])) 198 if not 'PYEMBED' in self.uselib: 199 self.uselib.append('PYEMBED') 200 201 @conf 202 def get_python_variables(self, variables, imports=None): 203 """ 204 Spawn a new python process to dump configuration variables 205 206 :param variables: variables to print 207 :type variables: list of string 208 :param imports: one import by element 209 :type imports: list of string 210 :return: the variable values 211 :rtype: list of string 212 """ 213 if not imports: 214 try: 215 imports = self.python_imports 216 except AttributeError: 217 imports = DISTUTILS_IMP 218 219 program = list(imports) # copy 220 program.append('') 221 for v in variables: 222 program.append("print(repr(%s))" % v) 223 os_env = dict(os.environ) 224 try: 225 del os_env['MACOSX_DEPLOYMENT_TARGET'] # see comments in the OSX tool 226 except KeyError: 227 pass 228 229 try: 230 out = self.cmd_and_log(self.env.PYTHON + ['-c', '\n'.join(program)], env=os_env) 231 except Errors.WafError: 232 self.fatal('Could not run %r' % self.env.PYTHON) 233 self.to_log(out) 234 return_values = [] 235 for s in out.splitlines(): 236 s = s.strip() 237 if not s: 238 continue 239 if s == 'None': 240 return_values.append(None) 241 elif (s[0] == "'" and s[-1] == "'") or (s[0] == '"' and s[-1] == '"'): 242 return_values.append(eval(s)) 243 elif s[0].isdigit(): 244 return_values.append(int(s)) 245 else: break 246 return return_values 247 248 @conf 249 def test_pyembed(self, mode, msg='Testing pyembed configuration'): 250 self.check(header_name='Python.h', define_name='HAVE_PYEMBED', msg=msg, 251 fragment=FRAG, errmsg='Could not build a python embedded interpreter', 252 features='%s %sprogram pyembed' % (mode, mode)) 253 254 @conf 255 def test_pyext(self, mode, msg='Testing pyext configuration'): 256 self.check(header_name='Python.h', define_name='HAVE_PYEXT', msg=msg, 257 fragment=FRAG, errmsg='Could not build python extensions', 258 features='%s %sshlib pyext' % (mode, mode)) 259 260 @conf 261 def python_cross_compile(self, features='pyembed pyext'): 262 """ 263 For cross-compilation purposes, it is possible to bypass the normal detection and set the flags that you want: 264 PYTHON_VERSION='3.4' PYTAG='cpython34' pyext_PATTERN="%s.so" PYTHON_LDFLAGS='-lpthread -ldl' waf configure 265 266 The following variables are used: 267 PYTHON_VERSION required 268 PYTAG required 269 PYTHON_LDFLAGS required 270 pyext_PATTERN required 271 PYTHON_PYEXT_LDFLAGS 272 PYTHON_PYEMBED_LDFLAGS 273 """ 274 features = Utils.to_list(features) 275 if not ('PYTHON_LDFLAGS' in self.environ or 'PYTHON_PYEXT_LDFLAGS' in self.environ or 'PYTHON_PYEMBED_LDFLAGS' in self.environ): 276 return False 277 278 for x in 'PYTHON_VERSION PYTAG pyext_PATTERN'.split(): 279 if not x in self.environ: 280 self.fatal('Please set %s in the os environment' % x) 281 else: 282 self.env[x] = self.environ[x] 283 284 xx = self.env.CXX_NAME and 'cxx' or 'c' 285 if 'pyext' in features: 286 flags = self.environ.get('PYTHON_PYEXT_LDFLAGS', self.environ.get('PYTHON_LDFLAGS')) 287 if flags is None: 288 self.fatal('No flags provided through PYTHON_PYEXT_LDFLAGS as required') 289 else: 290 self.parse_flags(flags, 'PYEXT') 291 self.test_pyext(xx) 292 if 'pyembed' in features: 293 flags = self.environ.get('PYTHON_PYEMBED_LDFLAGS', self.environ.get('PYTHON_LDFLAGS')) 294 if flags is None: 295 self.fatal('No flags provided through PYTHON_PYEMBED_LDFLAGS as required') 296 else: 297 self.parse_flags(flags, 'PYEMBED') 298 self.test_pyembed(xx) 299 return True 300 301 @conf 302 def check_python_headers(conf, features='pyembed pyext'): 303 """ 304 Check for headers and libraries necessary to extend or embed python. 305 It may use the module *distutils* or sysconfig in newer Python versions. 306 On success the environment variables xxx_PYEXT and xxx_PYEMBED are added: 307 308 * PYEXT: for compiling python extensions 309 * PYEMBED: for embedding a python interpreter 310 """ 311 features = Utils.to_list(features) 312 assert ('pyembed' in features) or ('pyext' in features), "check_python_headers features must include 'pyembed' and/or 'pyext'" 313 env = conf.env 314 if not env.CC_NAME and not env.CXX_NAME: 315 conf.fatal('load a compiler first (gcc, g++, ..)') 316 317 # bypass all the code below for cross-compilation 318 if conf.python_cross_compile(features): 319 return 320 321 if not env.PYTHON_VERSION: 322 conf.check_python_version() 323 324 pybin = env.PYTHON 325 if not pybin: 326 conf.fatal('Could not find the python executable') 327 328 # so we actually do all this for compatibility reasons and for obtaining pyext_PATTERN below 329 v = 'prefix SO EXT_SUFFIX LDFLAGS LIBDIR LIBPL INCLUDEPY Py_ENABLE_SHARED MACOSX_DEPLOYMENT_TARGET LDSHARED CFLAGS LDVERSION'.split() 330 try: 331 lst = conf.get_python_variables(["get_config_var('%s') or ''" % x for x in v]) 332 except RuntimeError: 333 conf.fatal("Python development headers not found (-v for details).") 334 335 vals = ['%s = %r' % (x, y) for (x, y) in zip(v, lst)] 336 conf.to_log("Configuration returned from %r:\n%s\n" % (pybin, '\n'.join(vals))) 337 338 dct = dict(zip(v, lst)) 339 x = 'MACOSX_DEPLOYMENT_TARGET' 340 if dct[x]: 341 env[x] = conf.environ[x] = str(dct[x]) 342 env.pyext_PATTERN = '%s' + (dct['EXT_SUFFIX'] or dct['SO']) # SO is deprecated in 3.5 and removed in 3.11 343 344 345 # Try to get pythonX.Y-config 346 num = '.'.join(env.PYTHON_VERSION.split('.')[:2]) 347 conf.find_program([''.join(pybin) + '-config', 'python%s-config' % num, 'python-config-%s' % num, 'python%sm-config' % num], var='PYTHON_CONFIG', msg="python-config", mandatory=False) 348 349 if env.PYTHON_CONFIG: 350 # check python-config output only once 351 if conf.env.HAVE_PYTHON_H: 352 return 353 354 # python2.6-config requires 3 runs 355 all_flags = [['--cflags', '--libs', '--ldflags']] 356 if sys.hexversion < 0x2070000: 357 all_flags = [[k] for k in all_flags[0]] 358 359 xx = env.CXX_NAME and 'cxx' or 'c' 360 361 if 'pyembed' in features: 362 for flags in all_flags: 363 # Python 3.8 has different flags for pyembed, needs --embed 364 embedflags = flags + ['--embed'] 365 try: 366 conf.check_cfg(msg='Asking python-config for pyembed %r flags' % ' '.join(embedflags), path=env.PYTHON_CONFIG, package='', uselib_store='PYEMBED', args=embedflags) 367 except conf.errors.ConfigurationError: 368 # However Python < 3.8 doesn't accept --embed, so we need a fallback 369 conf.check_cfg(msg='Asking python-config for pyembed %r flags' % ' '.join(flags), path=env.PYTHON_CONFIG, package='', uselib_store='PYEMBED', args=flags) 370 371 try: 372 conf.test_pyembed(xx) 373 except conf.errors.ConfigurationError: 374 # python bug 7352 375 if dct['Py_ENABLE_SHARED'] and dct['LIBDIR']: 376 env.append_unique('LIBPATH_PYEMBED', [dct['LIBDIR']]) 377 conf.test_pyembed(xx) 378 else: 379 raise 380 381 if 'pyext' in features: 382 for flags in all_flags: 383 conf.check_cfg(msg='Asking python-config for pyext %r flags' % ' '.join(flags), path=env.PYTHON_CONFIG, package='', uselib_store='PYEXT', args=flags) 384 385 try: 386 conf.test_pyext(xx) 387 except conf.errors.ConfigurationError: 388 # python bug 7352 389 if dct['Py_ENABLE_SHARED'] and dct['LIBDIR']: 390 env.append_unique('LIBPATH_PYEXT', [dct['LIBDIR']]) 391 conf.test_pyext(xx) 392 else: 393 raise 394 395 conf.define('HAVE_PYTHON_H', 1) 396 return 397 398 # No python-config, do something else on windows systems 399 all_flags = dct['LDFLAGS'] + ' ' + dct['CFLAGS'] 400 conf.parse_flags(all_flags, 'PYEMBED') 401 402 all_flags = dct['LDFLAGS'] + ' ' + dct['LDSHARED'] + ' ' + dct['CFLAGS'] 403 conf.parse_flags(all_flags, 'PYEXT') 404 405 result = None 406 if not dct["LDVERSION"]: 407 dct["LDVERSION"] = env.PYTHON_VERSION 408 409 # further simplification will be complicated 410 for name in ('python' + dct['LDVERSION'], 'python' + env.PYTHON_VERSION + 'm', 'python' + env.PYTHON_VERSION.replace('.', '')): 411 412 # LIBPATH_PYEMBED is already set; see if it works. 413 if not result and env.LIBPATH_PYEMBED: 414 path = env.LIBPATH_PYEMBED 415 conf.to_log("\n\n# Trying default LIBPATH_PYEMBED: %r\n" % path) 416 result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in LIBPATH_PYEMBED' % name) 417 418 if not result and dct['LIBDIR']: 419 path = [dct['LIBDIR']] 420 conf.to_log("\n\n# try again with -L$python_LIBDIR: %r\n" % path) 421 result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in LIBDIR' % name) 422 423 if not result and dct['LIBPL']: 424 path = [dct['LIBPL']] 425 conf.to_log("\n\n# try again with -L$python_LIBPL (some systems don't install the python library in $prefix/lib)\n") 426 result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in python_LIBPL' % name) 427 428 if not result: 429 path = [os.path.join(dct['prefix'], "libs")] 430 conf.to_log("\n\n# try again with -L$prefix/libs, and pythonXY rather than pythonX.Y (win32)\n") 431 result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in $prefix/libs' % name) 432 433 if not result: 434 path = [os.path.normpath(os.path.join(dct['INCLUDEPY'], '..', 'libs'))] 435 conf.to_log("\n\n# try again with -L$INCLUDEPY/../libs, and pythonXY rather than pythonX.Y (win32)\n") 436 result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in $INCLUDEPY/../libs' % name) 437 438 if result: 439 break # do not forget to set LIBPATH_PYEMBED 440 441 if result: 442 env.LIBPATH_PYEMBED = path 443 env.append_value('LIB_PYEMBED', [name]) 444 else: 445 conf.to_log("\n\n### LIB NOT FOUND\n") 446 447 # under certain conditions, python extensions must link to 448 # python libraries, not just python embedding programs. 449 if Utils.is_win32 or dct['Py_ENABLE_SHARED']: 450 env.LIBPATH_PYEXT = env.LIBPATH_PYEMBED 451 env.LIB_PYEXT = env.LIB_PYEMBED 452 453 conf.to_log("Found an include path for Python extensions: %r\n" % (dct['INCLUDEPY'],)) 454 env.INCLUDES_PYEXT = [dct['INCLUDEPY']] 455 env.INCLUDES_PYEMBED = [dct['INCLUDEPY']] 456 457 # Code using the Python API needs to be compiled with -fno-strict-aliasing 458 if env.CC_NAME == 'gcc': 459 env.append_unique('CFLAGS_PYEMBED', ['-fno-strict-aliasing']) 460 env.append_unique('CFLAGS_PYEXT', ['-fno-strict-aliasing']) 461 if env.CXX_NAME == 'gcc': 462 env.append_unique('CXXFLAGS_PYEMBED', ['-fno-strict-aliasing']) 463 env.append_unique('CXXFLAGS_PYEXT', ['-fno-strict-aliasing']) 464 465 if env.CC_NAME == "msvc": 466 try: 467 from distutils.msvccompiler import MSVCCompiler 468 except ImportError: 469 # From https://github.com/python/cpython/blob/main/Lib/distutils/msvccompiler.py 470 env.append_value('CFLAGS_PYEXT', [ '/nologo', '/Ox', '/MD', '/W3', '/GX', '/DNDEBUG']) 471 env.append_value('CXXFLAGS_PYEXT', [ '/nologo', '/Ox', '/MD', '/W3', '/GX', '/DNDEBUG']) 472 env.append_value('LINKFLAGS_PYEXT', ['/DLL', '/nologo', '/INCREMENTAL:NO']) 473 else: 474 dist_compiler = MSVCCompiler() 475 dist_compiler.initialize() 476 env.append_value('CFLAGS_PYEXT', dist_compiler.compile_options) 477 env.append_value('CXXFLAGS_PYEXT', dist_compiler.compile_options) 478 env.append_value('LINKFLAGS_PYEXT', dist_compiler.ldflags_shared) 479 480 conf.check(header_name='Python.h', define_name='HAVE_PYTHON_H', uselib='PYEMBED', fragment=FRAG, errmsg='Could not build a Python embedded interpreter') 481 482 @conf 483 def check_python_version(conf, minver=None): 484 """ 485 Check if the python interpreter is found matching a given minimum version. 486 minver should be a tuple, eg. to check for python >= 2.4.2 pass (2,4,2) as minver. 487 488 If successful, PYTHON_VERSION is defined as 'MAJOR.MINOR' (eg. '2.4') 489 of the actual python version found, and PYTHONDIR and PYTHONARCHDIR 490 are defined, pointing to the site-packages directories appropriate for 491 this python version, where modules/packages/extensions should be 492 installed. 493 494 :param minver: minimum version 495 :type minver: tuple of int 496 """ 497 assert minver is None or isinstance(minver, tuple) 498 pybin = conf.env.PYTHON 499 if not pybin: 500 conf.fatal('could not find the python executable') 501 502 # Get python version string 503 cmd = pybin + ['-c', 'import sys\nfor x in sys.version_info: print(str(x))'] 504 Logs.debug('python: Running python command %r', cmd) 505 lines = conf.cmd_and_log(cmd).split() 506 assert len(lines) == 5, "found %r lines, expected 5: %r" % (len(lines), lines) 507 pyver_tuple = (int(lines[0]), int(lines[1]), int(lines[2]), lines[3], int(lines[4])) 508 509 # Compare python version with the minimum required 510 result = (minver is None) or (pyver_tuple >= minver) 511 512 if result: 513 # define useful environment variables 514 pyver = '.'.join([str(x) for x in pyver_tuple[:2]]) 515 conf.env.PYTHON_VERSION = pyver 516 517 if 'PYTHONDIR' in conf.env: 518 # Check if --pythondir was specified 519 pydir = conf.env.PYTHONDIR 520 elif 'PYTHONDIR' in conf.environ: 521 # Check environment for PYTHONDIR 522 pydir = conf.environ['PYTHONDIR'] 523 else: 524 # Finally, try to guess 525 if Utils.is_win32: 526 (pydir,) = conf.get_python_variables(["get_python_lib(standard_lib=0) or ''"]) 527 else: 528 (pydir,) = conf.get_python_variables(["get_python_lib(standard_lib=0, prefix=%r) or ''" % conf.env.PREFIX]) 529 530 if 'PYTHONARCHDIR' in conf.env: 531 # Check if --pythonarchdir was specified 532 pyarchdir = conf.env.PYTHONARCHDIR 533 elif 'PYTHONARCHDIR' in conf.environ: 534 # Check environment for PYTHONDIR 535 pyarchdir = conf.environ['PYTHONARCHDIR'] 536 else: 537 # Finally, try to guess 538 (pyarchdir, ) = conf.get_python_variables(["get_python_lib(plat_specific=1, standard_lib=0, prefix=%r) or ''" % conf.env.PREFIX]) 539 if not pyarchdir: 540 pyarchdir = pydir 541 542 if hasattr(conf, 'define'): # conf.define is added by the C tool, so may not exist 543 conf.define('PYTHONDIR', pydir) 544 conf.define('PYTHONARCHDIR', pyarchdir) 545 546 conf.env.PYTHONDIR = pydir 547 conf.env.PYTHONARCHDIR = pyarchdir 548 549 # Feedback 550 pyver_full = '.'.join(map(str, pyver_tuple[:3])) 551 if minver is None: 552 conf.msg('Checking for python version', pyver_full) 553 else: 554 minver_str = '.'.join(map(str, minver)) 555 conf.msg('Checking for python version >= %s' % (minver_str,), pyver_full, color=result and 'GREEN' or 'YELLOW') 556 557 if not result: 558 conf.fatal('The python version is too old, expecting %r' % (minver,)) 559 560 PYTHON_MODULE_TEMPLATE = ''' 561 import %s as current_module 562 version = getattr(current_module, '__version__', None) 563 if version is not None: 564 print(str(version)) 565 else: 566 print('unknown version') 567 ''' 568 569 @conf 570 def check_python_module(conf, module_name, condition=''): 571 """ 572 Check if the selected python interpreter can import the given python module:: 573 574 def configure(conf): 575 conf.check_python_module('pygccxml') 576 conf.check_python_module('re', condition="ver > num(2, 0, 4) and ver <= num(3, 0, 0)") 577 578 :param module_name: module 579 :type module_name: string 580 """ 581 msg = "Checking for python module %r" % module_name 582 if condition: 583 msg = '%s (%s)' % (msg, condition) 584 conf.start_msg(msg) 585 try: 586 ret = conf.cmd_and_log(conf.env.PYTHON + ['-c', PYTHON_MODULE_TEMPLATE % module_name]) 587 except Errors.WafError: 588 conf.end_msg(False) 589 conf.fatal('Could not find the python module %r' % module_name) 590 591 ret = ret.strip() 592 if condition: 593 conf.end_msg(ret) 594 if ret == 'unknown version': 595 conf.fatal('Could not check the %s version' % module_name) 596 597 def num(*k): 598 if isinstance(k[0], int): 599 return Utils.loose_version('.'.join([str(x) for x in k])) 600 else: 601 return Utils.loose_version(k[0]) 602 d = {'num': num, 'ver': Utils.loose_version(ret)} 603 ev = eval(condition, {}, d) 604 if not ev: 605 conf.fatal('The %s version does not satisfy the requirements' % module_name) 606 else: 607 if ret == 'unknown version': 608 conf.end_msg(True) 609 else: 610 conf.end_msg(ret) 611 612 def configure(conf): 613 """ 614 Detect the python interpreter 615 """ 616 v = conf.env 617 if getattr(Options.options, 'pythondir', None): 618 v.PYTHONDIR = Options.options.pythondir 619 if getattr(Options.options, 'pythonarchdir', None): 620 v.PYTHONARCHDIR = Options.options.pythonarchdir 621 if getattr(Options.options, 'nopycache', None): 622 v.NOPYCACHE=Options.options.nopycache 623 624 if not v.PYTHON: 625 v.PYTHON = [getattr(Options.options, 'python', None) or sys.executable] 626 v.PYTHON = Utils.to_list(v.PYTHON) 627 conf.find_program('python', var='PYTHON') 628 629 v.PYFLAGS = '' 630 v.PYFLAGS_OPT = '-O' 631 632 v.PYC = getattr(Options.options, 'pyc', 1) 633 v.PYO = getattr(Options.options, 'pyo', 1) 634 635 try: 636 v.PYTAG = conf.cmd_and_log(conf.env.PYTHON + ['-c', "import sys\ntry:\n print(sys.implementation.cache_tag)\nexcept AttributeError:\n import imp\n print(imp.get_tag())\n"]).strip() 637 except Errors.WafError: 638 pass 639 640 def options(opt): 641 """ 642 Add python-specific options 643 """ 644 pyopt=opt.add_option_group("Python Options") 645 pyopt.add_option('--nopyc', dest = 'pyc', action='store_false', default=1, 646 help = 'Do not install bytecode compiled .pyc files (configuration) [Default:install]') 647 pyopt.add_option('--nopyo', dest='pyo', action='store_false', default=1, 648 help='Do not install optimised compiled .pyo files (configuration) [Default:install]') 649 pyopt.add_option('--nopycache',dest='nopycache', action='store_true', 650 help='Do not use __pycache__ directory to install objects [Default:auto]') 651 pyopt.add_option('--python', dest="python", 652 help='python binary to be used [Default: %s]' % sys.executable) 653 pyopt.add_option('--pythondir', dest='pythondir', 654 help='Installation path for python modules (py, platform-independent .py and .pyc files)') 655 pyopt.add_option('--pythonarchdir', dest='pythonarchdir', 656 help='Installation path for python extension (pyext, platform-dependent .so or .dylib files)') 657