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