python.py 22 KB

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