recompile Cython source files when .pxd override files change
[cython.git] / setup.py
1 from distutils.core import setup, Extension
2 from distutils.sysconfig import get_python_lib
3 import os, os.path
4 import sys
5
6 if 'sdist' in sys.argv and sys.platform != "win32" and sys.version_info >= (2,4):
7     # Record the current revision in .hgrev
8     import subprocess # os.popen is cleaner but deprecated
9     changeset = subprocess.Popen("hg identify --id --rev tip".split(),
10                                  stdout=subprocess.PIPE).stdout.read()
11     rev = changeset.decode('ISO-8859-1').strip()
12     hgrev = open('.hgrev', 'w')
13     hgrev.write(rev)
14     hgrev.close()
15
16 if sys.platform == "darwin":
17     # Don't create resource files on OS X tar.
18     os.environ['COPY_EXTENDED_ATTRIBUTES_DISABLE'] = 'true'
19     os.environ['COPYFILE_DISABLE'] = 'true'
20
21 setup_args = {}
22
23 def add_command_class(name, cls):
24     cmdclasses = setup_args.get('cmdclass', {})
25     cmdclasses[name] = cls
26     setup_args['cmdclass'] = cmdclasses
27
28 if sys.version_info[0] >= 3:
29     import lib2to3.refactor
30     from distutils.command.build_py \
31          import build_py_2to3 as build_py
32     # need to convert sources to Py3 on installation
33     fixers = [ fix for fix in lib2to3.refactor.get_fixers_from_package("lib2to3.fixes")
34                if fix.split('fix_')[-1] not in ('next',)
35                ]
36     build_py.fixer_names = fixers
37     add_command_class("build_py", build_py)
38
39 pxd_include_dirs = [
40     directory for directory, dirs, files in os.walk('Cython/Includes')
41     if '__init__.pyx' in files or '__init__.pxd' in files
42     or directory == 'Cython/Includes' or directory == 'Cython/Includes/Deprecated']
43
44 pxd_include_patterns = [
45     p+'/*.pxd' for p in pxd_include_dirs ] + [
46     p+'/*.pyx' for p in pxd_include_dirs ]
47
48 if sys.version_info < (2,4):
49     install_base_dir = get_python_lib(prefix='')
50     import glob
51     patterns = pxd_include_patterns + [
52         'Cython/Plex/*.pxd',
53         'Cython/Compiler/*.pxd',
54         'Cython/Runtime/*.pyx'
55         ]
56     setup_args['data_files'] = [
57         (os.path.dirname(os.path.join(install_base_dir, pattern)),
58          [ f for f in glob.glob(pattern) ])
59         for pattern in patterns
60         ]
61 else:
62     setup_args['package_data'] = {
63         'Cython.Plex'     : ['*.pxd'],
64         'Cython.Compiler' : ['*.pxd'],
65         'Cython.Runtime'  : ['*.pyx', '*.pxd'],
66         'Cython'          : [ p[7:] for p in pxd_include_patterns ],
67         }
68
69 # This dict is used for passing extra arguments that are setuptools 
70 # specific to setup
71 setuptools_extra_args = {}
72
73 if 'setuptools' in sys.modules:
74     setuptools_extra_args['zip_safe'] = False
75     setuptools_extra_args['entry_points'] = {
76         'console_scripts': [
77             'cython = Cython.Compiler.Main:setuptools_main',
78         ]
79     }
80     scripts = []
81 else:
82     if os.name == "posix":
83         scripts = ["bin/cython"]
84     else:
85         scripts = ["cython.py"]
86
87 def compile_cython_modules(profile=False):
88     source_root = os.path.abspath(os.path.dirname(__file__))
89     compiled_modules = ["Cython.Plex.Scanners",
90                         "Cython.Compiler.Scanning",
91                         "Cython.Compiler.Parsing",
92                         "Cython.Compiler.Visitor",
93                         "Cython.Runtime.refnanny"]
94     extensions = []
95
96     if sys.version_info[0] >= 3:
97         from Cython.Distutils import build_ext as build_ext_orig
98         for module in compiled_modules:
99             source_file = os.path.join(source_root, *module.split('.'))
100             if os.path.exists(source_file + ".py"):
101                 pyx_source_file = source_file + ".py"
102             else:
103                 pyx_source_file = source_file + ".pyx"
104             dep_files = []
105             if os.path.exists(source_file + '.pxd'):
106                 dep_files.append(source_file + '.pxd')
107             extensions.append(
108                 Extension(module, sources = [pyx_source_file],
109                           depends = dep_files)
110                 )
111
112         class build_ext(build_ext_orig):
113             # we must keep the original modules alive to make sure
114             # their code keeps working when we remove them from
115             # sys.modules
116             dead_modules = []
117
118             def build_extensions(self):
119                 # add path where 2to3 installed the transformed sources
120                 # and make sure Python (re-)imports them from there
121                 already_imported = [ module for module in sys.modules
122                                      if module == 'Cython' or module.startswith('Cython.') ]
123                 keep_alive = self.dead_modules.append
124                 for module in already_imported:
125                     keep_alive(sys.modules[module])
126                     del sys.modules[module]
127                 sys.path.insert(0, os.path.join(source_root, self.build_lib))
128
129                 if profile:
130                     from Cython.Compiler.Options import directive_defaults
131                     directive_defaults['profile'] = True
132                     print("Enabled profiling for the Cython binary modules")
133                 build_ext_orig.build_extensions(self)
134
135         setup_args['ext_modules'] = extensions
136         add_command_class("build_ext", build_ext)
137
138     else: # Python 2.x
139         from distutils.command.build_ext import build_ext as build_ext_orig
140         try:
141             class build_ext(build_ext_orig):
142                 def build_extension(self, ext, *args, **kargs):
143                     try:
144                         build_ext_orig.build_extension(self, ext, *args, **kargs)
145                     except StandardError:
146                         print("Compilation of '%s' failed" % ext.sources[0])
147             from Cython.Compiler.Main import compile
148             from Cython import Utils
149             if profile:
150                 from Cython.Compiler.Options import directive_defaults
151                 directive_defaults['profile'] = True
152                 print("Enabled profiling for the Cython binary modules")
153             source_root = os.path.dirname(__file__)
154             for module in compiled_modules:
155                 source_file = os.path.join(source_root, *module.split('.'))
156                 if os.path.exists(source_file + ".py"):
157                     pyx_source_file = source_file + ".py"
158                 else:
159                     pyx_source_file = source_file + ".pyx"
160                 c_source_file = source_file + ".c"
161                 source_is_newer = False
162                 if not os.path.exists(c_source_file):
163                     source_is_newer = True
164                 else:
165                     c_last_modified = Utils.modification_time(c_source_file)
166                     if Utils.file_newer_than(pyx_source_file, c_last_modified):
167                         source_is_newer = True
168                     else:
169                         pxd_source_file = source_file + ".pxd"
170                         if os.path.exists(pxd_source_file) and Utils.file_newer_than(pxd_source_file, c_last_modified):
171                             source_is_newer = True
172                 if source_is_newer:
173                     print("Compiling module %s ..." % module)
174                     result = compile(pyx_source_file)
175                     c_source_file = result.c_file
176                 if c_source_file:
177                     # Py2 distutils can't handle unicode file paths
178                     if isinstance(c_source_file, unicode):
179                         filename_encoding = sys.getfilesystemencoding()
180                         if filename_encoding is None:
181                             filename_encoding = sys.getdefaultencoding()
182                         c_source_file = c_source_file.encode(filename_encoding)
183                     extensions.append(
184                         Extension(module, sources = [c_source_file])
185                         )
186                 else:
187                     print("Compilation failed")
188             if extensions:
189                 setup_args['ext_modules'] = extensions
190                 add_command_class("build_ext", build_ext)
191         except Exception:
192             print('''
193 ERROR: %s
194
195 Extension module compilation failed, looks like Cython cannot run
196 properly on this system.  To work around this, pass the option
197 "--no-cython-compile".  This will install a pure Python version of
198 Cython without compiling its own sources.
199 ''' % sys.exc_info()[1])
200             raise
201
202 cython_profile = '--cython-profile' in sys.argv
203 if cython_profile:
204     sys.argv.remove('--cython-profile')
205
206 try:
207     sys.argv.remove("--no-cython-compile")
208 except ValueError:
209     compile_cython_modules(cython_profile)
210
211 setup_args.update(setuptools_extra_args)
212
213 from Cython import __version__ as version
214
215 setup(
216   name = 'Cython',
217   version = version,
218   url = 'http://www.cython.org',
219   author = 'Greg Ewing, Robert Bradshaw, Stefan Behnel, Dag Seljebotn, et al.',
220   author_email = 'cython-dev@codespeak.net',
221   description = "The Cython compiler for writing C extensions for the Python language.",
222   long_description = """\
223   The Cython language makes writing C extensions for the Python language as
224   easy as Python itself.  Cython is a source code translator based on the
225   well-known Pyrex_, but supports more cutting edge functionality and
226   optimizations.
227
228   The Cython language is very close to the Python language (and most Python
229   code is also valid Cython code), but Cython additionally supports calling C
230   functions and declaring C types on variables and class attributes. This
231   allows the compiler to generate very efficient C code from Cython code.
232
233   This makes Cython the ideal language for writing glue code for external C
234   libraries, and for fast C modules that speed up the execution of Python
235   code.
236
237   .. _Pyrex: http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/
238   """,
239   classifiers = [
240     "Development Status :: 5 - Production/Stable",
241     "Intended Audience :: Developers",
242     "License :: OSI Approved :: Apache Software License",
243     "Operating System :: OS Independent",
244     "Programming Language :: Python",
245     "Programming Language :: Python :: 2",
246     "Programming Language :: Python :: 3",
247     "Programming Language :: C",
248     "Programming Language :: Cython",
249     "Topic :: Software Development :: Code Generators",
250     "Topic :: Software Development :: Compilers",
251     "Topic :: Software Development :: Libraries :: Python Modules"
252   ],
253
254   scripts = scripts,
255   packages=[
256     'Cython',
257     'Cython.Build',
258     'Cython.Compiler',
259     'Cython.Runtime',
260     'Cython.Distutils',
261     'Cython.Plex',
262
263     'Cython.Tests',
264     'Cython.Compiler.Tests',
265     ],
266
267   # pyximport
268   py_modules = ["pyximport/__init__",
269                 "pyximport/pyximport",
270                 "pyximport/pyxbuild",
271
272                 "cython"],
273
274   **setup_args
275   )