passagemath-environment 10.4.1__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.
- passagemath_environment-10.4.1.data/scripts/sage +1140 -0
- passagemath_environment-10.4.1.data/scripts/sage-env +667 -0
- passagemath_environment-10.4.1.data/scripts/sage-num-threads.py +105 -0
- passagemath_environment-10.4.1.data/scripts/sage-python +2 -0
- passagemath_environment-10.4.1.data/scripts/sage-venv-config +42 -0
- passagemath_environment-10.4.1.data/scripts/sage-version.sh +9 -0
- passagemath_environment-10.4.1.dist-info/METADATA +76 -0
- passagemath_environment-10.4.1.dist-info/RECORD +70 -0
- passagemath_environment-10.4.1.dist-info/WHEEL +5 -0
- passagemath_environment-10.4.1.dist-info/top_level.txt +1 -0
- sage/all__sagemath_environment.py +4 -0
- sage/env.py +496 -0
- sage/features/__init__.py +981 -0
- sage/features/all.py +126 -0
- sage/features/bliss.py +85 -0
- sage/features/cddlib.py +38 -0
- sage/features/coxeter3.py +45 -0
- sage/features/csdp.py +83 -0
- sage/features/cython.py +38 -0
- sage/features/databases.py +302 -0
- sage/features/dvipng.py +40 -0
- sage/features/ecm.py +42 -0
- sage/features/ffmpeg.py +119 -0
- sage/features/four_ti_2.py +55 -0
- sage/features/fricas.py +66 -0
- sage/features/gap.py +86 -0
- sage/features/gfan.py +38 -0
- sage/features/giac.py +30 -0
- sage/features/graph_generators.py +171 -0
- sage/features/graphviz.py +117 -0
- sage/features/igraph.py +44 -0
- sage/features/imagemagick.py +138 -0
- sage/features/interfaces.py +256 -0
- sage/features/internet.py +65 -0
- sage/features/jmol.py +44 -0
- sage/features/join_feature.py +146 -0
- sage/features/kenzo.py +77 -0
- sage/features/latex.py +300 -0
- sage/features/latte.py +85 -0
- sage/features/lrs.py +164 -0
- sage/features/mcqd.py +45 -0
- sage/features/meataxe.py +46 -0
- sage/features/mip_backends.py +114 -0
- sage/features/msolve.py +68 -0
- sage/features/nauty.py +70 -0
- sage/features/normaliz.py +43 -0
- sage/features/palp.py +65 -0
- sage/features/pandoc.py +42 -0
- sage/features/pdf2svg.py +41 -0
- sage/features/phitigra.py +42 -0
- sage/features/pkg_systems.py +195 -0
- sage/features/polymake.py +43 -0
- sage/features/poppler.py +58 -0
- sage/features/rubiks.py +180 -0
- sage/features/sagemath.py +1205 -0
- sage/features/sat.py +103 -0
- sage/features/singular.py +48 -0
- sage/features/sirocco.py +45 -0
- sage/features/sphinx.py +71 -0
- sage/features/standard.py +38 -0
- sage/features/symengine_py.py +44 -0
- sage/features/tdlib.py +38 -0
- sage/features/threejs.py +75 -0
- sage/features/topcom.py +67 -0
- sage/misc/all__sagemath_environment.py +2 -0
- sage/misc/package.py +570 -0
- sage/misc/package_dir.py +621 -0
- sage/misc/temporary_file.py +546 -0
- sage/misc/viewer.py +369 -0
- sage/version.py +5 -0
sage/misc/package_dir.py
ADDED
@@ -0,0 +1,621 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-environment
|
2
|
+
"""
|
3
|
+
Recognizing package directories
|
4
|
+
"""
|
5
|
+
# ****************************************************************************
|
6
|
+
# Copyright (C) 2020-2022 Matthias Koeppe
|
7
|
+
#
|
8
|
+
# This program is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation, either version 2 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
# https://www.gnu.org/licenses/
|
13
|
+
# ****************************************************************************
|
14
|
+
|
15
|
+
import os
|
16
|
+
import glob
|
17
|
+
import re
|
18
|
+
import sys
|
19
|
+
|
20
|
+
from collections import defaultdict
|
21
|
+
from contextlib import contextmanager
|
22
|
+
|
23
|
+
|
24
|
+
class SourceDistributionFilter:
|
25
|
+
r"""
|
26
|
+
A :class:`collections.abc.Container` for source files in distributions.
|
27
|
+
|
28
|
+
INPUT:
|
29
|
+
|
30
|
+
- ``include_distributions`` -- (default: ``None``) if not ``None``,
|
31
|
+
should be a sequence or set of strings: include files whose
|
32
|
+
``distribution`` (from a ``# sage_setup:`` ``distribution = PACKAGE``
|
33
|
+
directive in the source file) is an element of ``distributions``.
|
34
|
+
|
35
|
+
- ``exclude_distributions`` -- (default: ``None``) if not ``None``,
|
36
|
+
should be a sequence or set of strings: exclude files whose
|
37
|
+
``distribution`` (from a ``# sage_setup:`` ``distribution = PACKAGE``
|
38
|
+
directive in the module source file) is in ``exclude_distributions``.
|
39
|
+
|
40
|
+
EXAMPLES::
|
41
|
+
|
42
|
+
sage: from sage.misc.package_dir import SourceDistributionFilter
|
43
|
+
sage: F = SourceDistributionFilter()
|
44
|
+
sage: sage.misc.package_dir.__file__ in F
|
45
|
+
True
|
46
|
+
sage: F = SourceDistributionFilter(include_distributions=['sagemath-environment'])
|
47
|
+
sage: sage.misc.package_dir.__file__ in F
|
48
|
+
True
|
49
|
+
sage: F = SourceDistributionFilter(exclude_distributions=['sagemath-environment'])
|
50
|
+
sage: sage.misc.package_dir.__file__ in F
|
51
|
+
False
|
52
|
+
"""
|
53
|
+
def __init__(self, include_distributions=None, exclude_distributions=None):
|
54
|
+
r"""
|
55
|
+
TESTS:
|
56
|
+
|
57
|
+
``exclude_distributions=None`` is normalized to the empty tuple::
|
58
|
+
|
59
|
+
sage: from sage.misc.package_dir import SourceDistributionFilter
|
60
|
+
sage: F = SourceDistributionFilter()
|
61
|
+
sage: F._exclude_distributions
|
62
|
+
()
|
63
|
+
"""
|
64
|
+
self._include_distributions = include_distributions
|
65
|
+
if exclude_distributions is None:
|
66
|
+
exclude_distributions = ()
|
67
|
+
self._exclude_distributions = exclude_distributions
|
68
|
+
|
69
|
+
def __contains__(self, filename):
|
70
|
+
r"""
|
71
|
+
TESTS:
|
72
|
+
|
73
|
+
No file access is used when neither ``include_distributions`` nor
|
74
|
+
``exclude_distributions`` is given::
|
75
|
+
|
76
|
+
sage: from sage.misc.package_dir import SourceDistributionFilter
|
77
|
+
sage: F = SourceDistributionFilter()
|
78
|
+
sage: '/doesnotexist' in F
|
79
|
+
True
|
80
|
+
|
81
|
+
``exclude_distributions`` can also be an empty container::
|
82
|
+
|
83
|
+
sage: F = SourceDistributionFilter(exclude_distributions=())
|
84
|
+
sage: '/doesnotexist' in F
|
85
|
+
True
|
86
|
+
"""
|
87
|
+
if self._include_distributions is None and not self._exclude_distributions:
|
88
|
+
return True
|
89
|
+
distribution = read_distribution(filename)
|
90
|
+
if self._include_distributions is not None:
|
91
|
+
if distribution not in self._include_distributions:
|
92
|
+
return False
|
93
|
+
return distribution not in self._exclude_distributions
|
94
|
+
|
95
|
+
|
96
|
+
distribution_directive = re.compile(r"(\s*#?\s*)(sage_setup:\s*distribution\s*=\s*([-_A-Za-z0-9]*))")
|
97
|
+
|
98
|
+
|
99
|
+
def read_distribution(src_file):
|
100
|
+
r"""
|
101
|
+
Parse ``src_file`` for a ``# sage_setup:`` ``distribution = PKG`` directive.
|
102
|
+
|
103
|
+
INPUT:
|
104
|
+
|
105
|
+
- ``src_file`` -- file name of a Python or Cython source file
|
106
|
+
|
107
|
+
OUTPUT:
|
108
|
+
|
109
|
+
A string, the name of the distribution package (``PKG``), or the empty
|
110
|
+
string if no directive was found.
|
111
|
+
|
112
|
+
EXAMPLES::
|
113
|
+
|
114
|
+
sage: # needs SAGE_SRC
|
115
|
+
sage: from sage.env import SAGE_SRC
|
116
|
+
sage: from sage.misc.package_dir import read_distribution
|
117
|
+
sage: read_distribution(os.path.join(SAGE_SRC, 'sage', 'graphs', 'graph_decompositions', 'tdlib.pyx'))
|
118
|
+
'sagemath-tdlib'
|
119
|
+
sage: read_distribution(os.path.join(SAGE_SRC, 'sage', 'graphs', 'graph_decompositions', 'modular_decomposition.py'))
|
120
|
+
''
|
121
|
+
"""
|
122
|
+
with open(src_file, encoding='utf-8', errors='ignore') as fh:
|
123
|
+
for line in fh:
|
124
|
+
# Adapted from Cython's Build/Dependencies.py
|
125
|
+
line = line.lstrip()
|
126
|
+
if not line:
|
127
|
+
continue
|
128
|
+
if line.startswith('#') or line.startswith(';'):
|
129
|
+
line = line[1:].lstrip()
|
130
|
+
elif line.startswith('/*') or line.startswith('//') or line.startswith(';;'):
|
131
|
+
line = line[2:].lstrip()
|
132
|
+
else:
|
133
|
+
break
|
134
|
+
kind = "sage_setup:"
|
135
|
+
if line.startswith(kind):
|
136
|
+
key, _, value = (s.strip() for s in line[len(kind):].partition('='))
|
137
|
+
if key == "distribution":
|
138
|
+
return value
|
139
|
+
return ''
|
140
|
+
|
141
|
+
|
142
|
+
def update_distribution(src_file, distribution, *, verbose=False):
|
143
|
+
r"""
|
144
|
+
Add or update a ``# sage_setup:`` ``distribution = PKG`` directive in ``src_file``.
|
145
|
+
|
146
|
+
For a Python or Cython file, if a ``distribution`` directive
|
147
|
+
is not already present, it is added.
|
148
|
+
|
149
|
+
For any other file, if a ``distribution`` directive is not already
|
150
|
+
present, no action is taken.
|
151
|
+
|
152
|
+
INPUT:
|
153
|
+
|
154
|
+
- ``src_file`` -- file name of a source file
|
155
|
+
|
156
|
+
EXAMPLES::
|
157
|
+
|
158
|
+
sage: from sage.misc.package_dir import read_distribution, update_distribution
|
159
|
+
sage: import tempfile
|
160
|
+
sage: def test(filename, file_contents):
|
161
|
+
....: with tempfile.TemporaryDirectory() as d:
|
162
|
+
....: fname = os.path.join(d, filename)
|
163
|
+
....: with open(fname, 'w') as f:
|
164
|
+
....: f.write(file_contents)
|
165
|
+
....: with open(fname, 'r') as f:
|
166
|
+
....: print(f.read() + "====")
|
167
|
+
....: update_distribution(fname, 'sagemath-categories')
|
168
|
+
....: with open(fname, 'r') as f:
|
169
|
+
....: print(f.read() + "====")
|
170
|
+
....: update_distribution(fname, '')
|
171
|
+
....: with open(fname, 'r') as f:
|
172
|
+
....: print(f.read(), end="")
|
173
|
+
sage: test('module.py', '# Python file\n')
|
174
|
+
# Python file
|
175
|
+
====
|
176
|
+
# sage_setup: distribution...= sagemath-categories
|
177
|
+
# Python file
|
178
|
+
====
|
179
|
+
# sage_setup: distribution...=
|
180
|
+
# Python file
|
181
|
+
sage: test('file.cpp', '// sage_setup: ' 'distribution=sagemath-modules\n'
|
182
|
+
....: '// C++ file with existing directive\n')
|
183
|
+
// sage_setup: distribution...=sagemath-modules
|
184
|
+
// C++ file with existing directive
|
185
|
+
====
|
186
|
+
// sage_setup: distribution...= sagemath-categories
|
187
|
+
// C++ file with existing directive
|
188
|
+
====
|
189
|
+
// sage_setup: distribution...=
|
190
|
+
// C++ file with existing directive
|
191
|
+
sage: test('file.cpp', '// C++ file without existing directive\n')
|
192
|
+
// C++ file without existing directive
|
193
|
+
====
|
194
|
+
// C++ file without existing directive
|
195
|
+
====
|
196
|
+
// C++ file without existing directive
|
197
|
+
"""
|
198
|
+
if not distribution:
|
199
|
+
distribution = ''
|
200
|
+
directive = 'sage_setup: ' f'distribution = {distribution}'.rstrip()
|
201
|
+
try:
|
202
|
+
with open(src_file) as f:
|
203
|
+
src_lines = f.read().splitlines()
|
204
|
+
except UnicodeDecodeError:
|
205
|
+
# Silently skip binary files
|
206
|
+
return
|
207
|
+
any_found = False
|
208
|
+
any_change = False
|
209
|
+
for i, line in enumerate(src_lines):
|
210
|
+
if m := distribution_directive.search(line):
|
211
|
+
old_distribution = m.group(3)
|
212
|
+
if any_found:
|
213
|
+
# Found a second distribution directive; remove it.
|
214
|
+
if not (line := distribution_directive.sub(r'', line)):
|
215
|
+
line = None
|
216
|
+
else:
|
217
|
+
line = distribution_directive.sub(fr'\1{directive}', line)
|
218
|
+
if line != src_lines[i]:
|
219
|
+
src_lines[i] = line
|
220
|
+
any_change = True
|
221
|
+
if verbose:
|
222
|
+
print(f"{src_file}: changed 'sage_setup: " f"distribution' "
|
223
|
+
f"from {old_distribution!r} to {distribution!r}")
|
224
|
+
any_found = True
|
225
|
+
if not any_found:
|
226
|
+
if any(src_file.endswith(ext)
|
227
|
+
for ext in [".pxd", ".pxi", ".py", ".pyx", ".sage"]):
|
228
|
+
src_lines.insert(0, f'# {directive}')
|
229
|
+
any_change = True
|
230
|
+
if verbose:
|
231
|
+
print(f"{src_file}: added 'sage_setup: "
|
232
|
+
f"distribution = {distribution}' directive")
|
233
|
+
if not any_change:
|
234
|
+
return
|
235
|
+
with open(src_file, 'w') as f:
|
236
|
+
for line in src_lines:
|
237
|
+
if line is not None:
|
238
|
+
f.write(line + '\n')
|
239
|
+
|
240
|
+
|
241
|
+
def is_package_or_sage_namespace_package_dir(path, *, distribution_filter=None):
|
242
|
+
r"""
|
243
|
+
Return whether ``path`` is a directory that contains a Python package.
|
244
|
+
|
245
|
+
Ordinary Python packages are recognized by the presence of ``__init__.py``.
|
246
|
+
|
247
|
+
Implicit namespace packages (PEP 420) are only recognized if they
|
248
|
+
follow the conventions of the Sage library, i.e., the directory contains
|
249
|
+
a file ``all.py`` or a file matching the pattern ``all__*.py``
|
250
|
+
such as ``all__sagemath_categories.py``.
|
251
|
+
|
252
|
+
INPUT:
|
253
|
+
|
254
|
+
- ``path`` -- a directory name
|
255
|
+
|
256
|
+
- ``distribution_filter`` -- (default: ``None``)
|
257
|
+
only consider ``all*.py`` files whose distribution (from a
|
258
|
+
``# sage_setup:`` ``distribution = PACKAGE`` directive in the source file)
|
259
|
+
is an element of ``distribution_filter``.
|
260
|
+
|
261
|
+
EXAMPLES:
|
262
|
+
|
263
|
+
:mod:`sage.cpython` is an ordinary package::
|
264
|
+
|
265
|
+
sage: from sage.misc.package_dir import is_package_or_sage_namespace_package_dir
|
266
|
+
sage: directory = sage.cpython.__path__[0]; directory
|
267
|
+
'.../sage/cpython'
|
268
|
+
sage: is_package_or_sage_namespace_package_dir(directory)
|
269
|
+
True
|
270
|
+
|
271
|
+
:mod:`sage.libs.mpfr` only has an ``__init__.pxd`` file, but we consider
|
272
|
+
it a package directory for consistency with Cython::
|
273
|
+
|
274
|
+
sage: directory = os.path.join(sage.libs.__path__[0], 'mpfr'); directory
|
275
|
+
'.../sage/libs/mpfr'
|
276
|
+
sage: is_package_or_sage_namespace_package_dir(directory) # known bug (seen in build.yml)
|
277
|
+
True
|
278
|
+
|
279
|
+
:mod:`sage` is designated to become an implicit namespace package::
|
280
|
+
|
281
|
+
sage: directory = sage.__path__[0]; directory
|
282
|
+
'.../sage'
|
283
|
+
sage: is_package_or_sage_namespace_package_dir(directory) # known bug (seen in build.yml)
|
284
|
+
True
|
285
|
+
|
286
|
+
Not a package::
|
287
|
+
|
288
|
+
sage: directory = os.path.join(sage.symbolic.__path__[0], 'ginac'); directory # needs sage.symbolic
|
289
|
+
'.../sage/symbolic/ginac'
|
290
|
+
sage: is_package_or_sage_namespace_package_dir(directory) # needs sage.symbolic
|
291
|
+
False
|
292
|
+
"""
|
293
|
+
if os.path.exists(os.path.join(path, '__init__.py')): # ordinary package
|
294
|
+
return True
|
295
|
+
if os.path.exists(os.path.join(path, '__init__.pxd')): # for consistency with Cython
|
296
|
+
return True
|
297
|
+
fname = os.path.join(path, 'all.py')
|
298
|
+
if os.path.exists(fname):
|
299
|
+
if distribution_filter is None or fname in distribution_filter: # complete namespace package
|
300
|
+
return True
|
301
|
+
for fname in glob.iglob(os.path.join(path, 'all__*.py')):
|
302
|
+
if distribution_filter is None or fname in distribution_filter: # partial namespace package
|
303
|
+
return True
|
304
|
+
return False
|
305
|
+
|
306
|
+
|
307
|
+
@contextmanager
|
308
|
+
def cython_namespace_package_support():
|
309
|
+
r"""
|
310
|
+
Activate namespace package support in Cython 0.x.
|
311
|
+
|
312
|
+
See https://github.com/cython/cython/issues/2918#issuecomment-991799049
|
313
|
+
"""
|
314
|
+
import Cython.Build.Dependencies
|
315
|
+
import Cython.Build.Cythonize
|
316
|
+
import Cython.Utils
|
317
|
+
orig_is_package_dir = Cython.Utils.is_package_dir
|
318
|
+
Cython.Utils.is_package_dir = Cython.Build.Cythonize.is_package_dir = Cython.Build.Dependencies.is_package_dir = Cython.Utils.cached_function(is_package_or_sage_namespace_package_dir)
|
319
|
+
try:
|
320
|
+
yield
|
321
|
+
finally:
|
322
|
+
Cython.Utils.is_package_dir = Cython.Build.Cythonize.is_package_dir = Cython.Build.Dependencies.is_package_dir = orig_is_package_dir
|
323
|
+
|
324
|
+
|
325
|
+
def walk_packages(path=None, prefix='', onerror=None):
|
326
|
+
r"""
|
327
|
+
Yield :class:`pkgutil.ModuleInfo` for all modules recursively on ``path``.
|
328
|
+
|
329
|
+
This version of the standard library function :func:`pkgutil.walk_packages`
|
330
|
+
addresses https://github.com/python/cpython/issues/73444 by handling
|
331
|
+
the implicit namespace packages in the package layout used by Sage;
|
332
|
+
see :func:`is_package_or_sage_namespace_package_dir`.
|
333
|
+
|
334
|
+
INPUT:
|
335
|
+
|
336
|
+
- ``path`` -- list of paths to look for modules in or
|
337
|
+
``None`` (all accessible modules)
|
338
|
+
|
339
|
+
- ``prefix`` -- string to output on the front of every module name
|
340
|
+
on output
|
341
|
+
|
342
|
+
- ``onerror`` -- a function which gets called with one argument (the
|
343
|
+
name of the package which was being imported) if any exception
|
344
|
+
occurs while trying to import a package. If ``None``, ignore
|
345
|
+
:exc:`ImportError` but propagate all other exceptions.
|
346
|
+
|
347
|
+
EXAMPLES::
|
348
|
+
|
349
|
+
sage: sorted(sage.misc.package_dir.walk_packages(sage.misc.__path__)) # a namespace package
|
350
|
+
[..., ModuleInfo(module_finder=FileFinder('.../sage/misc'), name='package_dir', ispkg=False), ...]
|
351
|
+
"""
|
352
|
+
# Adapted from https://github.com/python/cpython/blob/3.11/Lib/pkgutil.py
|
353
|
+
|
354
|
+
def iter_modules(path=None, prefix=''):
|
355
|
+
"""
|
356
|
+
Yield :class:`ModuleInfo` for all submodules on ``path``.
|
357
|
+
"""
|
358
|
+
from pkgutil import get_importer, iter_importers, ModuleInfo
|
359
|
+
|
360
|
+
if path is None:
|
361
|
+
importers = iter_importers()
|
362
|
+
elif isinstance(path, str):
|
363
|
+
raise ValueError("path must be None or list of paths to look for modules in")
|
364
|
+
else:
|
365
|
+
importers = map(get_importer, path)
|
366
|
+
|
367
|
+
yielded = {}
|
368
|
+
for i in importers:
|
369
|
+
for name, ispkg in iter_importer_modules(i, prefix):
|
370
|
+
if name not in yielded:
|
371
|
+
yielded[name] = 1
|
372
|
+
yield ModuleInfo(i, name, ispkg)
|
373
|
+
|
374
|
+
def iter_importer_modules(importer, prefix=''):
|
375
|
+
r"""
|
376
|
+
Yield :class:`ModuleInfo` for all modules of ``importer``.
|
377
|
+
"""
|
378
|
+
from importlib.machinery import FileFinder
|
379
|
+
|
380
|
+
if isinstance(importer, FileFinder):
|
381
|
+
if importer.path is None or not os.path.isdir(importer.path):
|
382
|
+
return
|
383
|
+
|
384
|
+
yielded = {}
|
385
|
+
import inspect
|
386
|
+
try:
|
387
|
+
filenames = os.listdir(importer.path)
|
388
|
+
except OSError:
|
389
|
+
# ignore unreadable directories like import does
|
390
|
+
filenames = []
|
391
|
+
filenames.sort() # handle packages before same-named modules
|
392
|
+
|
393
|
+
for fn in filenames:
|
394
|
+
modname = inspect.getmodulename(fn)
|
395
|
+
if modname and (modname in ['__init__', 'all']
|
396
|
+
or modname.startswith('all__')
|
397
|
+
or modname in yielded):
|
398
|
+
continue
|
399
|
+
|
400
|
+
path = os.path.join(importer.path, fn)
|
401
|
+
ispkg = False
|
402
|
+
|
403
|
+
if not modname and os.path.isdir(path) and '.' not in fn:
|
404
|
+
modname = fn
|
405
|
+
if not (ispkg := is_package_or_sage_namespace_package_dir(path)):
|
406
|
+
continue
|
407
|
+
|
408
|
+
if modname and '.' not in modname:
|
409
|
+
yielded[modname] = 1
|
410
|
+
yield prefix + modname, ispkg
|
411
|
+
|
412
|
+
elif not hasattr(importer, 'iter_modules'):
|
413
|
+
yield from []
|
414
|
+
|
415
|
+
else:
|
416
|
+
yield from importer.iter_modules(prefix)
|
417
|
+
|
418
|
+
def seen(p, m={}):
|
419
|
+
if p in m:
|
420
|
+
return True
|
421
|
+
m[p] = True
|
422
|
+
|
423
|
+
for info in iter_modules(path, prefix):
|
424
|
+
yield info
|
425
|
+
|
426
|
+
if info.ispkg:
|
427
|
+
try:
|
428
|
+
__import__(info.name)
|
429
|
+
except ImportError:
|
430
|
+
if onerror is not None:
|
431
|
+
onerror(info.name)
|
432
|
+
except Exception:
|
433
|
+
if onerror is not None:
|
434
|
+
onerror(info.name)
|
435
|
+
else:
|
436
|
+
raise
|
437
|
+
else:
|
438
|
+
path = getattr(sys.modules[info.name], '__path__', None) or []
|
439
|
+
|
440
|
+
# don't traverse path items we've seen before
|
441
|
+
path = [p for p in path if not seen(p)]
|
442
|
+
|
443
|
+
yield from walk_packages(path, info.name + '.', onerror)
|
444
|
+
|
445
|
+
|
446
|
+
def _all_filename(distribution):
|
447
|
+
if not distribution:
|
448
|
+
return 'all.py'
|
449
|
+
return f"all__{distribution.replace('-', '_')}.py"
|
450
|
+
|
451
|
+
|
452
|
+
def _distribution_from_all_filename(filename):
|
453
|
+
if m := re.match('all(__(.*?))?[.]py', filename):
|
454
|
+
if distribution_per_all_filename := m.group(2):
|
455
|
+
return distribution_per_all_filename.replace('_', '-')
|
456
|
+
return ''
|
457
|
+
return False
|
458
|
+
|
459
|
+
|
460
|
+
if __name__ == '__main__':
|
461
|
+
|
462
|
+
from argparse import ArgumentParser
|
463
|
+
|
464
|
+
parser = ArgumentParser(prog="sage --fixdistributions",
|
465
|
+
description="Maintenance tool for distribution packages of the Sage library",
|
466
|
+
epilog="By default, '%(prog)s' shows the distribution of each file.")
|
467
|
+
parser.add_argument('--add', metavar='DISTRIBUTION', type=str, default=None,
|
468
|
+
help=("add a 'sage_setup: DISTRIBUTION' directive to FILES; "
|
469
|
+
"do not change files that already have a nonempty directive"))
|
470
|
+
parser.add_argument('--set', metavar='DISTRIBUTION', type=str, default=None,
|
471
|
+
help="add or update the 'sage_setup: DISTRIBUTION' directive in FILES")
|
472
|
+
parser.add_argument('--from-egg-info', action='store_true', default=False,
|
473
|
+
help="take FILES from pkgs/DISTRIBUTION/DISTRIBUTION.egg-info/SOURCES.txt")
|
474
|
+
parser.add_argument("filename", metavar='FILES', nargs='*', type=str,
|
475
|
+
help=("source files or directories (default: all files from SAGE_SRC, "
|
476
|
+
"unless --from-egg-info, --add, or --set are used)"))
|
477
|
+
|
478
|
+
args = parser.parse_args()
|
479
|
+
|
480
|
+
distribution = args.set or args.add or ''
|
481
|
+
|
482
|
+
if distribution == 'all':
|
483
|
+
distributions = ["sagemath-bliss",
|
484
|
+
"sagemath-coxeter3",
|
485
|
+
"sagemath-mcqd",
|
486
|
+
"sagemath-meataxe",
|
487
|
+
"sagemath-sirocco",
|
488
|
+
"sagemath-tdlib",
|
489
|
+
"sagemath-environment",
|
490
|
+
"sagemath-categories",
|
491
|
+
"sagemath-repl",
|
492
|
+
"sagemath-objects"]
|
493
|
+
else:
|
494
|
+
distributions = [distribution.replace('_', '-')]
|
495
|
+
|
496
|
+
if args.from_egg_info:
|
497
|
+
if not distribution:
|
498
|
+
print("Switch '--from-egg-info' must be used with either "
|
499
|
+
"'--add DISTRIBUTION' or '--set DISTRIBUTION'")
|
500
|
+
sys.exit(1)
|
501
|
+
elif not args.filename:
|
502
|
+
if distribution:
|
503
|
+
print("Switches '--add' and '--set' require the switch '--from-egg-info' "
|
504
|
+
"or one or more file or directory names")
|
505
|
+
sys.exit(1)
|
506
|
+
from sage.env import SAGE_SRC
|
507
|
+
if (not SAGE_SRC
|
508
|
+
or not os.path.exists(os.path.join(SAGE_SRC, 'sage'))
|
509
|
+
or not os.path.exists(os.path.join(SAGE_SRC, 'conftest_test.py'))):
|
510
|
+
print(f'{SAGE_SRC=} does not seem to contain a copy of the Sage source tree')
|
511
|
+
sys.exit(1)
|
512
|
+
args.filename = [os.path.join(SAGE_SRC, 'sage')]
|
513
|
+
|
514
|
+
ordinary_packages = set()
|
515
|
+
package_distributions_per_directives = defaultdict(set) # path -> set of strings (distributions)
|
516
|
+
package_distributions_per_all_files = defaultdict(set) # path -> set of strings (distributions)
|
517
|
+
|
518
|
+
def handle_file(root, file):
|
519
|
+
path = os.path.join(root, file)
|
520
|
+
if args.set is not None:
|
521
|
+
update_distribution(path, distribution, verbose=True)
|
522
|
+
file_distribution = distribution
|
523
|
+
elif args.add is not None:
|
524
|
+
if not (file_distribution := read_distribution(path)):
|
525
|
+
update_distribution(path, distribution, verbose=True)
|
526
|
+
file_distribution = distribution
|
527
|
+
else:
|
528
|
+
file_distribution = read_distribution(path)
|
529
|
+
print(f'{path}: file in distribution {file_distribution!r}')
|
530
|
+
package_distributions_per_directives[root].add(file_distribution)
|
531
|
+
if file.startswith('__init__.'):
|
532
|
+
ordinary_packages.add(root)
|
533
|
+
elif (distribution_per_all_filename := _distribution_from_all_filename(file)) is False:
|
534
|
+
# Not an all*.py file.
|
535
|
+
pass
|
536
|
+
elif not distribution_per_all_filename:
|
537
|
+
# An all.py file.
|
538
|
+
if file_distribution:
|
539
|
+
# The all.py is declared to belong to a named distribution, that's OK
|
540
|
+
package_distributions_per_all_files[root].add(file_distribution)
|
541
|
+
else:
|
542
|
+
pass
|
543
|
+
else:
|
544
|
+
# An all__*.py file
|
545
|
+
if distribution_per_all_filename != file_distribution:
|
546
|
+
print(f'{path}: file should go in distribution {distribution_per_all_filename!r}, not {file_distribution!r}')
|
547
|
+
package_distributions_per_all_files[root].add(distribution_per_all_filename)
|
548
|
+
|
549
|
+
for distribution in distributions:
|
550
|
+
|
551
|
+
paths = list(args.filename)
|
552
|
+
|
553
|
+
if args.from_egg_info:
|
554
|
+
from sage.env import SAGE_ROOT
|
555
|
+
if not distribution:
|
556
|
+
print("Switch '--from-egg-info' must be used with either "
|
557
|
+
"'--add DISTRIBUTION' or '--set DISTRIBUTION'")
|
558
|
+
sys.exit(1)
|
559
|
+
if not SAGE_ROOT:
|
560
|
+
print(f'{SAGE_ROOT=} does not seem to contain a copy of the Sage source root')
|
561
|
+
sys.exit(1)
|
562
|
+
distribution_dir = os.path.join(SAGE_ROOT, 'pkgs', distribution)
|
563
|
+
if not os.path.exists(distribution_dir):
|
564
|
+
print(f'{distribution_dir} does not exist')
|
565
|
+
sys.exit(1)
|
566
|
+
distribution_underscore = distribution.replace('-', '_')
|
567
|
+
try:
|
568
|
+
with open(os.path.join(distribution_dir,
|
569
|
+
f'{distribution_underscore}.egg-info', 'SOURCES.txt')) as f:
|
570
|
+
paths.extend(os.path.join(SAGE_ROOT, 'src', line.strip())
|
571
|
+
for line in f
|
572
|
+
if line.startswith('sage/'))
|
573
|
+
print(f"sage --fixdistributions: found egg-info of distribution {distribution!r}")
|
574
|
+
except FileNotFoundError:
|
575
|
+
if len(distributions) > 1:
|
576
|
+
print(f"sage --fixdistributions: distribution {distribution!r} does not have egg-info, skipping it; "
|
577
|
+
f"run 'make {distribution_underscore}-sdist' or 'make {distribution_underscore}' to create it")
|
578
|
+
continue
|
579
|
+
else:
|
580
|
+
print(f"sage --fixdistributions: distribution {distribution!r} does not have egg-info; "
|
581
|
+
f"run 'make {distribution_underscore}-sdist' or 'make {distribution_underscore}' to create it")
|
582
|
+
sys.exit(1)
|
583
|
+
|
584
|
+
for path in paths:
|
585
|
+
path = os.path.relpath(path)
|
586
|
+
if os.path.isdir(path):
|
587
|
+
if not is_package_or_sage_namespace_package_dir(path):
|
588
|
+
print(f'{path}: non-package directory')
|
589
|
+
else:
|
590
|
+
for root, dirs, files in os.walk(path):
|
591
|
+
for dir in sorted(dirs):
|
592
|
+
path = os.path.join(root, dir)
|
593
|
+
if any(dir.startswith(prefix) for prefix in ['.', 'build', 'dist', '__pycache__', '_vendor', '.tox']):
|
594
|
+
# Silently skip
|
595
|
+
dirs.remove(dir)
|
596
|
+
elif not is_package_or_sage_namespace_package_dir(path):
|
597
|
+
print(f'{path}: non-package directory')
|
598
|
+
dirs.remove(dir)
|
599
|
+
for file in sorted(files):
|
600
|
+
if any(file.endswith(ext) for ext in [".pyc", ".pyo", ".bak", ".so", "~"]):
|
601
|
+
continue
|
602
|
+
handle_file(root, file)
|
603
|
+
else:
|
604
|
+
handle_file(*os.path.split(path))
|
605
|
+
|
606
|
+
print(f"sage --fixdistributions: checking consistency")
|
607
|
+
|
608
|
+
for package in ordinary_packages:
|
609
|
+
if len(package_distributions_per_directives[package]) > 1:
|
610
|
+
print(f'{package}: ordinary packages (with __init__.py) cannot be split in several distributions ('
|
611
|
+
+ ', '.join(f'{dist!r}'
|
612
|
+
for dist in sorted(package_distributions_per_directives[package])) + ')')
|
613
|
+
|
614
|
+
for package, distributions_per_directives in package_distributions_per_directives.items():
|
615
|
+
if package in ordinary_packages:
|
616
|
+
pass
|
617
|
+
elif ((missing_all_files := distributions_per_directives - package_distributions_per_all_files[package])
|
618
|
+
and not (missing_all_files == {''} and len(distributions_per_directives) < 2)):
|
619
|
+
s = '' if len(missing_all_files) == 1 else 's'
|
620
|
+
print(f'{package}: missing file{s} ' + ', '.join(_all_filename(distribution)
|
621
|
+
for distribution in missing_all_files))
|