Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/src/sage_setup/command/sage_build_ext.py
4081 views
1
import os
2
import errno
3
4
# Import setuptools before importing distutils, so that setuptools
5
# can replace distutils by its own vendored copy.
6
import setuptools
7
8
from distutils import log
9
from setuptools.command.build_ext import build_ext
10
from distutils.dep_util import newer_group
11
try:
12
# Available since https://setuptools.pypa.io/en/latest/history.html#v59-0-0
13
from setuptools.errors import DistutilsSetupError
14
except ImportError:
15
from distutils.errors import DistutilsSetupError
16
from sage_setup.run_parallel import execute_list_of_commands
17
18
19
class sage_build_ext(build_ext):
20
def finalize_options(self):
21
build_ext.finalize_options(self)
22
self.check_flags()
23
24
def run(self):
25
# Always run the Cythonize command before building extensions
26
self.run_command('build_cython')
27
build_ext.run(self)
28
29
def check_flags(self):
30
"""
31
Sanity check the compiler flags used to build the extensions
32
"""
33
forbidden = None
34
if os.environ.get("SAGE_FAT_BINARY") == "yes":
35
# When building with SAGE_FAT_BINARY=yes, we should not
36
# enable CPU features which do not exist on every CPU.
37
# Such flags usually come from other libraries adding the
38
# flags to the pkgconfig configuration. So if you hit these
39
# errors, the problem is most likely with some external
40
# library and not with Sage.
41
import re
42
forbidden = re.compile(r"-march=|-mpcu=|-msse3|-msse4|-mpopcnt|-mavx")
43
44
if forbidden is not None:
45
errors = 0
46
for ext in self.extensions:
47
flags = ext.extra_compile_args
48
for flag in flags:
49
if forbidden.match(flag):
50
log.error("%s uses forbidden flag '%s'", ext.name, flag)
51
errors += 1
52
if errors:
53
raise RuntimeError("forbidden flags used")
54
55
def build_extensions(self):
56
57
from distutils.debug import DEBUG
58
59
if DEBUG:
60
print("self.compiler.compiler:")
61
print(self.compiler.compiler)
62
print("self.compiler.compiler_cxx:")
63
print(self.compiler.compiler_cxx) # currently not used
64
print("self.compiler.compiler_so:")
65
print(self.compiler.compiler_so)
66
print("self.compiler.linker_so:")
67
print(self.compiler.linker_so)
68
# There are further interesting variables...
69
70
if DEBUG:
71
print("self.compiler.linker_so (after fixing library dirs):")
72
print(self.compiler.linker_so)
73
74
# First, sanity-check the 'extensions' list
75
self.check_extensions_list(self.extensions)
76
77
import time
78
t = time.time()
79
80
compile_commands = []
81
for ext in self.extensions:
82
need_to_compile, p = self.prepare_extension(ext)
83
if need_to_compile:
84
compile_commands.append((self.build_extension, p))
85
86
execute_list_of_commands(compile_commands)
87
88
print("Total time spent compiling C/C++ extensions: %.2f seconds." % (time.time() - t))
89
90
def prepare_extension(self, ext):
91
sources = ext.sources
92
if sources is None or not isinstance(sources, (list, tuple)):
93
raise DistutilsSetupError(("in 'ext_modules' option (extension '%s'), " +
94
"'sources' must be present and must be " +
95
"a list of source filenames") % ext.name)
96
sources = list(sources)
97
98
fullname = self.get_ext_fullname(ext.name)
99
if self.inplace:
100
# ignore build-lib -- put the compiled extension into
101
# the source tree along with pure Python modules
102
103
modpath = fullname.split('.')
104
package = '.'.join(modpath[0:-1])
105
base = modpath[-1]
106
107
build_py = self.get_finalized_command('build_py')
108
package_dir = build_py.get_package_dir(package)
109
ext_filename = os.path.join(package_dir,
110
self.get_ext_filename(base))
111
relative_ext_filename = self.get_ext_filename(base)
112
else:
113
ext_filename = os.path.join(self.build_lib,
114
self.get_ext_filename(fullname))
115
relative_ext_filename = self.get_ext_filename(fullname)
116
117
# while dispatching the calls to gcc in parallel, we sometimes
118
# hit a race condition where two separate build_ext objects
119
# try to create a given directory at the same time; whoever
120
# loses the race then seems to throw an error, saying that
121
# the directory already exists. so, instead of fighting to
122
# fix the race condition, we simply make sure the entire
123
# directory tree exists now, while we're processing the
124
# extensions in serial.
125
relative_ext_dir = os.path.split(relative_ext_filename)[0]
126
prefixes = ['', self.build_lib, self.build_temp]
127
for prefix in prefixes:
128
path = os.path.join(prefix, relative_ext_dir)
129
try:
130
os.makedirs(path)
131
except OSError as e:
132
assert e.errno == errno.EEXIST, 'Cannot create %s.' % path
133
depends = sources + ext.depends
134
if not (self.force or newer_group(depends, ext_filename, 'newer')):
135
log.debug("skipping '%s' extension (up-to-date)", ext.name)
136
need_to_compile = False
137
else:
138
log.info("building '%s' extension", ext.name)
139
need_to_compile = True
140
141
return need_to_compile, (sources, ext, ext_filename)
142
143
def build_extension(self, p):
144
145
sources, ext, ext_filename = p
146
147
# First, scan the sources for SWIG definition files (.i), run
148
# SWIG on 'em to create .c files, and modify the sources list
149
# accordingly.
150
sources = self.swig_sources(sources, ext)
151
152
# Next, compile the source code to object files.
153
154
# XXX not honouring 'define_macros' or 'undef_macros' -- the
155
# CCompiler API needs to change to accommodate this, and I
156
# want to do one thing at a time!
157
158
# Two possible sources for extra compiler arguments:
159
# - 'extra_compile_args' in Extension object
160
# - CFLAGS environment variable (not particularly
161
# elegant, but people seem to expect it and I
162
# guess it's useful)
163
# The environment variable should take precedence, and
164
# any sensible compiler will give precedence to later
165
# command line args. Hence we combine them in order:
166
extra_args = ext.extra_compile_args or []
167
168
macros = ext.define_macros[:]
169
for undef in ext.undef_macros:
170
macros.append((undef,))
171
172
objects = self.compiler.compile(sources,
173
output_dir=self.build_temp,
174
macros=macros,
175
include_dirs=ext.include_dirs,
176
debug=self.debug,
177
extra_postargs=extra_args,
178
depends=ext.depends)
179
180
# XXX -- this is a Vile HACK!
181
#
182
# The setup.py script for Python on Unix needs to be able to
183
# get this list so it can perform all the clean up needed to
184
# avoid keeping object files around when cleaning out a failed
185
# build of an extension module. Since Distutils does not
186
# track dependencies, we have to get rid of intermediates to
187
# ensure all the intermediates will be properly re-built.
188
#
189
self._built_objects = objects[:]
190
191
# Now link the object files together into a "shared object" --
192
# of course, first we have to figure out all the other things
193
# that go into the mix.
194
if ext.extra_objects:
195
objects.extend(ext.extra_objects)
196
extra_args = ext.extra_link_args or []
197
198
# Detect target language, if not provided
199
language = ext.language or self.compiler.detect_language(sources)
200
201
self.compiler.link_shared_object(
202
objects, ext_filename,
203
libraries=self.get_libraries(ext),
204
library_dirs=ext.library_dirs,
205
runtime_library_dirs=ext.runtime_library_dirs,
206
extra_postargs=extra_args,
207
export_symbols=self.get_export_symbols(ext),
208
debug=self.debug,
209
build_temp=self.build_temp,
210
target_lang=language)
211
212