From eaceaf7b86c93c8a2f75212d496538967959edaa Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Fri, 31 Jan 2014 23:15:17 +0000 Subject: [PATCH] [SCons] Use SCons to compile the Cython extension Fixes issues with mismatched compilers between the Cython extension and the cantera library, especially on OS X. Improves build dependency resolution to eliminate unnecessary recompilation of _cantera.cpp. --- SConstruct | 6 -- interfaces/cython/SConscript | 119 ++++++++++++---------------------- interfaces/cython/setup.py.in | 12 +--- 3 files changed, 41 insertions(+), 96 deletions(-) diff --git a/SConstruct b/SConstruct index 4f83fa404..ee682b9a8 100644 --- a/SConstruct +++ b/SConstruct @@ -325,12 +325,6 @@ config_options = [ default 'site-packages' directory. To install to the current user's site-packages directory, use 'python3_prefix=USER'.""", defaults.python_prefix, PathVariable.PathAccept), - PathVariable( - 'python_compiler', - """ Compiler to use while building the Python extension module. By default, - the compiler will be selected by distutils. Applies to both Python 2 - and Python 3.""", - '', PathVariable.PathAccept), EnumVariable( 'matlab_toolbox', """This variable controls whether the Matlab toolbox will be built. If diff --git a/interfaces/cython/SConscript b/interfaces/cython/SConscript index ae2d40ba1..9c122714f 100644 --- a/interfaces/cython/SConscript +++ b/interfaces/cython/SConscript @@ -1,33 +1,34 @@ """ Cython-based Python Module for Python 3 """ from buildutils import * +import Cython.Build Import('env', 'build', 'install') localenv = env.Clone() -def configure_numpy(env, python_command): +def configure_python(env, python_command): script = '\n'.join(("from distutils.sysconfig import *", "import numpy", - "print(get_config_var('SO'))", + "print(get_config_var('EXT_SUFFIX') or get_config_var('SO'))", + "print(get_config_var('INCLUDEPY'))", + "print(get_config_var('prefix'))", "print(get_python_version())", "print(numpy.get_include())")) info = getCommandOutput(python_command, '-c', script) - module_ext, py_version, numpy_include = info.splitlines()[-3:] - - incDirs = (".", "../../include", numpy_include, - localenv['sundials_include'], localenv['boost_inc_dir']) - env['py_include_dirs'] = repr([x for x in incDirs if x]) - + module_ext, inc, prefix, py_version, numpy_include = info.splitlines()[-5:] + env.Append(CPPPATH=[inc, numpy_include, Dir('#include')]) + env.Prepend(LIBS=env['cantera_libs']) + if env['OS'] == 'Darwin': + env.Append(LINKFLAGS='-undefined dynamic_lookup') + elif env['OS'] == 'Windows': + env.Append(LIBPATH=prefix+'/libs') return module_ext, py_version def add_dependencies(mod, ext): localenv.Depends(mod, ext) - localenv.Depends(ext, localenv['cantera_staticlib']) localenv.Depends(mod, dataFiles + testFiles + scripts) - for f in mglob(localenv, 'cantera', 'pyx', 'pxd'): - localenv.Depends(ext, f) for f in (mglob(localenv, 'cantera/test', 'py') + mglob(localenv, 'cantera/mixmaster', 'py') + @@ -42,6 +43,15 @@ def add_dependencies(mod, ext): mglob(localenv, 'cantera/examples/misc', 'py')): localenv.Depends(mod, f) + +def cythonize(target, source, env): + Cython.Build.cythonize([f.abspath for f in source]) + +cythonized = localenv.Command('cantera/_cantera.cpp', ['cantera/_cantera.pyx'], + cythonize) +for f in mglob(localenv, 'cantera', 'pyx', 'pxd'): + localenv.Depends(cythonized, f) + script_ext = '.py' if os.name == 'nt' else '' localenv['py_ctml_writer'] = repr('scripts/ctml_writer%s' % script_ext) localenv['py_ck2cti'] = repr('scripts/ck2cti%s' % script_ext) @@ -96,41 +106,6 @@ def install_module(prefix, python_version): ' --target-version=%s' % python_version) env['python%s_module_loc' % ver] = '' - -libDirs = ('../../build/lib', localenv['sundials_libdir'], - localenv['blas_lapack_dir'], localenv['boost_lib_dir']) -localenv['py_cantera_libs'] = repr(localenv['cantera_libs']) -localenv['py_libdirs'] = repr([x for x in libDirs if x]) - -# Compile the Python module with the same compiler as the rest of Cantera, -# unless otherwise specified -localenv['py_extra_compiler_args'] = repr([]) -localenv['py_extra_link_args'] = repr([]) -if localenv['OS'] == 'Windows': - if env['CC'] == 'cl': - flags = ['/EHsc'] - if env['debug']: - flags.extend(['/Zi', '/Fd_cantera.pdb']) - localenv['py_extra_link_args'] = repr(['/DEBUG']) - compilerOpt = ' --compiler=msvc' - localenv['py_extra_compiler_args'] = repr(flags) - - elif env['CC'] == 'gcc': - compilerOpt = ' --compiler=mingw32' -else: - compilerOpt = '' - if '-fprofile-arcs' in localenv['CCFLAGS']: - localenv['py_extra_compiler_args'] = repr(['-fprofile-arcs', '-ftest-coverage']) - localenv['py_extra_link_args'] = repr(['-fprofile-arcs', '-ftest-coverage']) - if env['python_compiler']: - localenv['ENV']['CC'] = env['python_compiler'] - localenv['ENV']['CXX'] = env['python_compiler'] - -if 'LDFLAGS' not in localenv['ENV']: - localenv['ENV']['LDFLAGS'] = '' -for framework in localenv['FRAMEWORKS']: - localenv['ENV']['LDFLAGS'] += ' -framework ' + framework - dataFiles = localenv.RecursiveInstall('#interfaces/cython/cantera/data', '#build/data') build(dataFiles) @@ -142,29 +117,22 @@ build(testFiles) # Cython module for Python 3.x if localenv['python3_package'] == 'y': py3env = localenv.Clone() - module_ext, py3_version = configure_numpy(py3env, py3env['python3_cmd']) - make_setup = py3env.SubstFile('#interfaces/cython/setup3.py', - '#interfaces/cython/setup.py.in') - build(make_setup) + module_ext, py3_version = configure_python(py3env, py3env['python3_cmd']) - build_base = ('cd interfaces/cython &&' - ' $python3_cmd setup3.py %s' - ' --build-lib=../../build/python3' - ' --build-temp=../../build/temp-py3' - + compilerOpt) + obj = py3env.SharedObject('#build/temp-py/_cantera3', 'cantera/_cantera.cpp') + ext = py3env.LoadableModule('#build/python3/cantera/_cantera%s' % module_ext, + obj, LIBPREFIX='', SHLIBSUFFIX=module_ext, + LIBSUFFIXES=[module_ext]) + py3env['py_extension'] = ext[0].name - build_cmd = build_base % 'build' - ext = build(py3env.Command('#build/python3/cantera/_cantera%s' % module_ext, - 'setup3.py', - build_base % 'build_ext')) - mod = build(py3env.Command('#build/python3/cantera/__init__.py', - 'setup3.py', + py3env.SubstFile('setup3.py', 'setup.py.in') + build_cmd = ('cd interfaces/cython &&' + ' $python3_cmd setup3.py build --build-lib=../../build/python3') + mod = build(py3env.Command('#build/python3/cantera/__init__.py', 'setup3.py', build_cmd)) env['python3_module'] = mod env['python3_extension'] = ext - py3env.AddPreAction(ext, Delete('interfaces/cython/cantera/_cantera.cpp')) - add_dependencies(mod, ext) install_module(py3env['python3_prefix'], py3_version) @@ -172,29 +140,22 @@ if localenv['python3_package'] == 'y': # Cython module for Python 2.x if localenv['python_package'] == 'full': py2env = localenv.Clone() - module_ext, py2_version = configure_numpy(py2env, py2env['python_cmd']) - make_setup = py2env.SubstFile('#interfaces/cython/setup2.py', - '#interfaces/cython/setup.py.in') - build(make_setup) + module_ext, py2_version = configure_python(py2env, py2env['python_cmd']) - build_base = ('cd interfaces/cython &&' - ' $python_cmd setup2.py %s' - ' --build-lib=../../build/python2' - ' --build-temp=../../build/temp-py2' - + compilerOpt) - - build_cmd = build_base % 'build' - ext = build(py2env.Command('#build/python2/cantera/_cantera%s' % module_ext, - 'setup2.py', - build_base % 'build_ext')) + obj = py2env.SharedObject('#build/temp-py/_cantera2', 'cantera/_cantera.cpp') + ext = py2env.LoadableModule('#build/python2/cantera/_cantera%s' % module_ext, + obj, LIBPREFIX='', SHLIBSUFFIX=module_ext, + LIBSUFFIXES=[module_ext]) + py2env['py_extension'] = ext[0].name + py2env.SubstFile('setup2.py', 'setup.py.in') + build_cmd = ('cd interfaces/cython &&' + ' $python_cmd setup2.py build --build-lib=../../build/python2') mod = build(py2env.Command('#build/python2/cantera/__init__.py', 'setup2.py', build_cmd)) env['python2_module'] = mod env['python2_extension'] = ext - py2env.AddPreAction(ext, Delete('interfaces/cython/cantera/_cantera.cpp')) - # Use 3to2 to convert examples from Python 3 syntax if env['python_convert_examples']: def convert_example(target, source, env): diff --git a/interfaces/cython/setup.py.in b/interfaces/cython/setup.py.in index e83b9f6f3..a806f09e7 100644 --- a/interfaces/cython/setup.py.in +++ b/interfaces/cython/setup.py.in @@ -1,6 +1,5 @@ import os from distutils.core import setup -from Cython.Build import cythonize # Monkey patch to prevent bdist_msi from incorrectly overwriting the value of # build-lib specified on the command line. @@ -32,14 +31,6 @@ if os.name == 'nt': bdist_msi.run = bdist_run_new -exts = cythonize("cantera/_cantera.pyx") - -exts[0].include_dirs += @py_include_dirs@ -exts[0].extra_compile_args += @py_extra_compiler_args@ -exts[0].libraries += @py_cantera_libs@ -exts[0].library_dirs += @py_libdirs@ -exts[0].extra_link_args += @py_extra_link_args@ - setup(name="Cantera", version="@cantera_version@", description="The Cantera Python Interface", @@ -58,8 +49,7 @@ setup(name="Cantera", scripts=[@py_ctml_writer@, @py_ck2cti@, @py_mixmaster@], - ext_modules = exts, package_data = {'cantera.data': ['*.*'], 'cantera.test.data': ['*.*'], 'cantera.examples': ['*/*.*'], - 'cantera': ['*.pxd']}) + 'cantera': ["@py_extension@", '*.pxd']})