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.
Files changed (70) hide show
  1. passagemath_environment-10.4.1.data/scripts/sage +1140 -0
  2. passagemath_environment-10.4.1.data/scripts/sage-env +667 -0
  3. passagemath_environment-10.4.1.data/scripts/sage-num-threads.py +105 -0
  4. passagemath_environment-10.4.1.data/scripts/sage-python +2 -0
  5. passagemath_environment-10.4.1.data/scripts/sage-venv-config +42 -0
  6. passagemath_environment-10.4.1.data/scripts/sage-version.sh +9 -0
  7. passagemath_environment-10.4.1.dist-info/METADATA +76 -0
  8. passagemath_environment-10.4.1.dist-info/RECORD +70 -0
  9. passagemath_environment-10.4.1.dist-info/WHEEL +5 -0
  10. passagemath_environment-10.4.1.dist-info/top_level.txt +1 -0
  11. sage/all__sagemath_environment.py +4 -0
  12. sage/env.py +496 -0
  13. sage/features/__init__.py +981 -0
  14. sage/features/all.py +126 -0
  15. sage/features/bliss.py +85 -0
  16. sage/features/cddlib.py +38 -0
  17. sage/features/coxeter3.py +45 -0
  18. sage/features/csdp.py +83 -0
  19. sage/features/cython.py +38 -0
  20. sage/features/databases.py +302 -0
  21. sage/features/dvipng.py +40 -0
  22. sage/features/ecm.py +42 -0
  23. sage/features/ffmpeg.py +119 -0
  24. sage/features/four_ti_2.py +55 -0
  25. sage/features/fricas.py +66 -0
  26. sage/features/gap.py +86 -0
  27. sage/features/gfan.py +38 -0
  28. sage/features/giac.py +30 -0
  29. sage/features/graph_generators.py +171 -0
  30. sage/features/graphviz.py +117 -0
  31. sage/features/igraph.py +44 -0
  32. sage/features/imagemagick.py +138 -0
  33. sage/features/interfaces.py +256 -0
  34. sage/features/internet.py +65 -0
  35. sage/features/jmol.py +44 -0
  36. sage/features/join_feature.py +146 -0
  37. sage/features/kenzo.py +77 -0
  38. sage/features/latex.py +300 -0
  39. sage/features/latte.py +85 -0
  40. sage/features/lrs.py +164 -0
  41. sage/features/mcqd.py +45 -0
  42. sage/features/meataxe.py +46 -0
  43. sage/features/mip_backends.py +114 -0
  44. sage/features/msolve.py +68 -0
  45. sage/features/nauty.py +70 -0
  46. sage/features/normaliz.py +43 -0
  47. sage/features/palp.py +65 -0
  48. sage/features/pandoc.py +42 -0
  49. sage/features/pdf2svg.py +41 -0
  50. sage/features/phitigra.py +42 -0
  51. sage/features/pkg_systems.py +195 -0
  52. sage/features/polymake.py +43 -0
  53. sage/features/poppler.py +58 -0
  54. sage/features/rubiks.py +180 -0
  55. sage/features/sagemath.py +1205 -0
  56. sage/features/sat.py +103 -0
  57. sage/features/singular.py +48 -0
  58. sage/features/sirocco.py +45 -0
  59. sage/features/sphinx.py +71 -0
  60. sage/features/standard.py +38 -0
  61. sage/features/symengine_py.py +44 -0
  62. sage/features/tdlib.py +38 -0
  63. sage/features/threejs.py +75 -0
  64. sage/features/topcom.py +67 -0
  65. sage/misc/all__sagemath_environment.py +2 -0
  66. sage/misc/package.py +570 -0
  67. sage/misc/package_dir.py +621 -0
  68. sage/misc/temporary_file.py +546 -0
  69. sage/misc/viewer.py +369 -0
  70. sage/version.py +5 -0
