Path: blob/develop/src/sage_setup/command/sage_build_cython.py
4081 views
########################################################################1##2## Customize the Extensions processed by Cython3##4########################################################################56import os7import sys8import time9import json1011# Import setuptools before importing distutils, so that setuptools12# can replace distutils by its own vendored copy.13import setuptools1415from distutils import log16from setuptools import Command1718from sage_setup.util import stable_uniq19from sage_setup.find import find_extra_files20from sage_setup.cython_options import compiler_directives, compile_time_env_variables2122# Do not put all, but only the most common libraries and their headers23# (that are likely to change on an upgrade) here:24# [At least at the moment. Make sure the headers aren't copied with "-p",25# or explicitly touch them in the respective spkg's spkg-install.]26lib_headers = dict()2728# Set by build/bin/sage-build-env-config. Empty if the system package is used.29gmp_prefix = os.environ.get("SAGE_GMP_PREFIX", "")30if gmp_prefix:31lib_headers["gmp"] = [os.path.join(gmp_prefix, 'include', 'gmp.h')] # cf. #8664, #989632lib_headers["gmpxx"] = [os.path.join(gmp_prefix, 'include', 'gmpxx.h')]33ntl_prefix = os.environ.get("SAGE_NTL_PREFIX", "")34if ntl_prefix:35lib_headers["ntl"] = [os.path.join(ntl_prefix, 'include', 'NTL', 'config.h')]3637# Manually add -fno-strict-aliasing, which is needed to compile Cython38# and disappears from the default flags if the user has set CFLAGS.39#40# Add -DCYTHON_CLINE_IN_TRACEBACK=1 which causes the .c line number to41# always appear in exception tracebacks (by default, this is a runtime42# setting in Cython which causes some overhead every time an exception43# is raised).44extra_compile_args = ["-fno-strict-aliasing", "-DCYTHON_CLINE_IN_TRACEBACK=1"]45extra_link_args = [ ]4647DEVEL = False48if DEVEL:49extra_compile_args.append('-ggdb')505152class sage_build_cython(Command):53name = 'build_cython'54description = "compile Cython extensions into C/C++ extensions"5556user_options = [57# TODO: Temporarily disabled since the value for this option is58# hard-coded; change as part of work on #2152559#('build-dir=', 'd',60# "directory for compiled C/C++ sources and header files"),61('profile', 'p',62"enable Cython profiling support"),63('parallel=', 'j',64"run cythonize in parallel with N processes"),65('force=', 'f',66"force files to be cythonized even if the are not changed")67]6869boolean_options = ['debug', 'profile', 'force']7071built_distributions = None7273def initialize_options(self):74self.extensions = None75self.build_base = None76self.build_dir = None7778# Always have Cython produce debugging info by default, unless79# SAGE_DEBUG=no explicitly80self.debug = True81self.profile = None82self.parallel = None83self.force = None8485self.cython_directives = None86self.compile_time_env = None8788self.build_lib = None89self.cythonized_files = None9091def finalize_options(self):92self.extensions = self.distribution.ext_modules9394# Let Cython generate its files in the "cythonized"95# subdirectory of the build_base directory.96self.set_undefined_options('build', ('build_base', 'build_base'))97self.build_dir = os.path.join(self.build_base, "cythonized")9899# Inherit some options from the 'build_ext' command if possible100# (this in turn implies inheritance from the 'build' command)101inherit_opts = [('build_lib', 'build_lib'),102('debug', 'debug'),103('force', 'force')]104105# Python 3.5 now has a parallel option as well106inherit_opts.append(('parallel', 'parallel'))107108self.set_undefined_options('build_ext', *inherit_opts)109110# Always produce debugging output unless SAGE_DEBUG=no is given111# explicitly112self.debug = os.environ.get('SAGE_DEBUG', None) != 'no'113114if self.debug:115log.info('Enabling Cython debugging support')116117if self.profile is None:118self.profile = os.environ.get('SAGE_PROFILE') == 'yes'119120if self.profile:121log.info('Enabling Cython profiling support')122123if self.parallel is None:124self.parallel = os.environ.get('SAGE_NUM_THREADS', '0')125126try:127self.parallel = int(self.parallel)128except ValueError:129raise ValueError("parallel should be an integer")130131try:132import Cython133except ImportError:134raise ImportError(135"Cython must be installed and importable in order to run "136"the cythonize command")137138self.cython_directives = compiler_directives(self.profile)139self.compile_time_env = compile_time_env_variables()140141# We check the Cython version and some relevant configuration142# options from the earlier build to see if we need to force a143# recythonization. If the version or options have changed, we144# must recythonize all files.145self._version_file = os.path.join(self.build_dir, '.cython_version')146self._version_stamp = json.dumps({147'version': Cython.__version__,148'debug': self.debug,149'directives': self.cython_directives,150'compile_time_env': self.compile_time_env,151}, sort_keys=True)152153# Read an already written version file if it exists and compare to the154# current version stamp155try:156if open(self._version_file).read() == self._version_stamp:157force = False158else:159# version_file exists but its contents are not what we160# want => recythonize all Cython code.161force = True162# In case this cythonization is interrupted, we end up163# in an inconsistent state with C code generated by164# different Cython versions or with different options.165# To ensure that this inconsistent state will be fixed,166# we remove the version_file now to force a167# recythonization the next time we build Sage.168os.unlink(self._version_file)169except OSError:170# Most likely, the version_file does not exist171# => (re)cythonize all Cython code.172force = True173174# If the --force flag was given at the command line, always force;175# otherwise use what we determined from reading the version file176if self.force is None:177self.force = force178179def get_cythonized_package_files(self):180"""181Return a list of files found in the Sage sources and/or Cythonize182output directory that should be installed with Python packages (a la183``package_files``).184"""185186if self.cythonized_files is not None:187return self.cythonized_files188189self.cythonized_files = list(find_extra_files(190".", ["sage"], self.build_dir, [],191distributions=self.built_distributions).items())192log.debug(f"cythonized_files = {self.cythonized_files}")193194return self.cythonized_files195196def run(self):197"""198Call ``cythonize()`` to replace the ``ext_modules`` with the199extensions containing Cython-generated C code.200"""201from sage.env import (cython_aliases, sage_include_directories)202# Set variables used in self.create_extension203from ..library_order import library_order204self.library_order = library_order205# Search for dependencies in the source tree and add to the list of include directories206self.sage_include_dirs = sage_include_directories(use_sources=True)207208from Cython.Build import cythonize209import Cython.Compiler.Options210211Cython.Compiler.Options.embed_pos_in_docstring = True212213log.info("Updating Cython code....")214t = time.time()215216from sage.misc.package_dir import cython_namespace_package_support217218with cython_namespace_package_support():219extensions = cythonize(220self.extensions,221nthreads=self.parallel,222build_dir=self.build_dir,223force=self.force,224aliases=cython_aliases(),225compiler_directives=self.cython_directives,226compile_time_env=self.compile_time_env,227create_extension=self.create_extension,228# Debugging229gdb_debug=self.debug,230output_dir=os.path.join(self.build_lib, "sage"),231# Disable Cython caching, which is currently too broken to232# use reliably: https://github.com/sagemath/sage/issues/17851233cache=False,234)235236# We use [:] to change the list in-place because the same list237# object is pointed to from different places.238self.extensions[:] = extensions239240log.info("Finished Cythonizing, time: %.2f seconds." % (time.time() - t))241242with open(self._version_file, 'w') as f:243f.write(self._version_stamp)244245# Finally, copy relevant cythonized files from build/cythonized246# tree into the build-lib tree247for (dst_dir, src_files) in self.get_cythonized_package_files():248dst = os.path.join(self.build_lib, dst_dir)249self.mkpath(dst)250for src in src_files:251self.copy_file(src, dst, preserve_mode=False)252253def create_extension(self, template, kwds):254"""255Create a distutils Extension given data from Cython.256257This adjust the ``kwds`` in the following ways:258259- Make everything depend on *this* setup.py file260261- Add dependencies on header files for certain libraries262263- Sort the libraries according to the library order264265- Add some default compile/link args and directories266267- Choose C99 standard for C code and C++11 for C++ code268269- Drop -std=c99 and similar from C++ extensions270271- Ensure that each flag, library, ... is listed at most once272"""273lang = kwds.get('language', 'c')274cplusplus = (lang == "c++")275276# Libraries: sort them277libs = kwds.get('libraries', [])278kwds['libraries'] = sorted(set(libs),279key=lambda lib: self.library_order.get(lib, 0))280281# Dependencies: add setup.py and lib_headers282depends = kwds.get('depends', []) + [self.distribution.script_name]283for lib, headers in lib_headers.items():284if lib in libs:285depends += headers286kwds['depends'] = depends # These are sorted and uniq'ed by Cython287288# Process extra_compile_args289cflags = []290for flag in kwds.get('extra_compile_args', []):291if flag.startswith("-std="):292if cplusplus and "++" not in flag:293continue # Skip -std=c99 and similar for C++294cflags.append(flag)295cflags = extra_compile_args + cflags296kwds['extra_compile_args'] = stable_uniq(cflags)297298# Process extra_link_args299ldflags = kwds.get('extra_link_args', []) + extra_link_args300kwds['extra_link_args'] = stable_uniq(ldflags)301302# Process library_dirs303lib_dirs = kwds.get('library_dirs', [])304kwds['library_dirs'] = stable_uniq(lib_dirs)305306# Process include_dirs307inc_dirs = kwds.get('include_dirs', []) + self.sage_include_dirs + [self.build_dir]308kwds['include_dirs'] = stable_uniq(inc_dirs)309310from Cython.Build.Dependencies import default_create_extension311312return default_create_extension(template, kwds)313314315