Cython 3.3.0a1__cp315-cp315-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Cython/Build/BuildExecutable.py +156 -0
- Cython/Build/Cache.py +199 -0
- Cython/Build/Cythonize.py +349 -0
- Cython/Build/Dependencies.py +1276 -0
- Cython/Build/Distutils.py +1 -0
- Cython/Build/Inline.py +467 -0
- Cython/Build/IpythonMagic.py +559 -0
- Cython/Build/SharedModule.py +84 -0
- Cython/Build/Tests/TestCyCache.py +195 -0
- Cython/Build/Tests/TestCythonizeArgsParser.py +480 -0
- Cython/Build/Tests/TestDependencies.py +133 -0
- Cython/Build/Tests/TestInline.py +177 -0
- Cython/Build/Tests/TestIpythonMagic.py +303 -0
- Cython/Build/Tests/TestRecythonize.py +212 -0
- Cython/Build/Tests/TestStripLiterals.py +155 -0
- Cython/Build/Tests/__init__.py +1 -0
- Cython/Build/__init__.py +11 -0
- Cython/CodeWriter.py +815 -0
- Cython/Compiler/AnalysedTreeTransforms.py +97 -0
- Cython/Compiler/Annotate.py +328 -0
- Cython/Compiler/AutoDocTransforms.py +320 -0
- Cython/Compiler/Buffer.py +680 -0
- Cython/Compiler/Builtin.py +997 -0
- Cython/Compiler/CmdLine.py +263 -0
- Cython/Compiler/Code.cp315-win_amd64.pyd +0 -0
- Cython/Compiler/Code.pxd +152 -0
- Cython/Compiler/Code.py +3907 -0
- Cython/Compiler/CodeGeneration.py +33 -0
- Cython/Compiler/CythonScope.py +194 -0
- Cython/Compiler/Dataclass.py +890 -0
- Cython/Compiler/DebugFlags.py +24 -0
- Cython/Compiler/Errors.py +310 -0
- Cython/Compiler/ExprNodes.py +15983 -0
- Cython/Compiler/FlowControl.cp315-win_amd64.pyd +0 -0
- Cython/Compiler/FlowControl.pxd +99 -0
- Cython/Compiler/FlowControl.py +1571 -0
- Cython/Compiler/FusedNode.cp315-win_amd64.pyd +0 -0
- Cython/Compiler/FusedNode.py +976 -0
- Cython/Compiler/Future.py +16 -0
- Cython/Compiler/Interpreter.py +57 -0
- Cython/Compiler/Lexicon.py +422 -0
- Cython/Compiler/LineTable.cp315-win_amd64.pyd +0 -0
- Cython/Compiler/LineTable.py +114 -0
- Cython/Compiler/Main.py +856 -0
- Cython/Compiler/MatchCaseNodes.py +2197 -0
- Cython/Compiler/MemoryView.py +930 -0
- Cython/Compiler/ModuleNode.py +4517 -0
- Cython/Compiler/Naming.py +367 -0
- Cython/Compiler/Nodes.py +10941 -0
- Cython/Compiler/Optimize.py +5455 -0
- Cython/Compiler/Options.py +838 -0
- Cython/Compiler/ParseTreeTransforms.pxd +79 -0
- Cython/Compiler/ParseTreeTransforms.py +4744 -0
- Cython/Compiler/Parsing.cp315-win_amd64.pyd +0 -0
- Cython/Compiler/Parsing.pxd +9 -0
- Cython/Compiler/Parsing.py +4792 -0
- Cython/Compiler/Pipeline.py +439 -0
- Cython/Compiler/PyrexTypes.py +6111 -0
- Cython/Compiler/Pythran.py +232 -0
- Cython/Compiler/Scanning.cp315-win_amd64.pyd +0 -0
- Cython/Compiler/Scanning.pxd +70 -0
- Cython/Compiler/Scanning.py +720 -0
- Cython/Compiler/StringEncoding.py +297 -0
- Cython/Compiler/Symtab.py +3092 -0
- Cython/Compiler/Tests/TestBuffer.py +105 -0
- Cython/Compiler/Tests/TestBuiltin.py +117 -0
- Cython/Compiler/Tests/TestCmdLine.py +587 -0
- Cython/Compiler/Tests/TestCode.py +145 -0
- Cython/Compiler/Tests/TestFlowControl.py +65 -0
- Cython/Compiler/Tests/TestGrammar.py +202 -0
- Cython/Compiler/Tests/TestMemView.py +71 -0
- Cython/Compiler/Tests/TestParseTreeTransforms.py +285 -0
- Cython/Compiler/Tests/TestScanning.py +132 -0
- Cython/Compiler/Tests/TestSignatureMatching.py +73 -0
- Cython/Compiler/Tests/TestStringEncoding.py +20 -0
- Cython/Compiler/Tests/TestTreeFragment.py +63 -0
- Cython/Compiler/Tests/TestTreePath.py +103 -0
- Cython/Compiler/Tests/TestTypes.py +118 -0
- Cython/Compiler/Tests/TestUtilityLoad.py +112 -0
- Cython/Compiler/Tests/TestVisitor.py +61 -0
- Cython/Compiler/Tests/Utils.py +36 -0
- Cython/Compiler/Tests/__init__.py +1 -0
- Cython/Compiler/TreeFragment.py +278 -0
- Cython/Compiler/TreePath.py +303 -0
- Cython/Compiler/TypeInference.py +611 -0
- Cython/Compiler/TypeSlots.py +1329 -0
- Cython/Compiler/UFuncs.py +311 -0
- Cython/Compiler/UtilNodes.py +413 -0
- Cython/Compiler/UtilityCode.py +348 -0
- Cython/Compiler/Version.py +8 -0
- Cython/Compiler/Visitor.cp315-win_amd64.pyd +0 -0
- Cython/Compiler/Visitor.pxd +53 -0
- Cython/Compiler/Visitor.py +864 -0
- Cython/Compiler/__init__.py +1 -0
- Cython/Coverage.py +448 -0
- Cython/Debugger/Cygdb.py +177 -0
- Cython/Debugger/DebugWriter.py +82 -0
- Cython/Debugger/Tests/TestLibCython.py +280 -0
- Cython/Debugger/Tests/__init__.py +1 -0
- Cython/Debugger/Tests/cfuncs.c +8 -0
- Cython/Debugger/Tests/codefile +49 -0
- Cython/Debugger/Tests/test_libcython_in_gdb.py +580 -0
- Cython/Debugger/Tests/test_libpython_in_gdb.py +90 -0
- Cython/Debugger/__init__.py +1 -0
- Cython/Debugger/libcython.py +1548 -0
- Cython/Debugger/libpython.py +2821 -0
- Cython/Debugging.py +20 -0
- Cython/Distutils/__init__.py +2 -0
- Cython/Distutils/build_ext.py +139 -0
- Cython/Distutils/extension.py +96 -0
- Cython/Distutils/old_build_ext.py +351 -0
- Cython/Includes/cpython/__init__.pxd +173 -0
- Cython/Includes/cpython/array.pxd +152 -0
- Cython/Includes/cpython/bool.pxd +37 -0
- Cython/Includes/cpython/buffer.pxd +112 -0
- Cython/Includes/cpython/bytearray.pxd +33 -0
- Cython/Includes/cpython/bytes.pxd +200 -0
- Cython/Includes/cpython/cellobject.pxd +35 -0
- Cython/Includes/cpython/ceval.pxd +8 -0
- Cython/Includes/cpython/codecs.pxd +121 -0
- Cython/Includes/cpython/complex.pxd +60 -0
- Cython/Includes/cpython/contextvars.pxd +145 -0
- Cython/Includes/cpython/conversion.pxd +36 -0
- Cython/Includes/cpython/datetime.pxd +395 -0
- Cython/Includes/cpython/descr.pxd +26 -0
- Cython/Includes/cpython/dict.pxd +268 -0
- Cython/Includes/cpython/exc.pxd +263 -0
- Cython/Includes/cpython/fileobject.pxd +57 -0
- Cython/Includes/cpython/float.pxd +56 -0
- Cython/Includes/cpython/frozendict.pxd +37 -0
- Cython/Includes/cpython/function.pxd +65 -0
- Cython/Includes/cpython/genobject.pxd +25 -0
- Cython/Includes/cpython/getargs.pxd +12 -0
- Cython/Includes/cpython/instance.pxd +25 -0
- Cython/Includes/cpython/iterator.pxd +36 -0
- Cython/Includes/cpython/iterobject.pxd +24 -0
- Cython/Includes/cpython/list.pxd +144 -0
- Cython/Includes/cpython/long.pxd +180 -0
- Cython/Includes/cpython/longintrepr.pxd +14 -0
- Cython/Includes/cpython/mapping.pxd +63 -0
- Cython/Includes/cpython/marshal.pxd +66 -0
- Cython/Includes/cpython/mem.pxd +120 -0
- Cython/Includes/cpython/memoryview.pxd +50 -0
- Cython/Includes/cpython/method.pxd +49 -0
- Cython/Includes/cpython/module.pxd +208 -0
- Cython/Includes/cpython/number.pxd +258 -0
- Cython/Includes/cpython/object.pxd +430 -0
- Cython/Includes/cpython/pycapsule.pxd +143 -0
- Cython/Includes/cpython/pylifecycle.pxd +68 -0
- Cython/Includes/cpython/pyport.pxd +8 -0
- Cython/Includes/cpython/pystate.pxd +95 -0
- Cython/Includes/cpython/pythread.pxd +53 -0
- Cython/Includes/cpython/ref.pxd +141 -0
- Cython/Includes/cpython/sentinel.pxd +17 -0
- Cython/Includes/cpython/sequence.pxd +134 -0
- Cython/Includes/cpython/set.pxd +119 -0
- Cython/Includes/cpython/slice.pxd +70 -0
- Cython/Includes/cpython/time.pxd +129 -0
- Cython/Includes/cpython/tuple.pxd +72 -0
- Cython/Includes/cpython/type.pxd +146 -0
- Cython/Includes/cpython/unicode.pxd +639 -0
- Cython/Includes/cpython/version.pxd +32 -0
- Cython/Includes/cpython/weakref.pxd +78 -0
- Cython/Includes/libc/__init__.pxd +1 -0
- Cython/Includes/libc/complex.pxd +35 -0
- Cython/Includes/libc/errno.pxd +127 -0
- Cython/Includes/libc/float.pxd +43 -0
- Cython/Includes/libc/limits.pxd +28 -0
- Cython/Includes/libc/locale.pxd +46 -0
- Cython/Includes/libc/math.pxd +209 -0
- Cython/Includes/libc/setjmp.pxd +10 -0
- Cython/Includes/libc/signal.pxd +64 -0
- Cython/Includes/libc/stddef.pxd +9 -0
- Cython/Includes/libc/stdint.pxd +105 -0
- Cython/Includes/libc/stdio.pxd +80 -0
- Cython/Includes/libc/stdlib.pxd +72 -0
- Cython/Includes/libc/string.pxd +50 -0
- Cython/Includes/libc/threads.pxd +234 -0
- Cython/Includes/libc/time.pxd +52 -0
- Cython/Includes/libcpp/__init__.pxd +4 -0
- Cython/Includes/libcpp/algorithm.pxd +320 -0
- Cython/Includes/libcpp/any.pxd +16 -0
- Cython/Includes/libcpp/atomic.pxd +59 -0
- Cython/Includes/libcpp/barrier.pxd +22 -0
- Cython/Includes/libcpp/bit.pxd +29 -0
- Cython/Includes/libcpp/cast.pxd +12 -0
- Cython/Includes/libcpp/cmath.pxd +518 -0
- Cython/Includes/libcpp/complex.pxd +106 -0
- Cython/Includes/libcpp/condition_variable.pxd +322 -0
- Cython/Includes/libcpp/deque.pxd +165 -0
- Cython/Includes/libcpp/exception.pxd +216 -0
- Cython/Includes/libcpp/execution.pxd +15 -0
- Cython/Includes/libcpp/forward_list.pxd +63 -0
- Cython/Includes/libcpp/functional.pxd +26 -0
- Cython/Includes/libcpp/future.pxd +103 -0
- Cython/Includes/libcpp/iterator.pxd +34 -0
- Cython/Includes/libcpp/latch.pxd +17 -0
- Cython/Includes/libcpp/limits.pxd +61 -0
- Cython/Includes/libcpp/list.pxd +117 -0
- Cython/Includes/libcpp/map.pxd +252 -0
- Cython/Includes/libcpp/memory.pxd +115 -0
- Cython/Includes/libcpp/mutex.pxd +387 -0
- Cython/Includes/libcpp/numbers.pxd +15 -0
- Cython/Includes/libcpp/numeric.pxd +131 -0
- Cython/Includes/libcpp/optional.pxd +34 -0
- Cython/Includes/libcpp/pair.pxd +1 -0
- Cython/Includes/libcpp/queue.pxd +25 -0
- Cython/Includes/libcpp/random.pxd +166 -0
- Cython/Includes/libcpp/semaphore.pxd +43 -0
- Cython/Includes/libcpp/set.pxd +228 -0
- Cython/Includes/libcpp/shared_mutex.pxd +96 -0
- Cython/Includes/libcpp/span.pxd +87 -0
- Cython/Includes/libcpp/stack.pxd +11 -0
- Cython/Includes/libcpp/stop_token.pxd +117 -0
- Cython/Includes/libcpp/string.pxd +355 -0
- Cython/Includes/libcpp/string_view.pxd +183 -0
- Cython/Includes/libcpp/typeindex.pxd +15 -0
- Cython/Includes/libcpp/typeinfo.pxd +10 -0
- Cython/Includes/libcpp/unordered_map.pxd +193 -0
- Cython/Includes/libcpp/unordered_set.pxd +152 -0
- Cython/Includes/libcpp/utility.pxd +30 -0
- Cython/Includes/libcpp/vector.pxd +186 -0
- Cython/Includes/numpy/math.pxd +150 -0
- Cython/Includes/openmp.pxd +50 -0
- Cython/Includes/posix/__init__.pxd +1 -0
- Cython/Includes/posix/dlfcn.pxd +14 -0
- Cython/Includes/posix/fcntl.pxd +86 -0
- Cython/Includes/posix/ioctl.pxd +4 -0
- Cython/Includes/posix/mman.pxd +101 -0
- Cython/Includes/posix/resource.pxd +57 -0
- Cython/Includes/posix/select.pxd +21 -0
- Cython/Includes/posix/signal.pxd +73 -0
- Cython/Includes/posix/stat.pxd +98 -0
- Cython/Includes/posix/stdio.pxd +37 -0
- Cython/Includes/posix/stdlib.pxd +29 -0
- Cython/Includes/posix/strings.pxd +9 -0
- Cython/Includes/posix/time.pxd +71 -0
- Cython/Includes/posix/types.pxd +30 -0
- Cython/Includes/posix/uio.pxd +26 -0
- Cython/Includes/posix/unistd.pxd +271 -0
- Cython/Includes/posix/wait.pxd +38 -0
- Cython/LZSS.py +170 -0
- Cython/Plex/Actions.cp315-win_amd64.pyd +0 -0
- Cython/Plex/Actions.pxd +24 -0
- Cython/Plex/Actions.py +119 -0
- Cython/Plex/DFA.cp315-win_amd64.pyd +0 -0
- Cython/Plex/DFA.pxd +14 -0
- Cython/Plex/DFA.py +164 -0
- Cython/Plex/Errors.py +48 -0
- Cython/Plex/Lexicons.py +178 -0
- Cython/Plex/Machines.cp315-win_amd64.pyd +0 -0
- Cython/Plex/Machines.pxd +36 -0
- Cython/Plex/Machines.py +238 -0
- Cython/Plex/Regexps.py +535 -0
- Cython/Plex/Scanners.cp315-win_amd64.pyd +0 -0
- Cython/Plex/Scanners.pxd +45 -0
- Cython/Plex/Scanners.py +328 -0
- Cython/Plex/Transitions.cp315-win_amd64.pyd +0 -0
- Cython/Plex/Transitions.pxd +14 -0
- Cython/Plex/Transitions.py +239 -0
- Cython/Plex/__init__.py +34 -0
- Cython/Runtime/__init__.py +1 -0
- Cython/Runtime/refnanny.cp315-win_amd64.pyd +0 -0
- Cython/Runtime/refnanny.pyx +237 -0
- Cython/Shadow.py +1167 -0
- Cython/StringIOTree.cp315-win_amd64.pyd +0 -0
- Cython/StringIOTree.py +169 -0
- Cython/Tempita/__init__.py +4 -0
- Cython/Tempita/_looper.py +154 -0
- Cython/Tempita/_tempita.cp315-win_amd64.pyd +0 -0
- Cython/Tempita/_tempita.py +1087 -0
- Cython/TestUtils.py +464 -0
- Cython/Tests/TestCodeWriter.py +128 -0
- Cython/Tests/TestCythonUtils.py +202 -0
- Cython/Tests/TestJediTyper.py +223 -0
- Cython/Tests/TestShadow.py +110 -0
- Cython/Tests/TestStringIOTree.py +68 -0
- Cython/Tests/TestTestUtils.py +89 -0
- Cython/Tests/__init__.py +1 -0
- Cython/Tests/xmlrunner.py +390 -0
- Cython/Utility/AsyncGen.c +1073 -0
- Cython/Utility/Buffer.c +866 -0
- Cython/Utility/BufferFormatFromTypeInfo.pxd +2 -0
- Cython/Utility/Builtins.c +933 -0
- Cython/Utility/CConvert.pyx +149 -0
- Cython/Utility/CMath.c +104 -0
- Cython/Utility/CommonStructures.c +244 -0
- Cython/Utility/Complex.c +378 -0
- Cython/Utility/Coroutine.c +2337 -0
- Cython/Utility/CpdefEnums.pyx +107 -0
- Cython/Utility/CppConvert.pyx +282 -0
- Cython/Utility/CppSupport.cpp +151 -0
- Cython/Utility/CythonFunction.c +2072 -0
- Cython/Utility/Dataclasses.c +101 -0
- Cython/Utility/Embed.c +129 -0
- Cython/Utility/Exceptions.c +1038 -0
- Cython/Utility/ExtensionTypes.c +1158 -0
- Cython/Utility/FunctionArguments.c +1045 -0
- Cython/Utility/FusedFunction.pyx +44 -0
- Cython/Utility/ImportExport.c +930 -0
- Cython/Utility/MatchCase.c +979 -0
- Cython/Utility/MatchCase_Cy.pyx +12 -0
- Cython/Utility/MemoryView.pxd +108 -0
- Cython/Utility/MemoryView.pyx +1499 -0
- Cython/Utility/MemoryView_C.c +1056 -0
- Cython/Utility/ModuleSetupCode.c +3352 -0
- Cython/Utility/NumpyImportArray.c +46 -0
- Cython/Utility/ObjectHandling.c +3372 -0
- Cython/Utility/Optimize.c +2563 -0
- Cython/Utility/Overflow.c +378 -0
- Cython/Utility/Profile.c +736 -0
- Cython/Utility/StringTools.c +1414 -0
- Cython/Utility/Synchronization.c +438 -0
- Cython/Utility/TString.c +369 -0
- Cython/Utility/TestCyUtilityLoader.pyx +8 -0
- Cython/Utility/TestCythonScope.pyx +75 -0
- Cython/Utility/TestUtilityLoader.c +12 -0
- Cython/Utility/TypeConversion.c +1556 -0
- Cython/Utility/UFuncs.pyx +50 -0
- Cython/Utility/UFuncs_C.c +89 -0
- Cython/Utility/__init__.py +28 -0
- Cython/Utility/arrayarray.h +172 -0
- Cython/Utils.cp315-win_amd64.pyd +0 -0
- Cython/Utils.py +677 -0
- Cython/__init__.py +12 -0
- Cython/py.typed +0 -0
- cython-3.3.0a1.dist-info/METADATA +394 -0
- cython-3.3.0a1.dist-info/RECORD +335 -0
- cython-3.3.0a1.dist-info/WHEEL +5 -0
- cython-3.3.0a1.dist-info/entry_points.txt +4 -0
- cython-3.3.0a1.dist-info/top_level.txt +3 -0
- cython.py +29 -0
- pyximport/__init__.py +4 -0
- pyximport/pyxbuild.py +160 -0
- pyximport/pyximport.py +482 -0
|
@@ -0,0 +1,1276 @@
|
|
|
1
|
+
import cython
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import os
|
|
5
|
+
import re, sys, time
|
|
6
|
+
from glob import iglob
|
|
7
|
+
from io import StringIO
|
|
8
|
+
from os.path import relpath as _relpath
|
|
9
|
+
from .Cache import Cache, FingerprintFlags
|
|
10
|
+
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import pythran
|
|
15
|
+
except:
|
|
16
|
+
pythran = None
|
|
17
|
+
|
|
18
|
+
from .. import Utils
|
|
19
|
+
from ..Utils import (cached_function, cached_method, path_exists,
|
|
20
|
+
safe_makedirs, copy_file_to_dir_if_newer, is_package_dir, write_depfile)
|
|
21
|
+
from ..Compiler import Errors
|
|
22
|
+
from ..Compiler.Main import Context
|
|
23
|
+
from ..Compiler import Options
|
|
24
|
+
from ..Compiler.Options import (CompilationOptions, default_options,
|
|
25
|
+
get_directive_defaults)
|
|
26
|
+
|
|
27
|
+
join_path = cached_function(os.path.join)
|
|
28
|
+
copy_once_if_newer = cached_function(copy_file_to_dir_if_newer)
|
|
29
|
+
safe_makedirs_once = cached_function(safe_makedirs)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _make_relative(file_paths, base=None):
|
|
33
|
+
if not base:
|
|
34
|
+
base = os.getcwd()
|
|
35
|
+
if base[-1] != os.path.sep:
|
|
36
|
+
base += os.path.sep
|
|
37
|
+
return [_relpath(path, base) if path.startswith(base) else path
|
|
38
|
+
for path in file_paths]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def extended_iglob(pattern):
|
|
42
|
+
if '{' in pattern:
|
|
43
|
+
m = re.match('(.*){([^}]+)}(.*)', pattern)
|
|
44
|
+
if m:
|
|
45
|
+
before, switch, after = m.groups()
|
|
46
|
+
for case in switch.split(','):
|
|
47
|
+
for path in extended_iglob(before + case + after):
|
|
48
|
+
yield path
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
# We always accept '/' and also '\' on Windows,
|
|
52
|
+
# because '/' is generally common for relative paths.
|
|
53
|
+
if '**/' in pattern or os.sep == '\\' and '**\\' in pattern:
|
|
54
|
+
seen = set()
|
|
55
|
+
first, rest = re.split(r'\*\*[%s]' % ('/\\\\' if os.sep == '\\' else '/'), pattern, maxsplit=1)
|
|
56
|
+
if first:
|
|
57
|
+
first = iglob(first + os.sep)
|
|
58
|
+
else:
|
|
59
|
+
first = ['']
|
|
60
|
+
for root in first:
|
|
61
|
+
for path in extended_iglob(join_path(root, rest)):
|
|
62
|
+
if path not in seen:
|
|
63
|
+
seen.add(path)
|
|
64
|
+
yield path
|
|
65
|
+
for path in extended_iglob(join_path(root, '*', '**', rest)):
|
|
66
|
+
if path not in seen:
|
|
67
|
+
seen.add(path)
|
|
68
|
+
yield path
|
|
69
|
+
else:
|
|
70
|
+
for path in iglob(pattern):
|
|
71
|
+
yield path
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def nonempty(it, error_msg="expected non-empty iterator"):
|
|
75
|
+
empty = True
|
|
76
|
+
for value in it:
|
|
77
|
+
empty = False
|
|
78
|
+
yield value
|
|
79
|
+
if empty:
|
|
80
|
+
raise ValueError(error_msg)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def update_pythran_extension(ext):
|
|
84
|
+
if pythran is None:
|
|
85
|
+
raise RuntimeError("You first need to install Pythran to use the np_pythran directive.")
|
|
86
|
+
try:
|
|
87
|
+
pythran_ext = pythran.config.make_extension(python=True)
|
|
88
|
+
except TypeError: # older pythran version only
|
|
89
|
+
pythran_ext = pythran.config.make_extension()
|
|
90
|
+
|
|
91
|
+
ext.include_dirs.extend(pythran_ext['include_dirs'])
|
|
92
|
+
ext.extra_compile_args.extend(pythran_ext['extra_compile_args'])
|
|
93
|
+
ext.extra_link_args.extend(pythran_ext['extra_link_args'])
|
|
94
|
+
ext.define_macros.extend(pythran_ext['define_macros'])
|
|
95
|
+
ext.undef_macros.extend(pythran_ext['undef_macros'])
|
|
96
|
+
ext.library_dirs.extend(pythran_ext['library_dirs'])
|
|
97
|
+
ext.libraries.extend(pythran_ext['libraries'])
|
|
98
|
+
ext.language = 'c++'
|
|
99
|
+
|
|
100
|
+
# These options are not compatible with the way normal Cython extensions work
|
|
101
|
+
for bad_option in ["-fwhole-program", "-fvisibility=hidden"]:
|
|
102
|
+
try:
|
|
103
|
+
ext.extra_compile_args.remove(bad_option)
|
|
104
|
+
except ValueError:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def parse_list(s):
|
|
109
|
+
"""
|
|
110
|
+
>>> parse_list("")
|
|
111
|
+
[]
|
|
112
|
+
>>> parse_list("a")
|
|
113
|
+
['a']
|
|
114
|
+
>>> parse_list("a b c")
|
|
115
|
+
['a', 'b', 'c']
|
|
116
|
+
>>> parse_list("[a, b, c]")
|
|
117
|
+
['a', 'b', 'c']
|
|
118
|
+
>>> parse_list('a " " b')
|
|
119
|
+
['a', ' ', 'b']
|
|
120
|
+
>>> parse_list('[a, ",a", "a,", ",", ]')
|
|
121
|
+
['a', ',a', 'a,', ',']
|
|
122
|
+
"""
|
|
123
|
+
if len(s) >= 2 and s[0] == '[' and s[-1] == ']':
|
|
124
|
+
s = s[1:-1]
|
|
125
|
+
delimiter = ','
|
|
126
|
+
else:
|
|
127
|
+
delimiter = ' '
|
|
128
|
+
s, literals = strip_string_literals(s)
|
|
129
|
+
def unquote(literal):
|
|
130
|
+
literal = literal.strip()
|
|
131
|
+
if literal[0] in "'\"":
|
|
132
|
+
return literals[literal[1:-1]]
|
|
133
|
+
else:
|
|
134
|
+
return literal
|
|
135
|
+
return [unquote(item) for item in s.split(delimiter) if item.strip()]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
transitive_str = object()
|
|
139
|
+
transitive_list = object()
|
|
140
|
+
bool_or = object()
|
|
141
|
+
|
|
142
|
+
distutils_settings = {
|
|
143
|
+
'name': str,
|
|
144
|
+
'sources': list,
|
|
145
|
+
'define_macros': list,
|
|
146
|
+
'undef_macros': list,
|
|
147
|
+
'libraries': transitive_list,
|
|
148
|
+
'library_dirs': transitive_list,
|
|
149
|
+
'runtime_library_dirs': transitive_list,
|
|
150
|
+
'include_dirs': transitive_list,
|
|
151
|
+
'extra_objects': list,
|
|
152
|
+
'extra_compile_args': transitive_list,
|
|
153
|
+
'extra_link_args': transitive_list,
|
|
154
|
+
'export_symbols': list,
|
|
155
|
+
'depends': transitive_list,
|
|
156
|
+
'language': transitive_str,
|
|
157
|
+
'np_pythran': bool_or
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _legacy_strtobool(val):
|
|
162
|
+
# Used to be "distutils.util.strtobool", adapted for deprecation warnings.
|
|
163
|
+
if val == "True":
|
|
164
|
+
return True
|
|
165
|
+
elif val == "False":
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
import warnings
|
|
169
|
+
warnings.warn("The 'np_python' option requires 'True' or 'False'", category=DeprecationWarning)
|
|
170
|
+
val = val.lower()
|
|
171
|
+
if val in ('y', 'yes', 't', 'true', 'on', '1'):
|
|
172
|
+
return True
|
|
173
|
+
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
|
|
174
|
+
return False
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError("invalid truth value %r" % (val,))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class DistutilsInfo:
|
|
180
|
+
|
|
181
|
+
def __init__(self, source=None, exn=None):
|
|
182
|
+
self.values = {}
|
|
183
|
+
if source is not None:
|
|
184
|
+
source_lines = StringIO(source) if isinstance(source, str) else source
|
|
185
|
+
for line in source_lines:
|
|
186
|
+
line = line.lstrip()
|
|
187
|
+
if not line:
|
|
188
|
+
continue
|
|
189
|
+
if line[0] != '#':
|
|
190
|
+
break
|
|
191
|
+
line = line[1:].lstrip()
|
|
192
|
+
kind = next((k for k in ("distutils:","cython:") if line.startswith(k)), None)
|
|
193
|
+
if kind is not None:
|
|
194
|
+
key, _, value = [s.strip() for s in line[len(kind):].partition('=')]
|
|
195
|
+
type = distutils_settings.get(key, None)
|
|
196
|
+
if line.startswith("cython:") and type is None: continue
|
|
197
|
+
if type in (list, transitive_list):
|
|
198
|
+
value = parse_list(value)
|
|
199
|
+
if key == 'define_macros':
|
|
200
|
+
value = [tuple(macro.split('=', 1))
|
|
201
|
+
if '=' in macro else (macro, None)
|
|
202
|
+
for macro in value]
|
|
203
|
+
if type is bool_or:
|
|
204
|
+
value = _legacy_strtobool(value)
|
|
205
|
+
self.values[key] = value
|
|
206
|
+
elif exn is not None:
|
|
207
|
+
for key in distutils_settings:
|
|
208
|
+
if key in ('name', 'sources','np_pythran'):
|
|
209
|
+
continue
|
|
210
|
+
value = getattr(exn, key, None)
|
|
211
|
+
if value:
|
|
212
|
+
self.values[key] = value
|
|
213
|
+
|
|
214
|
+
def merge(self, other):
|
|
215
|
+
if other is None:
|
|
216
|
+
return self
|
|
217
|
+
for key, value in other.values.items():
|
|
218
|
+
type = distutils_settings[key]
|
|
219
|
+
if type is transitive_str and key not in self.values:
|
|
220
|
+
self.values[key] = value
|
|
221
|
+
elif type is transitive_list:
|
|
222
|
+
if key in self.values:
|
|
223
|
+
# Change a *copy* of the list (Trac #845)
|
|
224
|
+
all = self.values[key][:]
|
|
225
|
+
for v in value:
|
|
226
|
+
if v not in all:
|
|
227
|
+
all.append(v)
|
|
228
|
+
value = all
|
|
229
|
+
self.values[key] = value
|
|
230
|
+
elif type is bool_or:
|
|
231
|
+
self.values[key] = self.values.get(key, False) | value
|
|
232
|
+
return self
|
|
233
|
+
|
|
234
|
+
def subs(self, aliases):
|
|
235
|
+
if aliases is None:
|
|
236
|
+
return self
|
|
237
|
+
resolved = DistutilsInfo()
|
|
238
|
+
for key, value in self.values.items():
|
|
239
|
+
type = distutils_settings[key]
|
|
240
|
+
if type in [list, transitive_list]:
|
|
241
|
+
new_value_list = []
|
|
242
|
+
for v in value:
|
|
243
|
+
if v in aliases:
|
|
244
|
+
v = aliases[v]
|
|
245
|
+
if isinstance(v, list):
|
|
246
|
+
new_value_list += v
|
|
247
|
+
else:
|
|
248
|
+
new_value_list.append(v)
|
|
249
|
+
value = new_value_list
|
|
250
|
+
else:
|
|
251
|
+
if value in aliases:
|
|
252
|
+
value = aliases[value]
|
|
253
|
+
resolved.values[key] = value
|
|
254
|
+
return resolved
|
|
255
|
+
|
|
256
|
+
def apply(self, extension):
|
|
257
|
+
for key, value in self.values.items():
|
|
258
|
+
type = distutils_settings[key]
|
|
259
|
+
if type in [list, transitive_list]:
|
|
260
|
+
value = getattr(extension, key) + list(value)
|
|
261
|
+
setattr(extension, key, value)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
_FIND_TOKEN = cython.declare(object, re.compile(r"""
|
|
265
|
+
(?P<comment> [#] ) |
|
|
266
|
+
(?P<brace> [{}] ) |
|
|
267
|
+
(?P<fstring> f )? (?P<quote> '+ | "+ )
|
|
268
|
+
""", re.VERBOSE).search)
|
|
269
|
+
|
|
270
|
+
_FIND_STRING_TOKEN = cython.declare(object, re.compile(r"""
|
|
271
|
+
(?P<escape> [\\]+ ) (?P<escaped_quote> ['"] ) |
|
|
272
|
+
(?P<fstring> f )? (?P<quote> '+ | "+ )
|
|
273
|
+
""", re.VERBOSE).search)
|
|
274
|
+
|
|
275
|
+
_FIND_FSTRING_TOKEN = cython.declare(object, re.compile(r"""
|
|
276
|
+
(?P<braces> [{]+ | [}]+ ) |
|
|
277
|
+
(?P<escape> [\\]+ ) (?P<escaped_quote> ['"] ) |
|
|
278
|
+
(?P<fstring> f )? (?P<quote> '+ | "+ )
|
|
279
|
+
""", re.VERBOSE).search)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def strip_string_literals(code: str, prefix: str = '__Pyx_L'):
|
|
283
|
+
"""
|
|
284
|
+
Normalizes every string literal to be of the form '__Pyx_Lxxx',
|
|
285
|
+
returning the normalized code and a mapping of labels to
|
|
286
|
+
string literals.
|
|
287
|
+
"""
|
|
288
|
+
new_code: list = []
|
|
289
|
+
literals: dict = {}
|
|
290
|
+
counter: cython.Py_ssize_t = 0
|
|
291
|
+
find_token = _FIND_TOKEN
|
|
292
|
+
|
|
293
|
+
def append_new_label(literal):
|
|
294
|
+
nonlocal counter
|
|
295
|
+
counter += 1
|
|
296
|
+
label = f"{prefix}{counter}_"
|
|
297
|
+
literals[label] = literal
|
|
298
|
+
new_code.append(label)
|
|
299
|
+
|
|
300
|
+
def parse_string(quote_type: str, start: cython.Py_ssize_t, is_fstring: cython.bint) -> cython.Py_ssize_t:
|
|
301
|
+
charpos: cython.Py_ssize_t = start
|
|
302
|
+
|
|
303
|
+
find_token = _FIND_FSTRING_TOKEN if is_fstring else _FIND_STRING_TOKEN
|
|
304
|
+
|
|
305
|
+
while charpos != -1:
|
|
306
|
+
token = find_token(code, charpos)
|
|
307
|
+
if token is None:
|
|
308
|
+
# This probably indicates an unclosed string literal, i.e. a broken file.
|
|
309
|
+
append_new_label(code[start:])
|
|
310
|
+
charpos = -1
|
|
311
|
+
break
|
|
312
|
+
charpos = token.end()
|
|
313
|
+
|
|
314
|
+
if token['escape']:
|
|
315
|
+
if len(token['escape']) % 2 == 0 and token['escaped_quote'] == quote_type[0]:
|
|
316
|
+
# Quote is not actually escaped and might be part of a terminator, look at it next.
|
|
317
|
+
charpos -= 1
|
|
318
|
+
|
|
319
|
+
elif is_fstring and token['braces']:
|
|
320
|
+
# Formats or brace(s) in fstring.
|
|
321
|
+
if len(token['braces']) % 2 == 0:
|
|
322
|
+
# Normal brace characters in string.
|
|
323
|
+
continue
|
|
324
|
+
if token['braces'][-1] == '{':
|
|
325
|
+
if start < charpos-1:
|
|
326
|
+
append_new_label(code[start : charpos-1])
|
|
327
|
+
new_code.append('{')
|
|
328
|
+
start = charpos = parse_code(charpos, in_fstring=True)
|
|
329
|
+
|
|
330
|
+
elif token['quote'].startswith(quote_type):
|
|
331
|
+
# Closing quote found (potentially together with further, unrelated quotes).
|
|
332
|
+
charpos = token.start('quote')
|
|
333
|
+
if charpos > start:
|
|
334
|
+
append_new_label(code[start : charpos])
|
|
335
|
+
new_code.append(quote_type)
|
|
336
|
+
charpos += len(quote_type)
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
return charpos
|
|
340
|
+
|
|
341
|
+
def parse_code(start: cython.Py_ssize_t, in_fstring: cython.bint = False) -> cython.Py_ssize_t:
|
|
342
|
+
charpos: cython.Py_ssize_t = start
|
|
343
|
+
end: cython.Py_ssize_t
|
|
344
|
+
quote: str
|
|
345
|
+
|
|
346
|
+
while charpos != -1:
|
|
347
|
+
token = find_token(code, charpos)
|
|
348
|
+
if token is None:
|
|
349
|
+
new_code.append(code[start:])
|
|
350
|
+
charpos = -1
|
|
351
|
+
break
|
|
352
|
+
charpos = end = token.end()
|
|
353
|
+
|
|
354
|
+
if token['quote']:
|
|
355
|
+
quote = token['quote']
|
|
356
|
+
if len(quote) >= 6:
|
|
357
|
+
# Ignore empty tripple-quoted strings: '''''' or """"""
|
|
358
|
+
quote = quote[:len(quote) % 6]
|
|
359
|
+
if quote and len(quote) != 2:
|
|
360
|
+
if len(quote) > 3:
|
|
361
|
+
end -= len(quote) - 3
|
|
362
|
+
quote = quote[:3]
|
|
363
|
+
new_code.append(code[start:end])
|
|
364
|
+
start = charpos = parse_string(quote, end, is_fstring=token['fstring'])
|
|
365
|
+
|
|
366
|
+
elif token['comment']:
|
|
367
|
+
new_code.append(code[start:end])
|
|
368
|
+
charpos = code.find('\n', end)
|
|
369
|
+
append_new_label(code[end : charpos if charpos != -1 else None])
|
|
370
|
+
if charpos == -1:
|
|
371
|
+
break # EOF
|
|
372
|
+
start = charpos
|
|
373
|
+
|
|
374
|
+
elif in_fstring and token['brace']:
|
|
375
|
+
if token['brace'] == '}':
|
|
376
|
+
# Closing '}' of f-string.
|
|
377
|
+
charpos = end = token.start() + 1
|
|
378
|
+
new_code.append(code[start:end]) # with '}'
|
|
379
|
+
break
|
|
380
|
+
else:
|
|
381
|
+
# Starting a calculated format modifier inside of an f-string format.
|
|
382
|
+
end = token.start() + 1
|
|
383
|
+
new_code.append(code[start:end]) # with '{'
|
|
384
|
+
start = charpos = parse_code(end, in_fstring=True)
|
|
385
|
+
|
|
386
|
+
return charpos
|
|
387
|
+
|
|
388
|
+
parse_code(0)
|
|
389
|
+
return "".join(new_code), literals
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# We need to allow spaces to allow for conditional compilation like
|
|
393
|
+
# IF ...:
|
|
394
|
+
# cimport ...
|
|
395
|
+
dependency_regex = re.compile(
|
|
396
|
+
r"(?:^ [ \t\f]* from [ \t\f]+ cython\.cimports\.([\w.]+) [ \t\f]+ c?import ) |"
|
|
397
|
+
r"(?:^ [ \t\f]* from [ \t\f]+ ([\w.]+) [ \t\f]+ cimport ) |"
|
|
398
|
+
r"(?:^ [ \t\f]* c?import [ \t\f]+ cython\.cimports\.([\w.]+) ) |"
|
|
399
|
+
r"(?:^ [ \t\f]* cimport [ \t\f]+ ([\w.]+ (?:[ \t\f]* , [ \t\f]* [\w.]+)*) ) |"
|
|
400
|
+
r"(?:^ [ \t\f]* cdef [ \t\f]+ extern [ \t\f]+ from [ \t\f]+ ['\"] ([^'\"]+) ['\"] ) |"
|
|
401
|
+
r"(?:^ [ \t\f]* include [ \t\f]+ ['\"] ([^'\"]+) ['\"] )",
|
|
402
|
+
re.MULTILINE | re.VERBOSE)
|
|
403
|
+
dependency_after_from_regex = re.compile(
|
|
404
|
+
r"(?:^ [ \t\f]+ \( ([\w., \t\f]*) \) [ \t\f]* [#\n]) |"
|
|
405
|
+
r"(?:^ [ \t\f]+ ([\w., \t\f]*) [ \t\f]* [#\n])",
|
|
406
|
+
re.MULTILINE | re.VERBOSE)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def normalize_existing(base_path, rel_paths):
|
|
410
|
+
return normalize_existing0(os.path.dirname(base_path), tuple(set(rel_paths)))
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@cached_function
|
|
414
|
+
def normalize_existing0(base_dir, rel_paths):
|
|
415
|
+
"""
|
|
416
|
+
Given some base directory ``base_dir`` and a list of path names
|
|
417
|
+
``rel_paths``, normalize each relative path name ``rel`` by
|
|
418
|
+
replacing it by ``os.path.join(base, rel)`` if that file exists.
|
|
419
|
+
|
|
420
|
+
Return a couple ``(normalized, needed_base)`` where ``normalized``
|
|
421
|
+
if the list of normalized file names and ``needed_base`` is
|
|
422
|
+
``base_dir`` if we actually needed ``base_dir``. If no paths were
|
|
423
|
+
changed (for example, if all paths were already absolute), then
|
|
424
|
+
``needed_base`` is ``None``.
|
|
425
|
+
"""
|
|
426
|
+
normalized = []
|
|
427
|
+
needed_base = None
|
|
428
|
+
for rel in rel_paths:
|
|
429
|
+
if os.path.isabs(rel):
|
|
430
|
+
normalized.append(rel)
|
|
431
|
+
continue
|
|
432
|
+
path = join_path(base_dir, rel)
|
|
433
|
+
if path_exists(path):
|
|
434
|
+
normalized.append(os.path.normpath(path))
|
|
435
|
+
needed_base = base_dir
|
|
436
|
+
else:
|
|
437
|
+
normalized.append(rel)
|
|
438
|
+
return (normalized, needed_base)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def resolve_depends(depends, include_dirs):
|
|
442
|
+
include_dirs = tuple(include_dirs)
|
|
443
|
+
resolved = []
|
|
444
|
+
for depend in depends:
|
|
445
|
+
path = resolve_depend(depend, include_dirs)
|
|
446
|
+
if path is not None:
|
|
447
|
+
resolved.append(path)
|
|
448
|
+
return resolved
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@cached_function
|
|
452
|
+
def resolve_depend(depend, include_dirs):
|
|
453
|
+
if depend[0] == '<' and depend[-1] == '>':
|
|
454
|
+
return None
|
|
455
|
+
for dir in include_dirs:
|
|
456
|
+
path = join_path(dir, depend)
|
|
457
|
+
if path_exists(path):
|
|
458
|
+
return os.path.normpath(path)
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@cached_function
|
|
463
|
+
def package(filename):
|
|
464
|
+
dir = os.path.dirname(os.path.abspath(str(filename)))
|
|
465
|
+
if dir != filename and is_package_dir(dir):
|
|
466
|
+
return package(dir) + (os.path.basename(dir),)
|
|
467
|
+
else:
|
|
468
|
+
return ()
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@cached_function
|
|
472
|
+
def fully_qualified_name(filename):
|
|
473
|
+
module = os.path.splitext(os.path.basename(filename))[0]
|
|
474
|
+
return '.'.join(package(filename) + (module,))
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@cached_function
|
|
478
|
+
def parse_dependencies(source_filename):
|
|
479
|
+
# Actual parsing is way too slow, so we use regular expressions.
|
|
480
|
+
# The only catch is that we must strip comments and string
|
|
481
|
+
# literals ahead of time.
|
|
482
|
+
with Utils.open_source_file(source_filename, error_handling='ignore') as fh:
|
|
483
|
+
source = fh.read()
|
|
484
|
+
distutils_info = DistutilsInfo(source)
|
|
485
|
+
source, literals = strip_string_literals(source)
|
|
486
|
+
source = source.replace('\\\n', ' ').replace('\t', ' ')
|
|
487
|
+
|
|
488
|
+
# TODO: pure mode
|
|
489
|
+
cimports = []
|
|
490
|
+
includes = []
|
|
491
|
+
externs = []
|
|
492
|
+
for m in dependency_regex.finditer(source):
|
|
493
|
+
pycimports_from, cimport_from, pycimports_list, cimport_list, extern, include = m.groups()
|
|
494
|
+
if pycimports_from:
|
|
495
|
+
cimport_from = pycimports_from
|
|
496
|
+
if pycimports_list:
|
|
497
|
+
cimport_list = pycimports_list
|
|
498
|
+
|
|
499
|
+
if cimport_from:
|
|
500
|
+
cimports.append(cimport_from)
|
|
501
|
+
m_after_from = dependency_after_from_regex.search(source, pos=m.end())
|
|
502
|
+
if m_after_from:
|
|
503
|
+
multiline, one_line = m_after_from.groups()
|
|
504
|
+
subimports = multiline or one_line
|
|
505
|
+
cimports.extend("{}.{}".format(cimport_from, s.strip())
|
|
506
|
+
for s in subimports.split(','))
|
|
507
|
+
|
|
508
|
+
elif cimport_list:
|
|
509
|
+
cimports.extend(x.strip() for x in cimport_list.split(","))
|
|
510
|
+
elif extern:
|
|
511
|
+
externs.append(literals[extern])
|
|
512
|
+
else:
|
|
513
|
+
includes.append(literals[include])
|
|
514
|
+
return cimports, includes, externs, distutils_info
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
class DependencyTree:
|
|
518
|
+
|
|
519
|
+
def __init__(self, context, quiet=False):
|
|
520
|
+
self.context = context
|
|
521
|
+
self.quiet = quiet
|
|
522
|
+
self._transitive_cache = {}
|
|
523
|
+
|
|
524
|
+
def parse_dependencies(self, source_filename):
|
|
525
|
+
if path_exists(source_filename):
|
|
526
|
+
source_filename = os.path.normpath(source_filename)
|
|
527
|
+
return parse_dependencies(source_filename)
|
|
528
|
+
|
|
529
|
+
@cached_method
|
|
530
|
+
def included_files(self, filename):
|
|
531
|
+
# This is messy because included files are textually included, resolving
|
|
532
|
+
# cimports (but not includes) relative to the including file.
|
|
533
|
+
all = set()
|
|
534
|
+
for include in self.parse_dependencies(filename)[1]:
|
|
535
|
+
include_path = join_path(os.path.dirname(filename), include)
|
|
536
|
+
if not path_exists(include_path):
|
|
537
|
+
include_path = self.context.find_include_file(include, source_file_path=filename)
|
|
538
|
+
if include_path:
|
|
539
|
+
if '.' + os.path.sep in include_path:
|
|
540
|
+
include_path = os.path.normpath(include_path)
|
|
541
|
+
all.add(include_path)
|
|
542
|
+
all.update(self.included_files(include_path))
|
|
543
|
+
elif not self.quiet:
|
|
544
|
+
print("Unable to locate '%s' referenced from '%s'" % (filename, include))
|
|
545
|
+
return all
|
|
546
|
+
|
|
547
|
+
@cached_method
|
|
548
|
+
def cimports_externs_incdirs(self, filename):
|
|
549
|
+
# This is really ugly. Nested cimports are resolved with respect to the
|
|
550
|
+
# includer, but includes are resolved with respect to the includee.
|
|
551
|
+
cimports, includes, externs = self.parse_dependencies(filename)[:3]
|
|
552
|
+
cimports = set(cimports)
|
|
553
|
+
externs = set(externs)
|
|
554
|
+
incdirs = set()
|
|
555
|
+
for include in self.included_files(filename):
|
|
556
|
+
included_cimports, included_externs, included_incdirs = self.cimports_externs_incdirs(include)
|
|
557
|
+
cimports.update(included_cimports)
|
|
558
|
+
externs.update(included_externs)
|
|
559
|
+
incdirs.update(included_incdirs)
|
|
560
|
+
externs, incdir = normalize_existing(filename, externs)
|
|
561
|
+
if incdir:
|
|
562
|
+
incdirs.add(incdir)
|
|
563
|
+
return tuple(cimports), externs, incdirs
|
|
564
|
+
|
|
565
|
+
def cimports(self, filename):
|
|
566
|
+
return self.cimports_externs_incdirs(filename)[0]
|
|
567
|
+
|
|
568
|
+
def package(self, filename):
|
|
569
|
+
return package(filename)
|
|
570
|
+
|
|
571
|
+
def fully_qualified_name(self, filename):
|
|
572
|
+
return fully_qualified_name(filename)
|
|
573
|
+
|
|
574
|
+
@cached_method
|
|
575
|
+
def find_pxd(self, module, filename=None):
|
|
576
|
+
is_relative = module[0] == '.'
|
|
577
|
+
if is_relative and not filename:
|
|
578
|
+
raise NotImplementedError("New relative imports.")
|
|
579
|
+
if filename is not None:
|
|
580
|
+
module_path = module.split('.')
|
|
581
|
+
if is_relative:
|
|
582
|
+
module_path.pop(0) # just explicitly relative
|
|
583
|
+
package_path = list(self.package(filename))
|
|
584
|
+
while module_path and not module_path[0]:
|
|
585
|
+
try:
|
|
586
|
+
package_path.pop()
|
|
587
|
+
except IndexError:
|
|
588
|
+
return None # FIXME: error?
|
|
589
|
+
module_path.pop(0)
|
|
590
|
+
relative = '.'.join(package_path + module_path)
|
|
591
|
+
pxd = self.context.find_pxd_file(relative, source_file_path=filename)
|
|
592
|
+
if pxd:
|
|
593
|
+
return pxd
|
|
594
|
+
if is_relative:
|
|
595
|
+
return None # FIXME: error?
|
|
596
|
+
return self.context.find_pxd_file(module, source_file_path=filename)
|
|
597
|
+
|
|
598
|
+
@cached_method
|
|
599
|
+
def cimported_files(self, filename):
|
|
600
|
+
filename_root, filename_ext = os.path.splitext(filename)
|
|
601
|
+
if filename_ext in ('.pyx', '.py') and path_exists(filename_root + '.pxd'):
|
|
602
|
+
pxd_list = [filename_root + '.pxd']
|
|
603
|
+
else:
|
|
604
|
+
pxd_list = []
|
|
605
|
+
# Cimports generates all possible combinations package.module
|
|
606
|
+
# when imported as from package cimport module.
|
|
607
|
+
for module in self.cimports(filename):
|
|
608
|
+
if module[:7] == 'cython.' or module == 'cython':
|
|
609
|
+
continue
|
|
610
|
+
pxd_file = self.find_pxd(module, filename)
|
|
611
|
+
if pxd_file is not None:
|
|
612
|
+
pxd_list.append(pxd_file)
|
|
613
|
+
return tuple(pxd_list)
|
|
614
|
+
|
|
615
|
+
@cached_method
|
|
616
|
+
def immediate_dependencies(self, filename):
|
|
617
|
+
all_deps = {filename}
|
|
618
|
+
all_deps.update(self.cimported_files(filename))
|
|
619
|
+
all_deps.update(self.included_files(filename))
|
|
620
|
+
return all_deps
|
|
621
|
+
|
|
622
|
+
def all_dependencies(self, filename):
|
|
623
|
+
return self.transitive_merge(filename, self.immediate_dependencies, set.union)
|
|
624
|
+
|
|
625
|
+
@cached_method
|
|
626
|
+
def timestamp(self, filename):
|
|
627
|
+
return os.path.getmtime(filename)
|
|
628
|
+
|
|
629
|
+
def extract_timestamp(self, filename):
|
|
630
|
+
return self.timestamp(filename), filename
|
|
631
|
+
|
|
632
|
+
def newest_dependency(self, filename):
|
|
633
|
+
return max([self.extract_timestamp(f) for f in self.all_dependencies(filename)])
|
|
634
|
+
|
|
635
|
+
def distutils_info0(self, filename):
|
|
636
|
+
info = self.parse_dependencies(filename)[3]
|
|
637
|
+
kwds = info.values
|
|
638
|
+
cimports, externs, incdirs = self.cimports_externs_incdirs(filename)
|
|
639
|
+
basedir = os.getcwd()
|
|
640
|
+
# Add dependencies on "cdef extern from ..." files
|
|
641
|
+
if externs:
|
|
642
|
+
externs = _make_relative(externs, basedir)
|
|
643
|
+
if 'depends' in kwds:
|
|
644
|
+
kwds['depends'] = list(set(kwds['depends']).union(externs))
|
|
645
|
+
else:
|
|
646
|
+
kwds['depends'] = list(externs)
|
|
647
|
+
# Add include_dirs to ensure that the C compiler will find the
|
|
648
|
+
# "cdef extern from ..." files
|
|
649
|
+
if incdirs:
|
|
650
|
+
include_dirs = list(kwds.get('include_dirs', []))
|
|
651
|
+
for inc in _make_relative(incdirs, basedir):
|
|
652
|
+
if inc not in include_dirs:
|
|
653
|
+
include_dirs.append(inc)
|
|
654
|
+
kwds['include_dirs'] = include_dirs
|
|
655
|
+
return info
|
|
656
|
+
|
|
657
|
+
def distutils_info(self, filename, aliases=None, base=None):
|
|
658
|
+
return (self.transitive_merge(filename, self.distutils_info0, DistutilsInfo.merge)
|
|
659
|
+
.subs(aliases)
|
|
660
|
+
.merge(base))
|
|
661
|
+
|
|
662
|
+
def transitive_merge(self, node, extract, merge):
|
|
663
|
+
try:
|
|
664
|
+
seen = self._transitive_cache[extract, merge]
|
|
665
|
+
except KeyError:
|
|
666
|
+
seen = self._transitive_cache[extract, merge] = {}
|
|
667
|
+
return self.transitive_merge_helper(
|
|
668
|
+
node, extract, merge, seen, {}, self.cimported_files)[0]
|
|
669
|
+
|
|
670
|
+
def transitive_merge_helper(self, node, extract, merge, seen, stack, outgoing):
|
|
671
|
+
if node in seen:
|
|
672
|
+
return seen[node], None
|
|
673
|
+
deps = extract(node)
|
|
674
|
+
if node in stack:
|
|
675
|
+
return deps, node
|
|
676
|
+
try:
|
|
677
|
+
stack[node] = len(stack)
|
|
678
|
+
loop = None
|
|
679
|
+
for next in outgoing(node):
|
|
680
|
+
sub_deps, sub_loop = self.transitive_merge_helper(next, extract, merge, seen, stack, outgoing)
|
|
681
|
+
if sub_loop is not None:
|
|
682
|
+
if loop is not None and stack[loop] < stack[sub_loop]:
|
|
683
|
+
pass
|
|
684
|
+
else:
|
|
685
|
+
loop = sub_loop
|
|
686
|
+
deps = merge(deps, sub_deps)
|
|
687
|
+
if loop == node:
|
|
688
|
+
loop = None
|
|
689
|
+
if loop is None:
|
|
690
|
+
seen[node] = deps
|
|
691
|
+
return deps, loop
|
|
692
|
+
finally:
|
|
693
|
+
del stack[node]
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
_dep_tree = None
|
|
697
|
+
|
|
698
|
+
def create_dependency_tree(ctx=None, quiet=False):
|
|
699
|
+
global _dep_tree
|
|
700
|
+
if _dep_tree is None:
|
|
701
|
+
if ctx is None:
|
|
702
|
+
ctx = Context(["."], get_directive_defaults(),
|
|
703
|
+
options=CompilationOptions(default_options))
|
|
704
|
+
_dep_tree = DependencyTree(ctx, quiet=quiet)
|
|
705
|
+
return _dep_tree
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
# If this changes, change also docs/src/reference/compilation.rst
|
|
709
|
+
# which mentions this function
|
|
710
|
+
def default_create_extension(template, kwds):
|
|
711
|
+
if 'depends' in kwds:
|
|
712
|
+
include_dirs = kwds.get('include_dirs', []) + ["."]
|
|
713
|
+
depends = resolve_depends(kwds['depends'], include_dirs)
|
|
714
|
+
kwds['depends'] = sorted(set(depends + template.depends))
|
|
715
|
+
|
|
716
|
+
t = template.__class__
|
|
717
|
+
ext = t(**kwds)
|
|
718
|
+
if hasattr(template, "py_limited_api"):
|
|
719
|
+
ext.py_limited_api = template.py_limited_api
|
|
720
|
+
metadata = dict(distutils=kwds, module_name=kwds['name'])
|
|
721
|
+
return (ext, metadata)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
# This may be useful for advanced users?
|
|
725
|
+
def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=False, language=None,
|
|
726
|
+
exclude_failures=False):
|
|
727
|
+
if language is not None:
|
|
728
|
+
print('Warning: passing language={0!r} to cythonize() is deprecated. '
|
|
729
|
+
'Instead, put "# distutils: language={0}" in your .pyx or .pxd file(s)'.format(language))
|
|
730
|
+
if exclude is None:
|
|
731
|
+
exclude = []
|
|
732
|
+
if patterns is None:
|
|
733
|
+
return [], {}
|
|
734
|
+
elif isinstance(patterns, str) or not isinstance(patterns, Iterable):
|
|
735
|
+
patterns = [patterns]
|
|
736
|
+
|
|
737
|
+
from distutils.extension import Extension
|
|
738
|
+
if 'setuptools' in sys.modules:
|
|
739
|
+
# Support setuptools Extension instances as well.
|
|
740
|
+
extension_classes = (
|
|
741
|
+
Extension, # should normally be the same as 'setuptools.extension._Extension'
|
|
742
|
+
sys.modules['setuptools.extension']._Extension,
|
|
743
|
+
sys.modules['setuptools'].Extension,
|
|
744
|
+
)
|
|
745
|
+
else:
|
|
746
|
+
extension_classes = (Extension,)
|
|
747
|
+
|
|
748
|
+
explicit_modules = {m.name for m in patterns if isinstance(m, extension_classes)}
|
|
749
|
+
deps = create_dependency_tree(ctx, quiet=quiet)
|
|
750
|
+
shared_utility_qualified_name = ctx.shared_utility_qualified_name
|
|
751
|
+
|
|
752
|
+
to_exclude = set()
|
|
753
|
+
if not isinstance(exclude, list):
|
|
754
|
+
exclude = [exclude]
|
|
755
|
+
for pattern in exclude:
|
|
756
|
+
to_exclude.update(map(os.path.abspath, extended_iglob(pattern)))
|
|
757
|
+
|
|
758
|
+
module_list = []
|
|
759
|
+
module_metadata = {}
|
|
760
|
+
|
|
761
|
+
# if no create_extension() function is defined, use a simple
|
|
762
|
+
# default function.
|
|
763
|
+
create_extension = ctx.options.create_extension or default_create_extension
|
|
764
|
+
|
|
765
|
+
seen = set()
|
|
766
|
+
for pattern in patterns:
|
|
767
|
+
if isinstance(pattern, str):
|
|
768
|
+
filepattern = pattern
|
|
769
|
+
template = Extension(pattern, []) # Fake Extension without sources
|
|
770
|
+
name = '*'
|
|
771
|
+
base = None
|
|
772
|
+
ext_language = language
|
|
773
|
+
elif isinstance(pattern, extension_classes):
|
|
774
|
+
cython_sources = [s for s in pattern.sources
|
|
775
|
+
if os.path.splitext(s)[1] in ('.py', '.pyx')]
|
|
776
|
+
template = pattern
|
|
777
|
+
name = template.name
|
|
778
|
+
base = DistutilsInfo(exn=template)
|
|
779
|
+
ext_language = None # do not override whatever the Extension says
|
|
780
|
+
if cython_sources:
|
|
781
|
+
filepattern = cython_sources[0]
|
|
782
|
+
if len(cython_sources) > 1:
|
|
783
|
+
print("Warning: Multiple cython sources found for extension '%s': %s\n"
|
|
784
|
+
"See https://cython.readthedocs.io/en/latest/src/userguide/sharing_declarations.html "
|
|
785
|
+
"for sharing declarations among Cython files." % (pattern.name, cython_sources))
|
|
786
|
+
elif shared_utility_qualified_name and pattern.name == shared_utility_qualified_name:
|
|
787
|
+
# This is the shared utility code file.
|
|
788
|
+
sources = pattern.sources or [
|
|
789
|
+
shared_utility_qualified_name.replace('.', os.sep) + ('.cpp' if pattern.language == 'c++' else '.c')]
|
|
790
|
+
m, _ = create_extension(pattern, dict(
|
|
791
|
+
name=shared_utility_qualified_name,
|
|
792
|
+
sources=sources,
|
|
793
|
+
language=pattern.language,
|
|
794
|
+
# shared utility code uses only parameters specified as argument of Extension() class
|
|
795
|
+
**base.values
|
|
796
|
+
))
|
|
797
|
+
m.np_pythran = False
|
|
798
|
+
m.shared_utility_qualified_name = None
|
|
799
|
+
module_list.append(m)
|
|
800
|
+
continue
|
|
801
|
+
else:
|
|
802
|
+
# ignore non-cython modules
|
|
803
|
+
module_list.append(pattern)
|
|
804
|
+
continue
|
|
805
|
+
else:
|
|
806
|
+
msg = str("pattern is not of type str nor subclass of Extension (%s)"
|
|
807
|
+
" but of type %s and class %s" % (repr(Extension),
|
|
808
|
+
type(pattern),
|
|
809
|
+
pattern.__class__))
|
|
810
|
+
raise TypeError(msg)
|
|
811
|
+
|
|
812
|
+
for file in nonempty(sorted(extended_iglob(filepattern)), "'%s' doesn't match any files" % filepattern):
|
|
813
|
+
if os.path.abspath(file) in to_exclude:
|
|
814
|
+
continue
|
|
815
|
+
module_name = deps.fully_qualified_name(file)
|
|
816
|
+
if '*' in name:
|
|
817
|
+
if module_name in explicit_modules:
|
|
818
|
+
continue
|
|
819
|
+
elif name:
|
|
820
|
+
module_name = name
|
|
821
|
+
|
|
822
|
+
Utils.raise_error_if_module_name_forbidden(module_name)
|
|
823
|
+
|
|
824
|
+
if module_name not in seen:
|
|
825
|
+
try:
|
|
826
|
+
kwds = deps.distutils_info(file, aliases, base).values
|
|
827
|
+
except Exception:
|
|
828
|
+
if exclude_failures:
|
|
829
|
+
continue
|
|
830
|
+
raise
|
|
831
|
+
if base is not None:
|
|
832
|
+
for key, value in base.values.items():
|
|
833
|
+
if key not in kwds:
|
|
834
|
+
kwds[key] = value
|
|
835
|
+
|
|
836
|
+
kwds['name'] = module_name
|
|
837
|
+
|
|
838
|
+
sources = [file] + [m for m in template.sources if m != filepattern]
|
|
839
|
+
if 'sources' in kwds:
|
|
840
|
+
# allow users to add .c files etc.
|
|
841
|
+
for source in kwds['sources']:
|
|
842
|
+
if source not in sources:
|
|
843
|
+
sources.append(source)
|
|
844
|
+
kwds['sources'] = sources
|
|
845
|
+
|
|
846
|
+
if ext_language and 'language' not in kwds:
|
|
847
|
+
kwds['language'] = ext_language
|
|
848
|
+
|
|
849
|
+
np_pythran = kwds.pop('np_pythran', False)
|
|
850
|
+
|
|
851
|
+
# Create the new extension
|
|
852
|
+
m, metadata = create_extension(template, kwds)
|
|
853
|
+
m.np_pythran = np_pythran or getattr(m, 'np_pythran', False)
|
|
854
|
+
m.shared_utility_qualified_name = shared_utility_qualified_name
|
|
855
|
+
if m.np_pythran:
|
|
856
|
+
update_pythran_extension(m)
|
|
857
|
+
module_list.append(m)
|
|
858
|
+
|
|
859
|
+
# Store metadata (this will be written as JSON in the
|
|
860
|
+
# generated C file but otherwise has no purpose)
|
|
861
|
+
module_metadata[module_name] = metadata
|
|
862
|
+
|
|
863
|
+
if file not in m.sources:
|
|
864
|
+
# Old setuptools unconditionally replaces .pyx with .c/.cpp
|
|
865
|
+
target_file = os.path.splitext(file)[0] + ('.cpp' if m.language == 'c++' else '.c')
|
|
866
|
+
try:
|
|
867
|
+
m.sources.remove(target_file)
|
|
868
|
+
except ValueError:
|
|
869
|
+
# never seen this in the wild, but probably better to warn about this unexpected case
|
|
870
|
+
print("Warning: Cython source file not found in sources list, adding %s" % file)
|
|
871
|
+
m.sources.insert(0, file)
|
|
872
|
+
seen.add(name)
|
|
873
|
+
return module_list, module_metadata
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
# This is the user-exposed entry point.
|
|
877
|
+
def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False, force=None, language=None,
|
|
878
|
+
exclude_failures=False, show_all_warnings=False, **options):
|
|
879
|
+
"""
|
|
880
|
+
Compile a set of source modules into C/C++ files and return a list of distutils
|
|
881
|
+
Extension objects for them.
|
|
882
|
+
|
|
883
|
+
:param module_list: As module list, pass either a glob pattern, a list of glob
|
|
884
|
+
patterns or a list of Extension objects. The latter
|
|
885
|
+
allows you to configure the extensions separately
|
|
886
|
+
through the normal distutils options.
|
|
887
|
+
You can also pass Extension objects that have
|
|
888
|
+
glob patterns as their sources. Then, cythonize
|
|
889
|
+
will resolve the pattern and create a
|
|
890
|
+
copy of the Extension for every matching file.
|
|
891
|
+
|
|
892
|
+
:param exclude: When passing glob patterns as ``module_list``, you can exclude certain
|
|
893
|
+
module names explicitly by passing them into the ``exclude`` option.
|
|
894
|
+
|
|
895
|
+
:param nthreads: The number of concurrent builds for parallel compilation
|
|
896
|
+
(requires the ``multiprocessing`` module).
|
|
897
|
+
|
|
898
|
+
:param aliases: If you want to use compiler directives like ``# distutils: ...`` but
|
|
899
|
+
can only know at compile time (when running the ``setup.py``) which values
|
|
900
|
+
to use, you can use aliases and pass a dictionary mapping those aliases
|
|
901
|
+
to Python strings when calling :func:`cythonize`. As an example, say you
|
|
902
|
+
want to use the compiler
|
|
903
|
+
directive ``# distutils: include_dirs = ../static_libs/include/``
|
|
904
|
+
but this path isn't always fixed and you want to find it when running
|
|
905
|
+
the ``setup.py``. You can then do ``# distutils: include_dirs = MY_HEADERS``,
|
|
906
|
+
find the value of ``MY_HEADERS`` in the ``setup.py``, put it in a python
|
|
907
|
+
variable called ``foo`` as a string, and then call
|
|
908
|
+
``cythonize(..., aliases={'MY_HEADERS': foo})``.
|
|
909
|
+
|
|
910
|
+
:param quiet: If True, Cython won't print error, warning, or status messages during the
|
|
911
|
+
compilation.
|
|
912
|
+
|
|
913
|
+
:param force: Forces the recompilation of the Cython modules, even if the timestamps
|
|
914
|
+
don't indicate that a recompilation is necessary.
|
|
915
|
+
|
|
916
|
+
:param language: To globally enable C++ mode, you can pass ``language='c++'``. Otherwise, this
|
|
917
|
+
will be determined at a per-file level based on compiler directives. This
|
|
918
|
+
affects only modules found based on file names. Extension instances passed
|
|
919
|
+
into :func:`cythonize` will not be changed. It is recommended to rather
|
|
920
|
+
use the compiler directive ``# distutils: language = c++`` than this option.
|
|
921
|
+
|
|
922
|
+
:param exclude_failures: For a broad 'try to compile' mode that ignores compilation
|
|
923
|
+
failures and simply excludes the failed extensions,
|
|
924
|
+
pass ``exclude_failures=True``. Note that this only
|
|
925
|
+
really makes sense for compiling ``.py`` files which can also
|
|
926
|
+
be used without compilation.
|
|
927
|
+
|
|
928
|
+
:param show_all_warnings: By default, not all Cython warnings are printed.
|
|
929
|
+
Set to true to show all warnings.
|
|
930
|
+
|
|
931
|
+
:param annotate: If ``True``, will produce a HTML file for each of the ``.pyx`` or ``.py``
|
|
932
|
+
files compiled. The HTML file gives an indication
|
|
933
|
+
of how much Python interaction there is in
|
|
934
|
+
each of the source code lines, compared to plain C code.
|
|
935
|
+
It also allows you to see the C/C++ code
|
|
936
|
+
generated for each line of Cython code. This report is invaluable when
|
|
937
|
+
optimizing a function for speed,
|
|
938
|
+
and for determining when to :ref:`release the GIL <nogil>`:
|
|
939
|
+
in general, a ``nogil`` block may contain only "white" code.
|
|
940
|
+
See examples in :ref:`determining_where_to_add_types` or
|
|
941
|
+
:ref:`primes`.
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
:param annotate-fullc: If ``True`` will produce a colorized HTML version of
|
|
945
|
+
the source which includes entire generated C/C++-code.
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
:param compiler_directives: Allow to set compiler directives in the ``setup.py`` like this:
|
|
949
|
+
``compiler_directives={'embedsignature': True}``.
|
|
950
|
+
See :ref:`compiler-directives`.
|
|
951
|
+
|
|
952
|
+
:param depfile: produce depfiles for the sources if True.
|
|
953
|
+
:param cache: If ``True`` the cache enabled with default path. If the value is a path to a directory,
|
|
954
|
+
then the directory is used to cache generated ``.c``/``.cpp`` files. By default cache is disabled.
|
|
955
|
+
See :ref:`cython-cache`.
|
|
956
|
+
"""
|
|
957
|
+
if exclude is None:
|
|
958
|
+
exclude = []
|
|
959
|
+
if 'include_path' not in options:
|
|
960
|
+
options['include_path'] = ['.']
|
|
961
|
+
if 'common_utility_include_dir' in options:
|
|
962
|
+
safe_makedirs(options['common_utility_include_dir'])
|
|
963
|
+
|
|
964
|
+
depfile = options.pop('depfile', None)
|
|
965
|
+
|
|
966
|
+
if pythran is None:
|
|
967
|
+
pythran_options = None
|
|
968
|
+
else:
|
|
969
|
+
pythran_options = CompilationOptions(**options)
|
|
970
|
+
pythran_options.cplus = True
|
|
971
|
+
pythran_options.np_pythran = True
|
|
972
|
+
|
|
973
|
+
if force is None:
|
|
974
|
+
force = os.environ.get("CYTHON_FORCE_REGEN") == "1" # allow global overrides for build systems
|
|
975
|
+
|
|
976
|
+
c_options = CompilationOptions(**options)
|
|
977
|
+
cpp_options = CompilationOptions(**options); cpp_options.cplus = True
|
|
978
|
+
ctx = Context.from_options(c_options)
|
|
979
|
+
options = c_options
|
|
980
|
+
shared_utility_qualified_name = ctx.shared_utility_qualified_name
|
|
981
|
+
module_list, module_metadata = create_extension_list(
|
|
982
|
+
module_list,
|
|
983
|
+
exclude=exclude,
|
|
984
|
+
ctx=ctx,
|
|
985
|
+
quiet=quiet,
|
|
986
|
+
exclude_failures=exclude_failures,
|
|
987
|
+
language=language,
|
|
988
|
+
aliases=aliases)
|
|
989
|
+
|
|
990
|
+
deps = create_dependency_tree(ctx, quiet=quiet)
|
|
991
|
+
build_dir = getattr(options, 'build_dir', None)
|
|
992
|
+
if options.cache and not (options.annotate or Options.annotate):
|
|
993
|
+
# cache is enabled when:
|
|
994
|
+
# * options.cache is True (the default path to the cache base dir is used)
|
|
995
|
+
# * options.cache is the explicit path to the cache base dir
|
|
996
|
+
# * annotations are not generated
|
|
997
|
+
cache_path = None if options.cache is True else options.cache
|
|
998
|
+
cache = Cache(cache_path, getattr(options, 'cache_size', None))
|
|
999
|
+
else:
|
|
1000
|
+
cache = None
|
|
1001
|
+
|
|
1002
|
+
def copy_to_build_dir(filepath, root=os.getcwd()):
|
|
1003
|
+
filepath_abs = os.path.abspath(filepath)
|
|
1004
|
+
if os.path.isabs(filepath):
|
|
1005
|
+
filepath = filepath_abs
|
|
1006
|
+
if filepath_abs.startswith(root):
|
|
1007
|
+
# distutil extension depends are relative to cwd
|
|
1008
|
+
mod_dir = join_path(build_dir,
|
|
1009
|
+
os.path.dirname(_relpath(filepath, root)))
|
|
1010
|
+
copy_once_if_newer(filepath_abs, mod_dir)
|
|
1011
|
+
|
|
1012
|
+
def file_in_build_dir(c_file):
|
|
1013
|
+
if not build_dir:
|
|
1014
|
+
return c_file
|
|
1015
|
+
if os.path.isabs(c_file):
|
|
1016
|
+
c_file = os.path.splitdrive(c_file)[1]
|
|
1017
|
+
c_file = c_file.split(os.sep, 1)[1]
|
|
1018
|
+
c_file = os.path.join(build_dir, c_file)
|
|
1019
|
+
dir = os.path.dirname(c_file)
|
|
1020
|
+
safe_makedirs_once(dir)
|
|
1021
|
+
return c_file
|
|
1022
|
+
|
|
1023
|
+
modules_by_cfile = collections.defaultdict(list)
|
|
1024
|
+
to_compile = []
|
|
1025
|
+
for m in module_list:
|
|
1026
|
+
if build_dir:
|
|
1027
|
+
for dep in m.depends:
|
|
1028
|
+
copy_to_build_dir(dep)
|
|
1029
|
+
|
|
1030
|
+
cy_sources = [
|
|
1031
|
+
source for source in m.sources
|
|
1032
|
+
if os.path.splitext(source)[1] in ('.pyx', '.py')]
|
|
1033
|
+
if len(cy_sources) == 1:
|
|
1034
|
+
# normal "special" case: believe the Extension module name to allow user overrides
|
|
1035
|
+
full_module_name = m.name
|
|
1036
|
+
else:
|
|
1037
|
+
# infer FQMN from source files
|
|
1038
|
+
full_module_name = None
|
|
1039
|
+
|
|
1040
|
+
np_pythran = getattr(m, 'np_pythran', False)
|
|
1041
|
+
py_limited_api = getattr(m, 'py_limited_api', False)
|
|
1042
|
+
|
|
1043
|
+
if np_pythran:
|
|
1044
|
+
options = pythran_options
|
|
1045
|
+
elif m.language == 'c++':
|
|
1046
|
+
options = cpp_options
|
|
1047
|
+
else:
|
|
1048
|
+
options = c_options
|
|
1049
|
+
|
|
1050
|
+
new_sources = []
|
|
1051
|
+
for source in m.sources:
|
|
1052
|
+
base, ext = os.path.splitext(source)
|
|
1053
|
+
if ext in ('.pyx', '.py'):
|
|
1054
|
+
c_file = base + ('.cpp' if m.language == 'c++' or np_pythran else '.c')
|
|
1055
|
+
|
|
1056
|
+
# setup for out of place build directory if enabled
|
|
1057
|
+
c_file = file_in_build_dir(c_file)
|
|
1058
|
+
|
|
1059
|
+
# write out the depfile, if requested
|
|
1060
|
+
if depfile:
|
|
1061
|
+
dependencies = deps.all_dependencies(source)
|
|
1062
|
+
write_depfile(c_file, source, dependencies)
|
|
1063
|
+
|
|
1064
|
+
# Missing files and those generated by other Cython versions should always be recreated.
|
|
1065
|
+
if Utils.file_generated_by_this_cython(c_file):
|
|
1066
|
+
c_timestamp = os.path.getmtime(c_file)
|
|
1067
|
+
else:
|
|
1068
|
+
c_timestamp = -1
|
|
1069
|
+
|
|
1070
|
+
# Priority goes first to modified files, second to direct
|
|
1071
|
+
# dependents, and finally to indirect dependents.
|
|
1072
|
+
if c_timestamp < deps.timestamp(source):
|
|
1073
|
+
dep_timestamp, dep = deps.timestamp(source), source
|
|
1074
|
+
priority = 0
|
|
1075
|
+
else:
|
|
1076
|
+
dep_timestamp, dep = deps.newest_dependency(source)
|
|
1077
|
+
priority = 2 - (dep in deps.immediate_dependencies(source))
|
|
1078
|
+
if force or c_timestamp < dep_timestamp:
|
|
1079
|
+
if not quiet and not force:
|
|
1080
|
+
if source == dep:
|
|
1081
|
+
print("Compiling %s because it changed." % Utils.decode_filename(source))
|
|
1082
|
+
else:
|
|
1083
|
+
print("Compiling %s because it depends on %s." % (
|
|
1084
|
+
Utils.decode_filename(source),
|
|
1085
|
+
Utils.decode_filename(dep),
|
|
1086
|
+
))
|
|
1087
|
+
if not force and cache:
|
|
1088
|
+
fingerprint = cache.transitive_fingerprint(
|
|
1089
|
+
source, deps.all_dependencies(source), options,
|
|
1090
|
+
FingerprintFlags(m.language or 'c', py_limited_api, np_pythran)
|
|
1091
|
+
)
|
|
1092
|
+
else:
|
|
1093
|
+
fingerprint = None
|
|
1094
|
+
to_compile.append((
|
|
1095
|
+
priority, source, c_file, fingerprint, quiet,
|
|
1096
|
+
options, not exclude_failures, module_metadata.get(m.name),
|
|
1097
|
+
full_module_name, show_all_warnings))
|
|
1098
|
+
modules_by_cfile[c_file].append(m)
|
|
1099
|
+
elif shared_utility_qualified_name and m.name == shared_utility_qualified_name:
|
|
1100
|
+
# Generate shared utility code module now.
|
|
1101
|
+
c_file = file_in_build_dir(source)
|
|
1102
|
+
module_options = CompilationOptions(
|
|
1103
|
+
options, shared_c_file_path=c_file, shared_utility_qualified_name=None)
|
|
1104
|
+
if not Utils.is_cython_generated_file(c_file):
|
|
1105
|
+
print(f"Warning: Shared module source file is not a Cython file - not creating '{m.name}' as '{c_file}'")
|
|
1106
|
+
elif force or not Utils.file_generated_by_this_cython(c_file):
|
|
1107
|
+
from .SharedModule import generate_shared_module
|
|
1108
|
+
if not quiet:
|
|
1109
|
+
print(f"Generating shared module '{m.name}'")
|
|
1110
|
+
generate_shared_module(module_options)
|
|
1111
|
+
else:
|
|
1112
|
+
c_file = source
|
|
1113
|
+
if build_dir:
|
|
1114
|
+
copy_to_build_dir(source)
|
|
1115
|
+
|
|
1116
|
+
new_sources.append(c_file)
|
|
1117
|
+
|
|
1118
|
+
m.sources = new_sources
|
|
1119
|
+
|
|
1120
|
+
to_compile.sort()
|
|
1121
|
+
N = len(to_compile)
|
|
1122
|
+
|
|
1123
|
+
# Drop "priority" sorting component of "to_compile" entries
|
|
1124
|
+
# and add a simple progress indicator and the remaining arguments.
|
|
1125
|
+
build_progress_indicator = ("[{0:%d}/%d] " % (len(str(N)), N)).format
|
|
1126
|
+
to_compile = [
|
|
1127
|
+
task[1:] + (build_progress_indicator(i), cache)
|
|
1128
|
+
for i, task in enumerate(to_compile, 1)
|
|
1129
|
+
]
|
|
1130
|
+
|
|
1131
|
+
if N <= 1:
|
|
1132
|
+
nthreads = 0
|
|
1133
|
+
try:
|
|
1134
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
1135
|
+
except ImportError:
|
|
1136
|
+
nthreads = 0
|
|
1137
|
+
|
|
1138
|
+
if nthreads:
|
|
1139
|
+
with ProcessPoolExecutor(
|
|
1140
|
+
max_workers=nthreads,
|
|
1141
|
+
initializer=_init_multiprocessing_helper,
|
|
1142
|
+
) as proc_pool:
|
|
1143
|
+
try:
|
|
1144
|
+
list(proc_pool.map(cythonize_one_helper, to_compile, chunksize=1))
|
|
1145
|
+
except KeyboardInterrupt:
|
|
1146
|
+
proc_pool.terminate_workers()
|
|
1147
|
+
proc_pool.shutdown(cancel_futures=True)
|
|
1148
|
+
raise
|
|
1149
|
+
else:
|
|
1150
|
+
for args in to_compile:
|
|
1151
|
+
cythonize_one(*args)
|
|
1152
|
+
|
|
1153
|
+
if exclude_failures:
|
|
1154
|
+
failed_modules = set()
|
|
1155
|
+
for c_file, modules in modules_by_cfile.items():
|
|
1156
|
+
if not os.path.exists(c_file):
|
|
1157
|
+
failed_modules.update(modules)
|
|
1158
|
+
elif os.path.getsize(c_file) < 200:
|
|
1159
|
+
f = open(c_file, 'r', encoding='iso8859-1')
|
|
1160
|
+
try:
|
|
1161
|
+
if f.read(len('#error ')) == '#error ':
|
|
1162
|
+
# dead compilation result
|
|
1163
|
+
failed_modules.update(modules)
|
|
1164
|
+
finally:
|
|
1165
|
+
f.close()
|
|
1166
|
+
if failed_modules:
|
|
1167
|
+
for module in failed_modules:
|
|
1168
|
+
module_list.remove(module)
|
|
1169
|
+
print("Failed compilations: %s" % ', '.join(sorted([
|
|
1170
|
+
module.name for module in failed_modules])))
|
|
1171
|
+
|
|
1172
|
+
if cache:
|
|
1173
|
+
cache.cleanup_cache()
|
|
1174
|
+
|
|
1175
|
+
# cythonize() is often followed by the (non-Python-buffered)
|
|
1176
|
+
# compiler output, flush now to avoid interleaving output.
|
|
1177
|
+
sys.stdout.flush()
|
|
1178
|
+
return module_list
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
if os.environ.get('XML_RESULTS'):
|
|
1182
|
+
compile_result_dir = os.environ['XML_RESULTS']
|
|
1183
|
+
def record_results(func):
|
|
1184
|
+
def with_record(*args):
|
|
1185
|
+
t = time.time()
|
|
1186
|
+
success = True
|
|
1187
|
+
try:
|
|
1188
|
+
try:
|
|
1189
|
+
func(*args)
|
|
1190
|
+
except:
|
|
1191
|
+
success = False
|
|
1192
|
+
finally:
|
|
1193
|
+
t = time.time() - t
|
|
1194
|
+
module = fully_qualified_name(args[0])
|
|
1195
|
+
name = "cythonize." + module
|
|
1196
|
+
failures = 1 - success
|
|
1197
|
+
with open(os.path.join(compile_result_dir, name + ".xml"), "w") as output:
|
|
1198
|
+
output.write(f"""
|
|
1199
|
+
<?xml version="1.0" ?>
|
|
1200
|
+
<testsuite name="{name}" errors="0" failures="{failures}" tests="1" time="{t}">
|
|
1201
|
+
<testcase classname="{name}" name="cythonize">
|
|
1202
|
+
{'' if success else 'failure'}
|
|
1203
|
+
</testcase>
|
|
1204
|
+
</testsuite>
|
|
1205
|
+
""".strip())
|
|
1206
|
+
return with_record
|
|
1207
|
+
else:
|
|
1208
|
+
def record_results(func):
|
|
1209
|
+
return func
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
# TODO: Share context? Issue: pyx processing leaks into pxd module
|
|
1213
|
+
@record_results
|
|
1214
|
+
def cythonize_one(pyx_file, c_file,
|
|
1215
|
+
fingerprint=None, quiet=False, options=None,
|
|
1216
|
+
raise_on_failure=True, embedded_metadata=None,
|
|
1217
|
+
full_module_name=None, show_all_warnings=False,
|
|
1218
|
+
progress="", cache=None):
|
|
1219
|
+
from ..Compiler.Main import compile_single, default_options
|
|
1220
|
+
from ..Compiler.Errors import CompileError, PyrexError
|
|
1221
|
+
|
|
1222
|
+
if not quiet:
|
|
1223
|
+
if cache and fingerprint and cache.lookup_cache(c_file, fingerprint):
|
|
1224
|
+
print(f"{progress}Found compiled {pyx_file} in cache")
|
|
1225
|
+
else:
|
|
1226
|
+
print(f"{progress}Cythonizing {Utils.decode_filename(pyx_file)}")
|
|
1227
|
+
if options is None:
|
|
1228
|
+
options = CompilationOptions(default_options)
|
|
1229
|
+
options.output_file = c_file
|
|
1230
|
+
options.embedded_metadata = embedded_metadata
|
|
1231
|
+
|
|
1232
|
+
old_warning_level = Errors.LEVEL
|
|
1233
|
+
if show_all_warnings:
|
|
1234
|
+
Errors.LEVEL = 0
|
|
1235
|
+
|
|
1236
|
+
any_failures = 0
|
|
1237
|
+
try:
|
|
1238
|
+
result = compile_single(pyx_file, options, full_module_name=full_module_name, cache=cache, fingerprint=fingerprint)
|
|
1239
|
+
if result.num_errors > 0:
|
|
1240
|
+
any_failures = 1
|
|
1241
|
+
except (OSError, PyrexError) as e:
|
|
1242
|
+
sys.stderr.write('%s\n' % e)
|
|
1243
|
+
any_failures = 1
|
|
1244
|
+
# XXX
|
|
1245
|
+
import traceback
|
|
1246
|
+
traceback.print_exc()
|
|
1247
|
+
except Exception:
|
|
1248
|
+
if raise_on_failure:
|
|
1249
|
+
raise
|
|
1250
|
+
import traceback
|
|
1251
|
+
traceback.print_exc()
|
|
1252
|
+
any_failures = 1
|
|
1253
|
+
finally:
|
|
1254
|
+
if show_all_warnings:
|
|
1255
|
+
Errors.LEVEL = old_warning_level
|
|
1256
|
+
|
|
1257
|
+
if any_failures:
|
|
1258
|
+
if raise_on_failure:
|
|
1259
|
+
raise CompileError(None, pyx_file)
|
|
1260
|
+
elif os.path.exists(c_file):
|
|
1261
|
+
os.remove(c_file)
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
def cythonize_one_helper(m):
|
|
1265
|
+
import traceback
|
|
1266
|
+
try:
|
|
1267
|
+
return cythonize_one(*m)
|
|
1268
|
+
except Exception:
|
|
1269
|
+
traceback.print_exc()
|
|
1270
|
+
raise
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
def _init_multiprocessing_helper():
|
|
1274
|
+
# KeyboardInterrupt kills workers, so don't let them get it
|
|
1275
|
+
import signal
|
|
1276
|
+
signal.signal(signal.SIGINT, signal.SIG_IGN)
|