@@ -0,0 +1,981 @@
1
+ # sage_setup: distribution = sagemath-environment
2
+ r"""
3
+ Testing for features of the environment at runtime
4
+
5
+ A computation can require a certain package to be installed in the runtime
6
+ environment. Abstractly such a package describes a :class:`Feature` which can
7
+ be tested for at runtime. It can be of various kinds, most prominently an
8
+ :class:`Executable` in the ``PATH``, a :class:`PythonModule`, or an additional
9
+ package for some installed
10
+ system such as a :class:`~sage.features.gap.GapPackage`.
11
+
12
+ AUTHORS:
13
+
14
+ - Julian Rüth (2016-04-07): Initial version
15
+
16
+ - Jeroen Demeyer (2018-02-12): Refactoring and clean up
17
+
18
+ EXAMPLES:
19
+
20
+ Some generic features are available for common cases. For example, to
21
+ test for the existence of a binary, one can use an :class:`Executable`
22
+ feature::
23
+
24
+ sage: from sage.features import Executable
25
+ sage: Executable(name='sh', executable='sh').is_present()
26
+ FeatureTestResult('sh', True)
27
+
28
+ Here we test whether the grape GAP package is available::
29
+
30
+ sage: from sage.features.gap import GapPackage
31
+ sage: GapPackage("grape", spkg='gap_packages').is_present() # optional - gap_package_grape
32
+ FeatureTestResult('gap_package_grape', True)
33
+
34
+ Note that a :class:`FeatureTestResult` acts like a bool in most contexts::
35
+
36
+ sage: if Executable(name='sh', executable='sh').is_present(): "present."
37
+ 'present.'
38
+
39
+ When one wants to raise an error if the feature is not available, one
40
+ can use the ``require`` method::
41
+
42
+ sage: Executable(name='sh', executable='sh').require()
43
+
44
+ sage: Executable(name='random', executable='randomOochoz6x', spkg='random', url='http://rand.om').require() # optional - sage_spkg
45
+ Traceback (most recent call last):
46
+ ...
47
+ FeatureNotPresentError: random is not available.
48
+ Executable 'randomOochoz6x' not found on PATH.
49
+ ...try to run...sage -i random...
50
+ Further installation instructions might be available at http://rand.om.
51
+
52
+ As can be seen above, features try to produce helpful error messages.
53
+ """
54
+
55
+ # *****************************************************************************
56
+ # Copyright (C) 2016 Julian Rüth
57
+ # 2018 Jeroen Demeyer
58
+ # 2018 Timo Kaufmann
59
+ # 2019-2022 Matthias Koeppe
60
+ # 2021 Kwankyu Lee
61
+ #
62
+ # Distributed under the terms of the GNU General Public License (GPL)
63
+ # as published by the Free Software Foundation; either version 2 of
64
+ # the License, or (at your option) any later version.
65
+ # https://www.gnu.org/licenses/
66
+ # *****************************************************************************
67
+
68
+ from __future__ import annotations
69
+
70
+ import os
71
+ import shutil
72
+ from pathlib import Path
73
+
74
+ from sage.env import SAGE_SHARE, SAGE_LOCAL, SAGE_VENV
75
+
76
+
77
+ class TrivialClasscallMetaClass(type):
78
+ """
79
+ A trivial version of :class:`sage.misc.classcall_metaclass.ClasscallMetaclass` without Cython dependencies.
80
+ """
81
+ def __call__(cls, *args, **kwds):
82
+ r"""
83
+ This method implements ``cls(<some arguments>)``.
84
+ """
85
+ if hasattr(cls, '__classcall__'):
86
+ return cls.__classcall__(cls, *args, **kwds)
87
+ else:
88
+ return type.__call__(cls, *args, **kwds)
89
+
90
+
91
+ _trivial_unique_representation_cache = dict()
92
+
93
+
94
+ class TrivialUniqueRepresentation(metaclass=TrivialClasscallMetaClass):
95
+ r"""
96
+ A trivial version of :class:`UniqueRepresentation` without Cython dependencies.
97
+ """
98
+
99
+ @staticmethod
100
+ def __classcall__(cls, *args, **options):
101
+ r"""
102
+ Construct a new object of this class or reuse an existing one.
103
+ """
104
+ key = (cls, tuple(args), frozenset(options.items()))
105
+ cached = _trivial_unique_representation_cache.get(key, None)
106
+ if cached is None:
107
+ cached = _trivial_unique_representation_cache[key] = type.__call__(cls, *args, **options)
108
+ return cached
109
+
110
+
111
+ class Feature(TrivialUniqueRepresentation):
112
+ r"""
113
+ A feature of the runtime environment.
114
+
115
+ INPUT:
116
+
117
+ - ``name`` -- string; name of the feature. This should be suitable as an optional tag
118
+ for the Sage doctester, i.e., lowercase alphanumeric with underscores (``_``) allowed;
119
+ features that correspond to Python modules/packages may use periods (``.``)
120
+
121
+ - ``spkg`` -- string; name of the SPKG providing the feature
122
+
123
+ - ``description`` -- string (optional); plain English description of the feature
124
+
125
+ - ``url`` -- a URL for the upstream package providing the feature
126
+
127
+ - ``type`` -- string; one of ``'standard'``, ``'optional'`` (default), ``'experimental'``
128
+
129
+ Overwrite :meth:`_is_present` to add feature checks.
130
+
131
+ EXAMPLES::
132
+
133
+ sage: from sage.features.gap import GapPackage
134
+ sage: GapPackage("grape", spkg='gap_packages') # indirect doctest
135
+ Feature('gap_package_grape')
136
+
137
+ For efficiency, features are unique::
138
+
139
+ sage: GapPackage("grape") is GapPackage("grape")
140
+ True
141
+ """
142
+ def __init__(self, name, spkg=None, url=None, description=None, type='optional'):
143
+ r"""
144
+ TESTS::
145
+
146
+ sage: from sage.features import Feature
147
+ sage: from sage.features.gap import GapPackage
148
+ sage: isinstance(GapPackage("grape", spkg='gap_packages'), Feature) # indirect doctest
149
+ True
150
+ """
151
+ self.name = name
152
+ self.spkg = spkg
153
+ self.url = url
154
+ self.description = description
155
+
156
+ self._cache_is_present = None
157
+ self._cache_resolution = None
158
+ self._hidden = False
159
+ self._type = type
160
+
161
+ try:
162
+ from sage.misc.package import spkg_type
163
+ except ImportError: # may have been surgically removed in a downstream distribution
164
+ pass
165
+ else:
166
+ if spkg and (t := spkg_type(spkg)) not in (type, None):
167
+ from warnings import warn
168
+ warn(f'Feature {name} is declared {type}, '
169
+ f'but it is provided by {spkg}, '
170
+ f'which is declared {t} in SAGE_ROOT/build/pkgs',
171
+ stacklevel=3)
172
+
173
+ def is_present(self):
174
+ r"""
175
+ Return whether the feature is present.
176
+
177
+ OUTPUT:
178
+
179
+ A :class:`FeatureTestResult` which can be used as a boolean and
180
+ contains additional information about the feature test.
181
+
182
+ EXAMPLES::
183
+
184
+ sage: from sage.features.gap import GapPackage
185
+ sage: GapPackage("grape", spkg='gap_packages').is_present() # optional - gap_package_grape
186
+ FeatureTestResult('gap_package_grape', True)
187
+ sage: GapPackage("NOT_A_PACKAGE", spkg='gap_packages').is_present()
188
+ FeatureTestResult('gap_package_NOT_A_PACKAGE', False)
189
+
190
+ The result is cached::
191
+
192
+ sage: from sage.features import Feature
193
+ sage: class TestFeature(Feature):
194
+ ....: def _is_present(self):
195
+ ....: print("checking presence")
196
+ ....: return True
197
+ sage: TestFeature("test").is_present()
198
+ checking presence
199
+ FeatureTestResult('test', True)
200
+ sage: TestFeature("test").is_present()
201
+ FeatureTestResult('test', True)
202
+ sage: TestFeature("other").is_present()
203
+ checking presence
204
+ FeatureTestResult('other', True)
205
+ sage: TestFeature("other").is_present()
206
+ FeatureTestResult('other', True)
207
+ """
208
+ # We do not use @cached_method here because we wish to use
209
+ # Feature early in the build system of sagelib.
210
+ if self._cache_is_present is None:
211
+ res = self._is_present()
212
+ if not isinstance(res, FeatureTestResult):
213
+ res = FeatureTestResult(self, res)
214
+ self._cache_is_present = res
215
+
216
+ if self._hidden:
217
+ return FeatureTestResult(self, False, reason="Feature `{name}` is hidden.".format(name=self.name))
218
+
219
+ return self._cache_is_present
220
+
221
+ def _is_present(self):
222
+ r"""
223
+ Override this in a derived class to implement the feature check.
224
+
225
+ This should return either an instance of
226
+ :class:`FeatureTestResult` or a boolean.
227
+ """
228
+ raise NotImplementedError("_is_present not implemented for feature {!r}".format(self.name))
229
+
230
+ def require(self):
231
+ r"""
232
+ Raise a :exc:`FeatureNotPresentError` if the feature is not present.
233
+
234
+ EXAMPLES::
235
+
236
+ sage: from sage.features.gap import GapPackage
237
+ sage: GapPackage("ve1EeThu").require() # needs sage.libs.gap
238
+ Traceback (most recent call last):
239
+ ...
240
+ FeatureNotPresentError: gap_package_ve1EeThu is not available.
241
+ `LoadPackage("ve1EeThu")` evaluated to `fail` in GAP.
242
+ """
243
+ presence = self.is_present()
244
+ if not presence:
245
+ raise FeatureNotPresentError(self, presence.reason, presence.resolution)
246
+
247
+ def __repr__(self):
248
+ r"""
249
+ Return a printable representation of this object.
250
+
251
+ EXAMPLES::
252
+
253
+ sage: from sage.features.gap import GapPackage
254
+ sage: GapPackage("grape") # indirect doctest
255
+ Feature('gap_package_grape')
256
+ """
257
+ description = f'{self.name!r}: {self.description}' if self.description else f'{self.name!r}'
258
+ return f'Feature({description})'
259
+
260
+ def _spkg_type(self):
261
+ r"""
262
+ Return the type of this feature.
263
+
264
+ For features provided by an SPKG in the Sage distribution,
265
+ this should match the SPKG type, or a warning will be issued.
266
+
267
+ EXAMPLES::
268
+
269
+ sage: from sage.features.databases import DatabaseCremona
270
+ sage: DatabaseCremona()._spkg_type()
271
+ 'optional'
272
+
273
+ OUTPUT:
274
+
275
+ The type as a string in ``('base', 'standard', 'optional', 'experimental')``.
276
+ """
277
+ return self._type
278
+
279
+ def resolution(self):
280
+ r"""
281
+ Return a suggestion on how to make :meth:`is_present` pass if it did not
282
+ pass.
283
+
284
+ OUTPUT: string
285
+
286
+ EXAMPLES::
287
+
288
+ sage: from sage.features import Executable
289
+ sage: Executable(name='CSDP', spkg='csdp', executable='theta', url='https://github.com/dimpase/csdp').resolution() # optional - sage_spkg
290
+ '...To install CSDP...you can try to run...sage -i csdp...Further installation instructions might be available at https://github.com/dimpase/csdp.'
291
+ """
292
+ if self._hidden:
293
+ return "Use method `unhide` to make it available again."
294
+ if self._cache_resolution is not None:
295
+ return self._cache_resolution
296
+ lines = []
297
+ if self.spkg:
298
+ for ps in package_systems():
299
+ lines.append(ps.spkg_installation_hint(self.spkg, feature=self.name))
300
+ if self.url:
301
+ lines.append("Further installation instructions might be available at {url}.".format(url=self.url))
302
+ self._cache_resolution = "\n".join(lines)
303
+ return self._cache_resolution
304
+
305
+ def joined_features(self):
306
+ r"""
307
+ Return a list of features that ``self`` is the join of.
308
+
309
+ OUTPUT:
310
+
311
+ A (possibly empty) list of instances of :class:`Feature`.
312
+
313
+ EXAMPLES::
314
+
315
+ sage: from sage.features.graphviz import Graphviz
316
+ sage: Graphviz().joined_features()
317
+ [Feature('dot'), Feature('neato'), Feature('twopi')]
318
+ sage: from sage.features.sagemath import sage__rings__function_field
319
+ sage: sage__rings__function_field().joined_features()
320
+ [Feature('sage.rings.function_field.function_field_polymod'),
321
+ Feature('sage.libs.singular'),
322
+ Feature('sage.libs.singular.singular'),
323
+ Feature('sage.interfaces.singular')]
324
+ sage: from sage.features.interfaces import Mathematica
325
+ sage: Mathematica().joined_features()
326
+ []
327
+ """
328
+ from sage.features.join_feature import JoinFeature
329
+ res = []
330
+ if isinstance(self, JoinFeature):
331
+ for f in self._features:
332
+ res += [f] + f.joined_features()
333
+ return res
334
+
335
+ def is_standard(self):
336
+ r"""
337
+ Return whether this feature corresponds to a standard SPKG.
338
+
339
+ EXAMPLES::
340
+
341
+ sage: from sage.features.databases import DatabaseCremona
342
+ sage: DatabaseCremona().is_standard()
343
+ False
344
+ """
345
+ if self.name.startswith('sage.'):
346
+ return True
347
+ return self._spkg_type() == 'standard'
348
+
349
+ def is_optional(self):
350
+ r"""
351
+ Return whether this feature corresponds to an optional SPKG.
352
+
353
+ EXAMPLES::
354
+
355
+ sage: from sage.features.databases import DatabaseCremona
356
+ sage: DatabaseCremona().is_optional()
357
+ True
358
+ """
359
+ return self._spkg_type() == 'optional'
360
+
361
+ def hide(self):
362
+ r"""
363
+ Hide this feature. For example this is used when the doctest option
364
+ ``--hide`` is set. Setting an installed feature as hidden pretends
365
+ that it is not available. To revert this use :meth:`unhide`.
366
+
367
+ EXAMPLES:
368
+
369
+ Benzene is an optional SPKG. The following test fails if it is hidden or
370
+ not installed. Thus, in the second invocation the optional tag is needed::
371
+
372
+ sage: from sage.features.graph_generators import Benzene
373
+ sage: Benzene().hide()
374
+ sage: len(list(graphs.fusenes(2))) # needs sage.graphs
375
+ Traceback (most recent call last):
376
+ ...
377
+ FeatureNotPresentError: benzene is not available.
378
+ Feature `benzene` is hidden.
379
+ Use method `unhide` to make it available again.
380
+
381
+ sage: Benzene().unhide() # optional - benzene, needs sage.graphs
382
+ sage: len(list(graphs.fusenes(2))) # optional - benzene, needs sage.graphs
383
+ 1
384
+ """
385
+ self._hidden = True
386
+
387
+ def unhide(self):
388
+ r"""
389
+ Revert what :meth:`hide` did.
390
+
391
+ EXAMPLES:
392
+
393
+ sage: from sage.features.sagemath import sage__plot
394
+ sage: sage__plot().hide()
395
+ sage: sage__plot().is_present()
396
+ FeatureTestResult('sage.plot', False)
397
+ sage: sage__plot().unhide() # needs sage.plot
398
+ sage: sage__plot().is_present() # needs sage.plot
399
+ FeatureTestResult('sage.plot', True)
400
+ """
401
+ self._hidden = False
402
+
403
+ def is_hidden(self):
404
+ r"""
405
+ Return whether ``self`` is present but currently hidden.
406
+
407
+ EXAMPLES:
408
+
409
+ sage: from sage.features.sagemath import sage__plot
410
+ sage: sage__plot().hide()
411
+ sage: sage__plot().is_hidden() # needs sage.plot
412
+ True
413
+ sage: sage__plot().unhide()
414
+ sage: sage__plot().is_hidden()
415
+ False
416
+ """
417
+ if self._hidden and self._is_present():
418
+ return True
419
+ return False
420
+
421
+ class FeatureNotPresentError(RuntimeError):
422
+ r"""
423
+ A missing feature error.
424
+
425
+ EXAMPLES::
426
+
427
+ sage: from sage.features import Feature, FeatureTestResult
428
+ sage: class Missing(Feature):
429
+ ....: def _is_present(self):
430
+ ....: return False
431
+
432
+ sage: Missing(name='missing').require()
433
+ Traceback (most recent call last):
434
+ ...
435
+ FeatureNotPresentError: missing is not available.
436
+ """
437
+ def __init__(self, feature, reason=None, resolution=None):
438
+ self.feature = feature
439
+ self.reason = reason
440
+ self._resolution = resolution
441
+
442
+ @property
443
+ def resolution(self):
444
+ if self._resolution:
445
+ return self._resolution
446
+ return self.feature.resolution()
447
+
448
+ def __str__(self):
449
+ r"""
450
+ Return the error message.
451
+
452
+ EXAMPLES::
453
+
454
+ sage: from sage.features.gap import GapPackage
455
+ sage: GapPackage("gapZuHoh8Uu").require() # indirect doctest # needs sage.libs.gap
456
+ Traceback (most recent call last):
457
+ ...
458
+ FeatureNotPresentError: gap_package_gapZuHoh8Uu is not available.
459
+ `LoadPackage("gapZuHoh8Uu")` evaluated to `fail` in GAP.
460
+ """
461
+ lines = ["{feature} is not available.".format(feature=self.feature.name)]
462
+ if self.reason:
463
+ lines.append(self.reason)
464
+ resolution = self.resolution
465
+ if resolution:
466
+ lines.append(str(resolution))
467
+ return "\n".join(lines)
468
+
469
+
470
+ class FeatureTestResult():
471
+ r"""
472
+ The result of a :meth:`Feature.is_present` call.
473
+
474
+ Behaves like a boolean with some extra data which may explain why a feature
475
+ is not present and how this may be resolved.
476
+
477
+ EXAMPLES::
478
+
479
+ sage: from sage.features.gap import GapPackage
480
+ sage: presence = GapPackage("NOT_A_PACKAGE").is_present(); presence # indirect doctest
481
+ FeatureTestResult('gap_package_NOT_A_PACKAGE', False)
482
+ sage: bool(presence)
483
+ False
484
+
485
+ Explanatory messages might be available as ``reason`` and
486
+ ``resolution``::
487
+
488
+ sage: presence.reason # needs sage.libs.gap
489
+ '`LoadPackage("NOT_A_PACKAGE")` evaluated to `fail` in GAP.'
490
+ sage: bool(presence.resolution)
491
+ False
492
+
493
+ If a feature is not present, ``resolution`` defaults to
494
+ ``feature.resolution()`` if this is defined. If you do not want to use this
495
+ default you need explicitly set ``resolution`` to a string::
496
+
497
+ sage: from sage.features import FeatureTestResult
498
+ sage: package = GapPackage("NOT_A_PACKAGE", spkg='no_package')
499
+ sage: str(FeatureTestResult(package, True).resolution) # optional - sage_spkg
500
+ '...To install gap_package_NOT_A_PACKAGE...you can try to run...sage -i no_package...'
501
+ sage: str(FeatureTestResult(package, False).resolution) # optional - sage_spkg
502
+ '...To install gap_package_NOT_A_PACKAGE...you can try to run...sage -i no_package...'
503
+ sage: FeatureTestResult(package, False, resolution='rtm').resolution
504
+ 'rtm'
505
+ """
506
+ def __init__(self, feature, is_present, reason=None, resolution=None):
507
+ r"""
508
+ TESTS::
509
+
510
+ sage: from sage.features import Executable, FeatureTestResult
511
+ sage: isinstance(Executable(name='sh', executable='sh').is_present(), FeatureTestResult)
512
+ True
513
+ """
514
+ self.feature = feature
515
+ self.is_present = is_present
516
+ self.reason = reason
517
+ self._resolution = resolution
518
+
519
+ @property
520
+ def resolution(self):
521
+ if self._resolution:
522
+ return self._resolution
523
+ return self.feature.resolution()
524
+
525
+ def __bool__(self):
526
+ r"""
527
+ Whether the tested :class:`Feature` is present.
528
+
529
+ TESTS::
530
+
531
+ sage: from sage.features import Feature, FeatureTestResult
532
+ sage: bool(FeatureTestResult(Feature("SomePresentFeature"), True)) # indirect doctest
533
+ True
534
+ sage: bool(FeatureTestResult(Feature("SomeMissingFeature"), False))
535
+ False
536
+ """
537
+ return bool(self.is_present)
538
+
539
+ def __repr__(self):
540
+ r"""
541
+ TESTS::
542
+
543
+ sage: from sage.features import Feature, FeatureTestResult
544
+ sage: FeatureTestResult(Feature("SomePresentFeature"), True) # indirect doctest
545
+ FeatureTestResult('SomePresentFeature', True)
546
+ """
547
+ return "FeatureTestResult({feature!r}, {is_present!r})".format(feature=self.feature.name, is_present=self.is_present)
548
+
549
+
550
+ _cache_package_systems = None
551
+
552
+
553
+ def package_systems():
554
+ """
555
+ Return a list of :class:`~sage.features.pkg_systems.PackageSystem` objects
556
+ representing the available package systems.
557
+
558
+ The list is ordered by decreasing preference.
559
+
560
+ EXAMPLES::
561
+
562
+ sage: from sage.features import package_systems
563
+ sage: package_systems() # random
564
+ [Feature('homebrew'), Feature('sage_spkg'), Feature('pip')]
565
+ """
566
+ # The current implementation never returns more than one system.
567
+ from subprocess import run, CalledProcessError, PIPE
568
+ global _cache_package_systems
569
+ if _cache_package_systems is None:
570
+ from .pkg_systems import PackageSystem, SagePackageSystem, PipPackageSystem
571
+ _cache_package_systems = []
572
+ # Try to use scripts from SAGE_ROOT (or an installation of sage_bootstrap)
573
+ # to obtain system package advice.
574
+ try:
575
+ proc = run('sage-guess-package-system', shell=True, capture_output=True, text=True, check=True)
576
+ system_name = proc.stdout.strip()
577
+ if system_name != 'unknown':
578
+ _cache_package_systems = [PackageSystem(system_name)]
579
+ except CalledProcessError:
580
+ pass
581
+ more_package_systems = [SagePackageSystem(), PipPackageSystem()]
582
+ _cache_package_systems += [ps for ps in more_package_systems if ps.is_present()]
583
+
584
+ return _cache_package_systems
585
+
586
+
587
+ class FileFeature(Feature):
588
+ r"""
589
+ Base class for features that describe a file or directory in the file system.
590
+
591
+ A subclass should implement a method :meth:`absolute_filename`.
592
+
593
+ EXAMPLES:
594
+
595
+ Two direct concrete subclasses of :class:`FileFeature` are defined::
596
+
597
+ sage: from sage.features import StaticFile, Executable, FileFeature
598
+ sage: issubclass(StaticFile, FileFeature)
599
+ True
600
+ sage: issubclass(Executable, FileFeature)
601
+ True
602
+
603
+ To work with the file described by the feature, use the method :meth:`absolute_filename`.
604
+ A :exc:`FeatureNotPresentError` is raised if the file cannot be found::
605
+
606
+ sage: Executable(name='does-not-exist', executable='does-not-exist-xxxxyxyyxyy').absolute_filename()
607
+ Traceback (most recent call last):
608
+ ...
609
+ sage.features.FeatureNotPresentError: does-not-exist is not available.
610
+ Executable 'does-not-exist-xxxxyxyyxyy' not found on PATH.
611
+
612
+ A :class:`FileFeature` also provides the :meth:`is_present` method to test for
613
+ the presence of the file at run time. This is inherited from the base class
614
+ :class:`Feature`::
615
+
616
+ sage: Executable(name='sh', executable='sh').is_present()
617
+ FeatureTestResult('sh', True)
618
+ """
619
+ def _is_present(self):
620
+ r"""
621
+ Whether the file is present.
622
+
623
+ EXAMPLES::
624
+
625
+ sage: from sage.features import StaticFile
626
+ sage: StaticFile(name='no_such_file', filename='KaT1aihu', spkg='some_spkg', url='http://rand.om').is_present()
627
+ FeatureTestResult('no_such_file', False)
628
+ """
629
+ try:
630
+ abspath = self.absolute_filename()
631
+ return FeatureTestResult(self, True, reason="Found at `{abspath}`.".format(abspath=abspath))
632
+ except FeatureNotPresentError as e:
633
+ return FeatureTestResult(self, False, reason=e.reason, resolution=e.resolution)
634
+
635
+ def absolute_filename(self) -> str:
636
+ r"""
637
+ The absolute path of the file as a string.
638
+
639
+ Concrete subclasses must override this abstract method.
640
+
641
+ TESTS::
642
+
643
+ sage: from sage.features import FileFeature
644
+ sage: FileFeature(name='abstract_file').absolute_filename()
645
+ Traceback (most recent call last):
646
+ ...
647
+ NotImplementedError
648
+ """
649
+ # We do not use sage.misc.abstract_method here because that is provided by
650
+ # the distribution sagemath-objects, which is not an install-requires of
651
+ # the distribution sagemath-environment.
652
+ raise NotImplementedError
653
+
654
+
655
+ class Executable(FileFeature):
656
+ r"""
657
+ A feature describing an executable in the ``PATH``.
658
+
659
+ In an installation of Sage with ``SAGE_LOCAL`` different from ``SAGE_VENV``, the
660
+ executable is searched first in ``SAGE_VENV/bin``, then in ``SAGE_LOCAL/bin``,
661
+ then in ``PATH``.
662
+
663
+ .. NOTE::
664
+
665
+ Overwrite :meth:`is_functional` if you also want to check whether
666
+ the executable shows proper behaviour.
667
+
668
+ Calls to :meth:`is_present` are cached. You might want to cache the
669
+ :class:`Executable` object to prevent unnecessary calls to the
670
+ executable.
671
+
672
+ EXAMPLES::
673
+
674
+ sage: from sage.features import Executable
675
+ sage: Executable(name='sh', executable='sh').is_present()
676
+ FeatureTestResult('sh', True)
677
+ sage: Executable(name='does-not-exist', executable='does-not-exist-xxxxyxyyxyy').is_present()
678
+ FeatureTestResult('does-not-exist', False)
679
+ """
680
+ def __init__(self, name, executable, **kwds):
681
+ r"""
682
+ TESTS::
683
+
684
+ sage: from sage.features import Executable
685
+ sage: isinstance(Executable(name='sh', executable='sh'), Executable)
686
+ True
687
+ """
688
+ Feature.__init__(self, name, **kwds)
689
+ self.executable = executable
690
+
691
+ def _is_present(self):
692
+ r"""
693
+ Test whether the executable is on the current PATH and functional.
694
+
695
+ .. SEEALSO:: :meth:`is_functional`
696
+
697
+ EXAMPLES::
698
+
699
+ sage: from sage.features import Executable
700
+ sage: Executable(name='sh', executable='sh').is_present()
701
+ FeatureTestResult('sh', True)
702
+ """
703
+ result = FileFeature._is_present(self)
704
+ if not result:
705
+ return result
706
+ return self.is_functional()
707
+
708
+ def is_functional(self):
709
+ r"""
710
+ Return whether an executable in the path is functional.
711
+
712
+ This method is used internally and can be overridden in subclasses
713
+ in order to implement a feature test. It should not be called directly.
714
+ Use :meth:`Feature.is_present` instead.
715
+
716
+ EXAMPLES:
717
+
718
+ The function returns ``True`` unless explicitly overwritten::
719
+
720
+ sage: from sage.features import Executable
721
+ sage: Executable(name='sh', executable='sh').is_functional()
722
+ FeatureTestResult('sh', True)
723
+ """
724
+ return FeatureTestResult(self, True)
725
+
726
+ def absolute_filename(self) -> str:
727
+ r"""
728
+ The absolute path of the executable as a string.
729
+
730
+ EXAMPLES::
731
+
732
+ sage: from sage.features import Executable
733
+ sage: Executable(name='sh', executable='sh').absolute_filename()
734
+ '/...bin/sh'
735
+
736
+ A :exc:`FeatureNotPresentError` is raised if the file cannot be found::
737
+
738
+ sage: Executable(name='does-not-exist', executable='does-not-exist-xxxxyxyyxyy').absolute_filename()
739
+ Traceback (most recent call last):
740
+ ...
741
+ sage.features.FeatureNotPresentError: does-not-exist is not available.
742
+ Executable 'does-not-exist-xxxxyxyyxyy' not found on PATH.
743
+ """
744
+ if SAGE_LOCAL:
745
+ if Path(SAGE_VENV).resolve() != Path(SAGE_LOCAL).resolve():
746
+ # As sage.env currently gives SAGE_LOCAL a fallback value from SAGE_VENV,
747
+ # SAGE_LOCAL is never unset. So we only use it if it differs from SAGE_VENV.
748
+ search_path = ':'.join([os.path.join(SAGE_VENV, 'bin'),
749
+ os.path.join(SAGE_LOCAL, 'bin')])
750
+ path = shutil.which(self.executable, path=search_path)
751
+ if path is not None:
752
+ return path
753
+ # Now look up in the regular PATH.
754
+ path = shutil.which(self.executable)
755
+ if path is not None:
756
+ return path
757
+ raise FeatureNotPresentError(self,
758
+ reason="Executable {executable!r} not found on PATH.".format(executable=self.executable),
759
+ resolution=self.resolution())
760
+
761
+
762
+ class StaticFile(FileFeature):
763
+ r"""
764
+ A :class:`Feature` which describes the presence of a certain file such as a
765
+ database.
766
+
767
+ EXAMPLES::
768
+
769
+ sage: from sage.features import StaticFile
770
+ sage: StaticFile(name='no_such_file', filename='KaT1aihu', # optional - sage_spkg
771
+ ....: search_path='/', spkg='some_spkg',
772
+ ....: url='http://rand.om').require()
773
+ Traceback (most recent call last):
774
+ ...
775
+ FeatureNotPresentError: no_such_file is not available.
776
+ 'KaT1aihu' not found in any of ['/']...
777
+ To install no_such_file...you can try to run...sage -i some_spkg...
778
+ Further installation instructions might be available at http://rand.om.
779
+ """
780
+ def __init__(self, name, filename, *, search_path=None, type='optional', **kwds):
781
+ r"""
782
+ TESTS::
783
+
784
+ sage: from sage.features import StaticFile
785
+ sage: StaticFile(name='null', filename='null', search_path='/dev')
786
+ Feature('null')
787
+ sage: sh = StaticFile(name='shell', filename='sh',
788
+ ....: search_path=("/dev", "/bin", "/usr"))
789
+ sage: sh
790
+ Feature('shell')
791
+ sage: sh.absolute_filename()
792
+ '/bin/sh'
793
+ """
794
+ Feature.__init__(self, name, type=type, **kwds)
795
+ self.filename = filename
796
+ if search_path is None:
797
+ self.search_path = [SAGE_SHARE]
798
+ elif isinstance(search_path, str):
799
+ self.search_path = [search_path]
800
+ else:
801
+ self.search_path = list(search_path)
802
+
803
+ def absolute_filename(self) -> str:
804
+ r"""
805
+ The absolute path of the file as a string.
806
+
807
+ EXAMPLES::
808
+
809
+ sage: from sage.features import StaticFile
810
+ sage: from sage.misc.temporary_file import tmp_dir
811
+ sage: dir_with_file = tmp_dir()
812
+ sage: file_path = os.path.join(dir_with_file, "file.txt")
813
+ sage: open(file_path, 'a').close() # make sure the file exists
814
+ sage: search_path = ( '/foo/bar', dir_with_file ) # file is somewhere in the search path
815
+ sage: feature = StaticFile(name='file', filename='file.txt', search_path=search_path)
816
+ sage: feature.absolute_filename() == file_path
817
+ True
818
+
819
+ A :exc:`FeatureNotPresentError` is raised if the file cannot be found::
820
+
821
+ sage: from sage.features import StaticFile
822
+ sage: StaticFile(name='no_such_file', filename='KaT1aihu',\
823
+ search_path=(), spkg='some_spkg',\
824
+ url='http://rand.om').absolute_filename() # optional - sage_spkg
825
+ Traceback (most recent call last):
826
+ ...
827
+ FeatureNotPresentError: no_such_file is not available.
828
+ 'KaT1aihu' not found in any of []...
829
+ To install no_such_file...you can try to run...sage -i some_spkg...
830
+ Further installation instructions might be available at http://rand.om.
831
+ """
832
+ for directory in self.search_path:
833
+ path = os.path.join(directory, self.filename)
834
+ if os.path.isfile(path) or os.path.isdir(path):
835
+ return os.path.abspath(path)
836
+ reason = "{filename!r} not found in any of {search_path}".format(filename=self.filename, search_path=self.search_path)
837
+ raise FeatureNotPresentError(self, reason=reason, resolution=self.resolution())
838
+
839
+
840
+ class CythonFeature(Feature):
841
+ r"""
842
+ A :class:`Feature` which describes the ability to compile and import
843
+ a particular piece of Cython code.
844
+
845
+ To test the presence of ``name``, the cython compiler is run on
846
+ ``test_code`` and the resulting module is imported.
847
+
848
+ EXAMPLES::
849
+
850
+ sage: from sage.features import CythonFeature
851
+ sage: fabs_test_code = '''
852
+ ....: cdef extern from "<math.h>":
853
+ ....: double fabs(double x)
854
+ ....:
855
+ ....: assert fabs(-1) == 1
856
+ ....: '''
857
+ sage: fabs = CythonFeature("fabs", test_code=fabs_test_code, # needs sage.misc.cython
858
+ ....: spkg='gcc', url='https://gnu.org',
859
+ ....: type='standard')
860
+ sage: fabs.is_present() # needs sage.misc.cython
861
+ FeatureTestResult('fabs', True)
862
+
863
+ Test various failures::
864
+
865
+ sage: broken_code = '''this is not a valid Cython program!'''
866
+ sage: broken = CythonFeature("broken", test_code=broken_code)
867
+ sage: broken.is_present()
868
+ FeatureTestResult('broken', False)
869
+
870
+ ::
871
+
872
+ sage: broken_code = '''cdef extern from "no_such_header_file": pass'''
873
+ sage: broken = CythonFeature("broken", test_code=broken_code)
874
+ sage: broken.is_present()
875
+ FeatureTestResult('broken', False)
876
+
877
+ ::
878
+
879
+ sage: broken_code = '''import no_such_python_module'''
880
+ sage: broken = CythonFeature("broken", test_code=broken_code)
881
+ sage: broken.is_present()
882
+ FeatureTestResult('broken', False)
883
+
884
+ ::
885
+
886
+ sage: broken_code = '''raise AssertionError("sorry!")'''
887
+ sage: broken = CythonFeature("broken", test_code=broken_code)
888
+ sage: broken.is_present()
889
+ FeatureTestResult('broken', False)
890
+ """
891
+ def __init__(self, name, test_code, **kwds):
892
+ r"""
893
+ TESTS::
894
+
895
+ sage: from sage.features import CythonFeature
896
+ sage: from sage.features.bliss import BlissLibrary
897
+ sage: isinstance(BlissLibrary(), CythonFeature) # indirect doctest
898
+ True
899
+ """
900
+ Feature.__init__(self, name, **kwds)
901
+ self.test_code = test_code
902
+
903
+ def _is_present(self):
904
+ r"""
905
+ Run test code to determine whether the shared library is present.
906
+
907
+ EXAMPLES::
908
+
909
+ sage: from sage.features import CythonFeature
910
+ sage: empty = CythonFeature("empty", test_code="")
911
+ sage: empty.is_present() # needs sage.misc.cython
912
+ FeatureTestResult('empty', True)
913
+ """
914
+ from sage.misc.temporary_file import tmp_filename
915
+ try:
916
+ # Available since https://setuptools.pypa.io/en/latest/history.html#v59-0-0
917
+ from setuptools.errors import CCompilerError
918
+ except ImportError:
919
+ try:
920
+ from distutils.errors import CCompilerError
921
+ except ImportError:
922
+ CCompilerError = ()
923
+ with open(tmp_filename(ext='.pyx'), 'w') as pyx:
924
+ pyx.write(self.test_code)
925
+ try:
926
+ from sage.misc.cython import cython_import
927
+ except ImportError:
928
+ return FeatureTestResult(self, False, reason="sage.misc.cython is not available")
929
+ try:
930
+ cython_import(pyx.name, verbose=-1)
931
+ except CCompilerError:
932
+ return FeatureTestResult(self, False, reason="Failed to compile test code.")
933
+ except ImportError:
934
+ return FeatureTestResult(self, False, reason="Failed to import test code.")
935
+ except Exception:
936
+ return FeatureTestResult(self, False, reason="Failed to run test code.")
937
+ return FeatureTestResult(self, True, reason="Test code compiled and imported.")
938
+
939
+
940
+ class PythonModule(Feature):
941
+ r"""
942
+ A :class:`Feature` which describes whether a python module can be imported.
943
+
944
+ EXAMPLES:
945
+
946
+ Not all builds of python include the ``ssl`` module, so you could check
947
+ whether it is available::
948
+
949
+ sage: from sage.features import PythonModule
950
+ sage: PythonModule("ssl").require() # not tested - output depends on the python build
951
+ """
952
+ def __init__(self, name, **kwds):
953
+ r"""
954
+ TESTS::
955
+
956
+ sage: from sage.features import PythonModule
957
+ sage: from sage.features.databases import DatabaseKnotInfo
958
+ sage: isinstance(DatabaseKnotInfo(), PythonModule) # indirect doctest
959
+ True
960
+ """
961
+ Feature.__init__(self, name, **kwds)
962
+
963
+ def _is_present(self):
964
+ r"""
965
+ Return whether the module can be imported. This is determined by
966
+ actually importing it.
967
+
968
+ EXAMPLES::
969
+
970
+ sage: from sage.features import PythonModule
971
+ sage: PythonModule("sys").is_present()
972
+ FeatureTestResult('sys', True)
973
+ sage: PythonModule("_no_such_module_").is_present()
974
+ FeatureTestResult('_no_such_module_', False)
975
+ """
976
+ import importlib
977
+ try:
978
+ importlib.import_module(self.name)
979
+ except ImportError as exception:
980
+ return FeatureTestResult(self, False, reason=f"Failed to import `{self.name}`: {exception}")
981
+ return FeatureTestResult(self, True, reason=f"Successfully imported `{self.name}`.")