Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/setup.py
8814 views
1
#!/usr/bin/env python
2
3
import os, sys, time, errno, platform, subprocess
4
from distutils.core import setup
5
from distutils.extension import Extension
6
from glob import glob, fnmatch
7
from warnings import warn
8
9
#########################################################
10
### List of Extensions
11
###
12
### Since Sage 3.2 the list of extensions resides in
13
### module_list.py in the same directory as this file
14
### (augmented by the list of interpreters
15
### generated by sage/ext/gen_interpreters.py)
16
#########################################################
17
18
from module_list import ext_modules
19
import sage.ext.gen_interpreters
20
import warnings
21
from sage.env import *
22
23
#########################################################
24
### Configuration
25
#########################################################
26
27
if len(sys.argv) > 1 and sys.argv[1] == "sdist":
28
sdist = True
29
else:
30
sdist = False
31
32
try:
33
compile_result_dir = os.environ['XML_RESULTS']
34
keep_going = True
35
except KeyError:
36
compile_result_dir = None
37
keep_going = False
38
39
SAGE_INC = os.path.join(SAGE_LOCAL,'include')
40
41
# search for dependencies and add to gcc -I<path>
42
include_dirs = [SAGE_INC,
43
os.path.join(SAGE_INC, 'csage'),
44
SAGE_SRC,
45
os.path.join(SAGE_SRC, 'sage', 'ext')]
46
47
# search for dependencies only
48
extra_include_dirs = [ os.path.join(SAGE_INC,'python'+platform.python_version().rsplit('.', 1)[0]) ]
49
50
# Manually add -fno-strict-aliasing, which is needed to compile Cython
51
# and disappears from the default flags if the user has set CFLAGS.
52
extra_compile_args = [ "-fno-strict-aliasing" ]
53
extra_link_args = [ ]
54
55
# comment these four lines out to turn on warnings from gcc
56
import distutils.sysconfig
57
NO_WARN = True
58
if NO_WARN and distutils.sysconfig.get_config_var('CC').startswith("gcc"):
59
extra_compile_args.append('-w')
60
61
DEVEL = False
62
if DEVEL:
63
extra_compile_args.append('-ggdb')
64
65
# Work around GCC-4.8.0 bug which miscompiles some sig_on() statements,
66
# as witnessed by a doctest in sage/libs/gap/element.pyx if the
67
# compiler flag -Og is used. See also
68
# * http://trac.sagemath.org/sage_trac/ticket/14460
69
# * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56982
70
if subprocess.call("""$CC --version | grep -i 'gcc.* 4[.][89]' >/dev/null """, shell=True) == 0:
71
extra_compile_args.append('-fno-tree-dominator-opts')
72
73
# Generate interpreters
74
75
sage.ext.gen_interpreters.rebuild(os.path.join(SAGE_SRC, 'sage', 'ext', 'interpreters'))
76
ext_modules = ext_modules + sage.ext.gen_interpreters.modules
77
78
79
#########################################################
80
### Testing related stuff
81
#########################################################
82
83
class CompileRecorder(object):
84
85
def __init__(self, f):
86
self._f = f
87
self._obj = None
88
89
def __get__(self, obj, type=None):
90
# Act like a method...
91
self._obj = obj
92
return self
93
94
def __call__(self, *args):
95
t = time.time()
96
try:
97
if self._obj:
98
res = self._f(self._obj, *args)
99
else:
100
res = self._f(*args)
101
except Exception, ex:
102
print ex
103
res = ex
104
t = time.time() - t
105
106
errors = failures = 0
107
if self._f is compile_command0:
108
name = "cythonize." + args[0][1].name
109
failures = int(bool(res))
110
else:
111
name = "gcc." + args[0][1].name
112
errors = int(bool(res))
113
if errors or failures:
114
type = "failure" if failures else "error"
115
failure_item = """<%(type)s/>""" % locals()
116
else:
117
failure_item = ""
118
output = open("%s/%s.xml" % (compile_result_dir, name), "w")
119
output.write("""
120
<?xml version="1.0" ?>
121
<testsuite name="%(name)s" errors="%(errors)s" failures="%(failures)s" tests="1" time="%(t)s">
122
<testcase classname="%(name)s" name="compile">
123
%(failure_item)s
124
</testcase>
125
</testsuite>
126
""".strip() % locals())
127
output.close()
128
return res
129
130
if compile_result_dir:
131
record_compile = CompileRecorder
132
else:
133
record_compile = lambda x: x
134
135
# Remove (potentially invalid) star import caches
136
import sage.misc.lazy_import_cache
137
if os.path.exists(sage.misc.lazy_import_cache.get_cache_file()):
138
os.unlink(sage.misc.lazy_import_cache.get_cache_file())
139
140
141
######################################################################
142
# CODE for generating C/C++ code from Cython and doing dependency
143
# checking, etc. In theory distutils would run Cython, but I don't
144
# trust it at all, and it won't have the more sophisticated dependency
145
# checking that we need.
146
######################################################################
147
148
# Do not put all, but only the most common libraries and their headers
149
# (that are likely to change on an upgrade) here:
150
# [At least at the moment. Make sure the headers aren't copied with "-p",
151
# or explicitly touch them in the respective spkg's spkg-install.]
152
lib_headers = { "gmp": [ os.path.join(SAGE_INC,'gmp.h') ], # cf. #8664, #9896
153
"gmpxx": [ os.path.join(SAGE_INC,'gmpxx.h') ]
154
}
155
156
for m in ext_modules:
157
158
for lib in lib_headers.keys():
159
if lib in m.libraries:
160
m.depends += lib_headers[lib]
161
162
# FIMXE: Do NOT link the following libraries to each and
163
# every module (regardless of the language btw.):
164
m.libraries = ['csage'] + m.libraries + ['stdc++', 'ntl']
165
166
m.extra_compile_args += extra_compile_args
167
m.extra_link_args += extra_link_args
168
m.library_dirs += ['%s/lib' % SAGE_LOCAL]
169
170
171
172
#############################################
173
###### Parallel Cython execution
174
#############################################
175
176
def run_command(cmd):
177
"""
178
INPUT:
179
cmd -- a string; a command to run
180
181
OUTPUT:
182
prints cmd to the console and then runs os.system
183
"""
184
print cmd
185
return os.system(cmd)
186
187
def apply_pair(p):
188
"""
189
Given a pair p consisting of a function and a value, apply
190
the function to the value.
191
192
This exists solely because we can't pickle an anonymous function
193
in execute_list_of_commands_in_parallel below.
194
"""
195
return p[0](p[1])
196
197
def execute_list_of_commands_in_parallel(command_list, nthreads):
198
"""
199
Execute the given list of commands, possibly in parallel, using
200
``nthreads`` threads. Terminates ``setup.py`` with an exit code
201
of 1 if an error occurs in any subcommand.
202
203
INPUT:
204
205
- ``command_list`` -- a list of commands, each given as a pair of
206
the form ``[function, argument]`` of a function to call and its
207
argument
208
209
- ``nthreads`` -- integer; number of threads to use
210
211
WARNING: commands are run roughly in order, but of course successive
212
commands may be run at the same time.
213
"""
214
from multiprocessing import Pool
215
import fpickle_setup #doing this import will allow instancemethods to be pickable
216
p = Pool(nthreads)
217
process_command_results(p.imap(apply_pair, command_list))
218
219
def process_command_results(result_values):
220
error = None
221
for r in result_values:
222
if r:
223
print "Error running command, failed with status %s."%r
224
if not keep_going:
225
sys.exit(1)
226
error = r
227
if error:
228
sys.exit(1)
229
230
def execute_list_of_commands(command_list):
231
"""
232
INPUT:
233
234
- ``command_list`` -- a list of strings or pairs
235
236
OUTPUT:
237
238
For each entry in command_list, we attempt to run the command.
239
If it is a string, we call ``os.system()``. If it is a pair [f, v],
240
we call f(v).
241
242
If the environment variable :envvar:`SAGE_NUM_THREADS` is set, use
243
that many threads.
244
"""
245
t = time.time()
246
# Determine the number of threads from the environment variable
247
# SAGE_NUM_THREADS, which is set automatically by sage-env
248
try:
249
nthreads = int(os.environ['SAGE_NUM_THREADS'])
250
except KeyError:
251
nthreads = 1
252
253
# normalize the command_list to handle strings correctly
254
command_list = [ [run_command, x] if isinstance(x, str) else x for x in command_list ]
255
256
# No need for more threads than there are commands, but at least one
257
nthreads = min(len(command_list), nthreads)
258
nthreads = max(1, nthreads)
259
260
def plural(n,noun):
261
if n == 1:
262
return "1 %s"%noun
263
return "%i %ss"%(n,noun)
264
265
print "Executing %s (using %s)"%(plural(len(command_list),"command"), plural(nthreads,"thread"))
266
execute_list_of_commands_in_parallel(command_list, nthreads)
267
print "Time to execute %s: %s seconds"%(plural(len(command_list),"command"), time.time() - t)
268
269
270
########################################################################
271
##
272
## Parallel gcc execution
273
##
274
## This code is responsible for making distutils dispatch the calls to
275
## build_ext in parallel. Since distutils doesn't seem to do this by
276
## default, we create our own extension builder and override the
277
## appropriate methods. Unfortunately, in distutils, the logic of
278
## deciding whether an extension needs to be recompiled and actually
279
## making the call to gcc to recompile the extension are in the same
280
## function. As a result, we can't just override one function and have
281
## everything magically work. Instead, we split this work between two
282
## functions. This works fine for our application, but it means that
283
## we can't use this modification to make the other parts of Sage that
284
## build with distutils call gcc in parallel.
285
##
286
########################################################################
287
288
from distutils.command.build_ext import build_ext
289
from distutils.dep_util import newer_group
290
from types import ListType, TupleType
291
from distutils import log
292
293
class sage_build_ext(build_ext):
294
295
def build_extensions(self):
296
297
from distutils.debug import DEBUG
298
299
if DEBUG:
300
print "self.compiler.compiler:"
301
print self.compiler.compiler
302
print "self.compiler.compiler_cxx:"
303
print self.compiler.compiler_cxx # currently not used
304
print "self.compiler.compiler_so:"
305
print self.compiler.compiler_so
306
print "self.compiler.linker_so:"
307
print self.compiler.linker_so
308
# There are further interesting variables...
309
sys.stdout.flush()
310
311
312
# At least on MacOS X, the library dir of the *original* Sage
313
# installation is "hard-coded" into the linker *command*, s.t.
314
# that directory is always searched *first*, which causes trouble
315
# after the Sage installation has been moved (or its directory simply
316
# been renamed), especially in conjunction with upgrades (cf. #9896).
317
# (In principle, the Python configuration should be modified on
318
# Sage relocations as well, but until that's done, we simply fix
319
# the most important.)
320
# Since the following is performed only once per call to "setup",
321
# and doesn't hurt on other systems, we unconditionally replace *any*
322
# library directory specified in the (dynamic) linker command by the
323
# current Sage library directory (if it doesn't already match that),
324
# and issue a warning message:
325
326
if True or sys.platform[:6]=="darwin":
327
328
sage_libdir = os.path.realpath(SAGE_LOCAL+"/lib")
329
ldso_cmd = self.compiler.linker_so # a list of strings, like argv
330
331
for i in range(1, len(ldso_cmd)):
332
333
if ldso_cmd[i][:2] == "-L":
334
libdir = os.path.realpath(ldso_cmd[i][2:])
335
self.debug_print(
336
"Library dir found in dynamic linker command: " +
337
"\"%s\"" % libdir)
338
if libdir != sage_libdir:
339
self.compiler.warn(
340
"Replacing library search directory in linker " +
341
"command:\n \"%s\" -> \"%s\"\n" % (libdir,
342
sage_libdir))
343
ldso_cmd[i] = "-L"+sage_libdir
344
345
if DEBUG:
346
print "self.compiler.linker_so (after fixing library dirs):"
347
print self.compiler.linker_so
348
sys.stdout.flush()
349
350
351
# First, sanity-check the 'extensions' list
352
self.check_extensions_list(self.extensions)
353
354
import time
355
t = time.time()
356
357
compile_commands = []
358
for ext in self.extensions:
359
need_to_compile, p = self.prepare_extension(ext)
360
if need_to_compile:
361
compile_commands.append((record_compile(self.build_extension), p))
362
363
execute_list_of_commands(compile_commands)
364
365
print "Total time spent compiling C/C++ extensions: ", time.time() - t, "seconds."
366
367
def prepare_extension(self, ext):
368
sources = ext.sources
369
if sources is None or type(sources) not in (ListType, TupleType):
370
raise DistutilsSetupError, \
371
("in 'ext_modules' option (extension '%s'), " +
372
"'sources' must be present and must be " +
373
"a list of source filenames") % ext.name
374
sources = list(sources)
375
376
fullname = self.get_ext_fullname(ext.name)
377
if self.inplace:
378
# ignore build-lib -- put the compiled extension into
379
# the source tree along with pure Python modules
380
381
modpath = string.split(fullname, '.')
382
package = string.join(modpath[0:-1], '.')
383
base = modpath[-1]
384
385
build_py = self.get_finalized_command('build_py')
386
package_dir = build_py.get_package_dir(package)
387
ext_filename = os.path.join(package_dir,
388
self.get_ext_filename(base))
389
relative_ext_filename = self.get_ext_filename(base)
390
else:
391
ext_filename = os.path.join(self.build_lib,
392
self.get_ext_filename(fullname))
393
relative_ext_filename = self.get_ext_filename(fullname)
394
395
# while dispatching the calls to gcc in parallel, we sometimes
396
# hit a race condition where two separate build_ext objects
397
# try to create a given directory at the same time; whoever
398
# loses the race then seems to throw an error, saying that
399
# the directory already exists. so, instead of fighting to
400
# fix the race condition, we simply make sure the entire
401
# directory tree exists now, while we're processing the
402
# extensions in serial.
403
relative_ext_dir = os.path.split(relative_ext_filename)[0]
404
prefixes = ['', self.build_lib, self.build_temp]
405
for prefix in prefixes:
406
path = os.path.join(prefix, relative_ext_dir)
407
try:
408
os.makedirs(path)
409
except OSError, e:
410
assert e.errno==errno.EEXIST, 'Cannot create %s.' % path
411
depends = sources + ext.depends
412
if not (self.force or newer_group(depends, ext_filename, 'newer')):
413
log.debug("skipping '%s' extension (up-to-date)", ext.name)
414
need_to_compile = False
415
else:
416
log.info("building '%s' extension", ext.name)
417
need_to_compile = True
418
419
return need_to_compile, (sources, ext, ext_filename)
420
421
def build_extension(self, p):
422
423
sources, ext, ext_filename = p
424
425
# First, scan the sources for SWIG definition files (.i), run
426
# SWIG on 'em to create .c files, and modify the sources list
427
# accordingly.
428
sources = self.swig_sources(sources, ext)
429
430
# Next, compile the source code to object files.
431
432
# XXX not honouring 'define_macros' or 'undef_macros' -- the
433
# CCompiler API needs to change to accommodate this, and I
434
# want to do one thing at a time!
435
436
# Two possible sources for extra compiler arguments:
437
# - 'extra_compile_args' in Extension object
438
# - CFLAGS environment variable (not particularly
439
# elegant, but people seem to expect it and I
440
# guess it's useful)
441
# The environment variable should take precedence, and
442
# any sensible compiler will give precedence to later
443
# command line args. Hence we combine them in order:
444
extra_args = ext.extra_compile_args or []
445
446
macros = ext.define_macros[:]
447
for undef in ext.undef_macros:
448
macros.append((undef,))
449
450
objects = self.compiler.compile(sources,
451
output_dir=self.build_temp,
452
macros=macros,
453
include_dirs=ext.include_dirs,
454
debug=self.debug,
455
extra_postargs=extra_args,
456
depends=ext.depends)
457
458
# XXX -- this is a Vile HACK!
459
#
460
# The setup.py script for Python on Unix needs to be able to
461
# get this list so it can perform all the clean up needed to
462
# avoid keeping object files around when cleaning out a failed
463
# build of an extension module. Since Distutils does not
464
# track dependencies, we have to get rid of intermediates to
465
# ensure all the intermediates will be properly re-built.
466
#
467
self._built_objects = objects[:]
468
469
# Now link the object files together into a "shared object" --
470
# of course, first we have to figure out all the other things
471
# that go into the mix.
472
if ext.extra_objects:
473
objects.extend(ext.extra_objects)
474
extra_args = ext.extra_link_args or []
475
476
# Detect target language, if not provided
477
language = ext.language or self.compiler.detect_language(sources)
478
479
self.compiler.link_shared_object(
480
objects, ext_filename,
481
libraries=self.get_libraries(ext),
482
library_dirs=ext.library_dirs,
483
runtime_library_dirs=ext.runtime_library_dirs,
484
extra_postargs=extra_args,
485
export_symbols=self.get_export_symbols(ext),
486
debug=self.debug,
487
build_temp=self.build_temp,
488
target_lang=language)
489
490
491
492
493
#############################################
494
###### Cythonize
495
#############################################
496
497
if not sdist:
498
print "Updating Cython code...."
499
t = time.time()
500
501
from Cython.Build import cythonize
502
import Cython.Compiler.Options
503
import Cython.Compiler.Main
504
505
# Sage uses these directives (mostly for historical reasons).
506
Cython.Compiler.Options.embed_pos_in_docstring = True
507
Cython.Compiler.Options.directive_defaults['autotestdict'] = False
508
Cython.Compiler.Options.directive_defaults['cdivision'] = True
509
Cython.Compiler.Options.directive_defaults['fast_getattr'] = True
510
# The globals() builtin in Cython was fixed to return to the current scope,
511
# but Sage relies on the broken behavior of returning to the nearest
512
# enclosing Python scope (e.g. to perform variable injection).
513
Cython.Compiler.Options.old_style_globals = True
514
515
if os.environ.get('SAGE_DEBUG', None) != 'no':
516
Cython.Compiler.Main.default_options['gdb_debug'] = True
517
Cython.Compiler.Main.default_options['output_dir'] = 'build'
518
519
CYCACHE_DIR = os.environ.get('CYCACHE_DIR', os.path.join(DOT_SAGE,'cycache'))
520
if os.path.exists(os.path.join(CYCACHE_DIR, os.pardir)):
521
Cython.Compiler.Main.default_options['cache'] = CYCACHE_DIR
522
523
force = True
524
version_file = os.path.join(os.path.dirname(__file__), '.cython_version')
525
if os.path.exists(version_file) and open(version_file).read() == Cython.__version__:
526
force = False
527
528
for ext_module in ext_modules:
529
ext_module.include_dirs += include_dirs
530
531
ext_modules = cythonize(
532
ext_modules,
533
nthreads = int(os.environ.get('SAGE_NUM_THREADS', 0)),
534
build_dir = None, # Don't "cythonize out-of-tree" (cf. #14570) until
535
# sage-clone and sage-sync-build can deal with that.
536
force=force)
537
538
open(version_file, 'w').write(Cython.__version__)
539
print "Finished compiling Cython code (time = %s seconds)" % (time.time() - t)
540
sys.stdout.flush()
541
542
543
#########################################################
544
### Distutils
545
#########################################################
546
547
def python_packages():
548
packages = []
549
root = os.path.join(os.path.dirname(__file__))
550
for dirpath, dirnames, filenames in os.walk(os.path.join(root, 'sage')):
551
if '__init__.py' in filenames:
552
packages.append(dirpath.replace(os.path.sep, '.'))
553
return packages
554
555
code = setup(name = 'sage',
556
557
version = SAGE_VERSION,
558
559
description = 'Sage: Open Source Mathematics Software',
560
561
license = 'GNU Public License (GPL)',
562
563
author = 'William Stein et al.',
564
565
author_email= 'http://groups.google.com/group/sage-support',
566
567
url = 'http://www.sagemath.org',
568
569
packages = python_packages(),
570
571
scripts = [],
572
573
cmdclass = { 'build_ext': sage_build_ext },
574
575
ext_modules = ext_modules,
576
include_dirs = include_dirs)
577
578
579