Skip to content

Commit c0abe3f

Browse files
author
Release Manager
committed
Trac #33213: Replace SAGE_TMP by the system location in the sage library
Re: https://groups.google.com/g/sage-devel/c/zhjl_j6j_Qc These days, using a `SAGE_TMP` that by default lives under `$HOME` is overly complex and often inefficient. In this ticket we implement the first phase of its removal, to be replaced by python's `tempfile` module. Specifically, 1. We replace all direct uses of `SAGE_TMP` within the sage library and doctests. 2. We update `tmp_dir()` and `tmp_filename()` to use the `tempfile` defaults. 3. We remove `SAGE_TMP`. Afterward, the custom functions `tmp_dir()` and `tmp_filename()` can be deprecated in favor of `tempfile.TemporaryDirectory()` and `tempfile.NamedTemporaryFile()`. Moreover when #8784 is done, we'll be able to remove sage-cleaner entirely. URL: https://trac.sagemath.org/33213 Reported by: mjo Ticket author(s): Michael Orlitzky Reviewer(s): Matthias Koeppe, Dima Pasechnik
2 parents 7ab174a + d94a1e6 commit c0abe3f

File tree

35 files changed

+336
-255
lines changed

35 files changed

+336
-255
lines changed

pkgs/sage-setup/tox.ini

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
#
55
# To build and test in the tox environment:
66
#
7-
# ./sage -sh -c '(cd pkgs/sage-setup && tox)'
7+
# ./sage -sh -c '(cd pkgs/sage-setup && tox -e sagepython)'
88
#
99
# To test interactively:
1010
#
11-
# pkgs/sage-setup/.tox/python/bin/python
11+
# pkgs/sage-setup/.tox/sagepython/bin/python
1212
#
1313
[tox]
1414

@@ -28,3 +28,9 @@ commands =
2828

2929
# TODO: Test importing sage_setup.library_order -- when that can handle missing pkgconfig libraries...
3030
# TODO: Test more modules -- when the dependency on sage.env has been removed...
31+
32+
[testenv:sagepython]
33+
passenv =
34+
SAGE_VENV
35+
36+
basepython = {env:SAGE_VENV}/bin/python3

pkgs/sagemath-categories/tox.ini

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# To build and test in the tox environment:
22
#
3-
# ./sage -sh -c '(cd pkgs/sagemath-categories/src && tox -v -v -v)'
3+
# ./sage -sh -c '(cd pkgs/sagemath-categories/src && tox -v -v -v -e sagepython)'
44
#
55
# To test interactively:
66
#
7-
# pkgs/sagemath-categories/.tox/python/bin/python
7+
# pkgs/sagemath-categories/.tox/sagepython/bin/python
88
#
99
[tox]
1010

@@ -31,3 +31,9 @@ commands =
3131
python -c 'import sys; "" in sys.path and sys.path.remove(""); from sage.categories.all import *; SimplicialComplexes(); FunctionFields()'
3232

3333
bash -c 'cd bin && SAGE_SRC=$(python -c "from sage.env import SAGE_SRC; print(SAGE_SRC)") && sage-runtests --environment=sage.all__sagemath_categories --optional=sage $SAGE_SRC/sage/structure || echo "(lots of doctest failures are expected)"'
34+
35+
[testenv:sagepython]
36+
passenv =
37+
SAGE_VENV
38+
39+
basepython = {env:SAGE_VENV}/bin/python3

pkgs/sagemath-objects/tox.ini

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# To build and test in the tox environment:
22
#
3-
# ./sage -sh -c '(cd pkgs/sagemath-objects && tox -v -v)'
3+
# ./sage -sh -c '(cd pkgs/sagemath-objects && tox -v -v -e sagepython)'
44
#
55
# To test interactively:
66
#
7-
# pkgs/sagemath-objects/.tox/python/bin/python
7+
# pkgs/sagemath-objects/.tox/sagepython/bin/python
88
#
99
[tox]
1010

@@ -29,3 +29,9 @@ commands =
2929
python -c 'import sys; "" in sys.path and sys.path.remove(""); from sage.all__sagemath_objects import *'
3030

3131
bash -c 'cd bin && SAGE_SRC=$(python -c "from sage.env import SAGE_SRC; print(SAGE_SRC)") && sage-runtests --environment=sage.all__sagemath_objects --optional=sage $SAGE_SRC/sage/structure || echo "(lots of doctest failures are expected)"'
32+
33+
[testenv:sagepython]
34+
passenv =
35+
SAGE_VENV
36+
37+
basepython = {env:SAGE_VENV}/bin/python3

pkgs/sagemath-standard/tox.ini

+17-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
#
1010
# To build and test in the tox environment using the concrete Python dependencies specified
1111
# by requirements.txt, using the wheels built and stored by the Sage distribution:
12-
# (Using 'sage -sh' ensures that we use the same Python as the one that we built the wheels
12+
# (Using 'sage -sh' in combination with 'sagepython-...' tox environments
13+
# ensures that we use the same Python as the one that we built the wheels
1314
# for. This can also be done ensured manually by using the tox environment py38-sagewheels etc.)
1415
#
1516
# Afterwards, to test interactively:
@@ -25,17 +26,17 @@ envlist =
2526
# Build dependencies according to requirements.txt (all versions fixed).
2627
# Use ONLY the wheels built and stored by the Sage distribution (no PyPI):
2728
#
28-
# ./sage -sh -c '(cd pkgs/sagemath-standard && tox -v -v -v -e python-sagewheels-nopypi)'
29+
# ./sage -sh -c '(cd pkgs/sagemath-standard && tox -v -v -v -e sagepython-sagewheels-nopypi)'
2930
#
30-
python-sagewheels-nopypi,
31+
sagepython-sagewheels-nopypi,
3132
#
3233
# Build and test without using the concrete dependencies specified by requirements.txt,
3334
# using the dependencies declared in pyproject.toml and setup.cfg (install-requires) only:
3435
# Still use ONLY the wheels built and stored by the Sage distribution (no PyPI).
3536
#
36-
# ./sage -sh -c '(cd pkgs/sagemath-standard && tox -v -v -v -e python-sagewheels-nopypi-norequirements)'
37+
# ./sage -sh -c '(cd pkgs/sagemath-standard && tox -v -v -v -e sagepython-sagewheels-nopypi-norequirements)'
3738
#
38-
python-sagewheels-nopypi-norequirements,
39+
sagepython-sagewheels-nopypi-norequirements,
3940
#
4041
# EXPERIMENTAL ENVIRONMENTS:
4142
#
@@ -44,14 +45,14 @@ envlist =
4445
# and additionally allow packages from PyPI.
4546
# Because all versions are fixed, we "should" end up using the prebuilt wheels.
4647
#
47-
# ./sage -sh -c '(cd pkgs/sagemath-standard && tox -v -v -v -e python-sagewheels)'
48+
# ./sage -sh -c '(cd pkgs/sagemath-standard && tox -v -v -v -e sagepython-sagewheels)'
4849
#
49-
python-sagewheels,
50+
sagepython-sagewheels,
5051
#
5152
# Likewise, but using pipenv using Pipfile-dist (= SAGE_ROOT/Pipfile).
5253
# This also fixes the concrete dependencies (at least for some packages).
5354
#
54-
python-sagewheels-pipenv-dist,
55+
sagepython-sagewheels-pipenv-dist,
5556
#
5657
# Build using the dependencies declared in pyproject.toml and setup.cfg (install-requires) only.
5758
# Use the wheels built and stored by the Sage distribution,
@@ -60,11 +61,11 @@ envlist =
6061
# Because the version ranges will allow for packages to come in from PyPI (in source or wheel form),
6162
# this is likely to fail because we do not have control over the configuration of these packages.
6263
#
63-
python-sagewheels-norequirements,
64+
sagepython-sagewheels-norequirements,
6465
#
6566
# Likewise, but using pipenv
6667
#
67-
python-sagewheels-pipenv
68+
sagepython-sagewheels-pipenv
6869

6970
[testenv]
7071
deps =
@@ -129,3 +130,9 @@ commands =
129130
sage -c 'import sys; print("sys.path =", sys.path); import sage.all; print(sage.all.__file__)'
130131

131132
sage -t -p --all
133+
134+
[testenv:sagepython]
135+
passenv =
136+
SAGE_VENV
137+
138+
basepython = {env:SAGE_VENV}/bin/python3

src/doc/en/developer/packaging_sage_library.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -584,19 +584,19 @@ Following the comments in the file
584584
``SAGE_ROOT/pkgs/sagemath-standard/tox.ini``, we can try the following
585585
command::
586586

587-
$ ./bootstrap && ./sage -sh -c '(cd pkgs/sagemath-standard && SAGE_NUM_THREADS=16 tox -v -v -v -e py39-sagewheels-nopypi)'
587+
$ ./bootstrap && ./sage -sh -c '(cd pkgs/sagemath-standard && SAGE_NUM_THREADS=16 tox -v -v -v -e sagepython-sagewheels-nopypi)'
588588

589589
This command does not make any changes to the normal installation of
590590
Sage. The virtual environment is created in a subdirectory of
591591
``SAGE_ROOT/pkgs/sagemath-standard-no-symbolics/.tox/``. After the command
592592
finishes, we can start the separate installation of the Sage library
593593
in its virtual environment::
594594

595-
$ pkgs/sagemath-standard/.tox/py39-sagewheels-nopypi/bin/sage
595+
$ pkgs/sagemath-standard/.tox/sagepython-sagewheels-nopypi/bin/sage
596596

597597
We can also run parts of the testsuite::
598598

599-
$ pkgs/sagemath-standard/.tox/py39-sagewheels-nopypi/bin/sage -tp 4 src/sage/graphs/
599+
$ pkgs/sagemath-standard/.tox/sagepython-sagewheels-nopypi/bin/sage -tp 4 src/sage/graphs/
600600

601601
The whole ``.tox`` directory can be safely deleted at any time.
602602

@@ -609,15 +609,15 @@ without depending on optional packages, but without the packages
609609

610610
Again we can run the test with ``tox`` in a separate virtual environment::
611611

612-
$ ./bootstrap && ./sage -sh -c '(cd pkgs/sagemath-standard-no-symbolics && SAGE_NUM_THREADS=16 tox -v -v -v -e py39-sagewheels-nopypi)'
612+
$ ./bootstrap && ./sage -sh -c '(cd pkgs/sagemath-standard-no-symbolics && SAGE_NUM_THREADS=16 tox -v -v -v -e sagepython-sagewheels-nopypi)'
613613

614614
Some small distributions, for example the ones providing the two
615615
lowest levels, `sagemath-objects <https://pypi.org/project/sagemath-objects/>`_
616616
and `sagemath-categories <https://pypi.org/project/sagemath-categories/>`_
617617
(from :trac:`29865`), can be installed and tested
618618
without relying on the wheels from the Sage build::
619619

620-
$ ./bootstrap && ./sage -sh -c '(cd pkgs/sagemath-objects && SAGE_NUM_THREADS=16 tox -v -v -v -e py39)'
620+
$ ./bootstrap && ./sage -sh -c '(cd pkgs/sagemath-objects && SAGE_NUM_THREADS=16 tox -v -v -v -e sagepython)'
621621

622622
This command finds the declared build-time and run-time dependencies
623623
on PyPI, either as source tarballs or as prebuilt wheels, and builds

src/sage/combinat/cluster_algebra_quiver/cluster_seed.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,9 @@ def save_image(self, filename, circular=False, mark=None, save_pos=False):
10801080
EXAMPLES::
10811081
10821082
sage: S = ClusterSeed(['F',4,[1,2]])
1083-
sage: S.save_image(os.path.join(SAGE_TMP, 'sage.png'))
1083+
sage: import tempfile
1084+
sage: with tempfile.NamedTemporaryFile(suffix=".png") as f:
1085+
....: S.save_image(f.name)
10841086
"""
10851087
graph_plot = self.plot( circular=circular, mark=mark, save_pos=save_pos)
10861088
graph_plot.save( filename=filename )

src/sage/combinat/cluster_algebra_quiver/quiver.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,9 @@ def save_image(self, filename, circular=False):
715715
EXAMPLES::
716716
717717
sage: Q = ClusterQuiver(['F',4,[1,2]])
718-
sage: Q.save_image(os.path.join(SAGE_TMP, 'sage.png'))
718+
sage: import tempfile
719+
sage: with tempfile.NamedTemporaryFile(suffix=".png") as f:
720+
....: Q.save_image(f.name)
719721
"""
720722
graph_plot = self.plot(circular=circular)
721723
graph_plot.save(filename=filename)
@@ -738,14 +740,18 @@ def qmu_save(self, filename=None):
738740
EXAMPLES::
739741
740742
sage: Q = ClusterQuiver(['F',4,[1,2]])
741-
sage: Q.qmu_save(os.path.join(SAGE_TMP, 'sage.qmu'))
743+
sage: import tempfile
744+
sage: with tempfile.NamedTemporaryFile(suffix=".qmu") as f:
745+
....: Q.qmu_save(f.name)
742746
743747
Make sure we can save quivers with `m != n` frozen variables, see :trac:`14851`::
744748
745749
sage: S = ClusterSeed(['A',3])
746750
sage: T1 = S.principal_extension()
747751
sage: Q = T1.quiver()
748-
sage: Q.qmu_save(os.path.join(SAGE_TMP, 'sage.qmu'))
752+
sage: import tempfile
753+
sage: with tempfile.NamedTemporaryFile(suffix=".qmu") as f:
754+
....: Q.qmu_save(f.name)
749755
"""
750756
M = self.b_matrix()
751757
if self.m():

src/sage/combinat/words/morphism.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,9 @@ def __call__(self, w, order=1, datatype=None):
761761
762762
sage: w == loads(dumps(w))
763763
True
764-
sage: save(w, filename=os.path.join(SAGE_TMP, 'test.sobj'))
764+
sage: import tempfile
765+
sage: with tempfile.NamedTemporaryFile(suffix=".sobj") as f:
766+
....: save(w, filename=f.name)
765767
766768
The ``datatype`` argument is deprecated::
767769

src/sage/interfaces/cleaner.py

+4-18
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,10 @@
1717

1818
import os
1919

20-
from sage.misc.misc import SAGE_TMP
21-
22-
def cleaner(pid, cmd=''):
23-
"""
24-
Write a line to the ``spawned_processes`` file with the given
25-
``pid`` and ``cmd``.
26-
"""
27-
if cmd != '':
28-
cmd = cmd.strip().split()[0]
29-
# This is safe, since only this process writes to this file.
30-
F = os.path.join(SAGE_TMP, 'spawned_processes')
31-
try:
32-
with open(F, 'a') as o:
33-
o.write('%s %s\n'%(pid, cmd))
34-
except IOError:
35-
pass
36-
else:
37-
start_cleaner()
20+
import atexit, tempfile
21+
_spd = tempfile.TemporaryDirectory()
22+
SAGE_SPAWNED_PROCESS_FILE = os.path.join(_spd.name, "spawned_processes")
23+
atexit.register(lambda: _spd.cleanup())
3824

3925

4026
def start_cleaner():

src/sage/interfaces/expect.py

+25-6
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import time
4949
import gc
5050
from . import quit
51-
from . import cleaner
5251
from random import randrange
5352

5453
import pexpect
@@ -59,7 +58,6 @@
5958

6059
from sage.structure.element import RingElement
6160

62-
from sage.misc.misc import SAGE_TMP_INTERFACE
6361
from sage.env import SAGE_EXTCODE, LOCAL_IDENTIFIER
6462
from sage.misc.object_multiplexer import Multiplex
6563
from sage.misc.instancedoc import instancedoc
@@ -440,7 +438,11 @@ def _do_cleaner(self):
440438
return False
441439

442440
def _start(self, alt_message=None, block_during_init=True):
443-
self.quit() # in case one is already running
441+
if self.is_running():
442+
# In case one is already running. We check first because
443+
# quit() can reset the local temporary filename at an
444+
# unexpected time as the process is started "on demand."
445+
self.quit()
444446

445447
self._session_number += 1
446448

@@ -515,7 +517,7 @@ def _start(self, alt_message=None, block_during_init=True):
515517
os.chdir(currentdir)
516518

517519
if self._do_cleaner():
518-
cleaner.cleaner(self._expect.pid, cmd)
520+
quit.register_spawned_process(self._expect.pid, cmd)
519521

520522
try:
521523
self._expect.expect(self._prompt)
@@ -594,6 +596,14 @@ def _reset_expect(self):
594596
"""
595597
self._session_number += 1
596598
try:
599+
# Spaghetti alert: when running several computations in
600+
# parallel, the pexpect interface is reset in each one,
601+
# and this next line is needed to trigger the generation
602+
# of a new temporary file when otherwise the existing
603+
# member variable would be shared. That also means that
604+
# you can't use this method to clean up an existing
605+
# tmpfile, because you can delete the one that the parent
606+
# is using.
597607
del self.__local_tmpfile
598608
except AttributeError:
599609
pass
@@ -732,8 +742,17 @@ def _local_tmpfile(self):
732742
try:
733743
return self.__local_tmpfile
734744
except AttributeError:
735-
self.__local_tmpfile = os.path.join(SAGE_TMP_INTERFACE, 'tmp' + str(self.pid()))
736-
return self.__local_tmpfile
745+
pass
746+
747+
import atexit, os
748+
from tempfile import NamedTemporaryFile
749+
# FriCAS uses the ".input" suffix, and the other
750+
# interfaces are suffix-agnostic, so using ".input" here
751+
# lets us avoid a subclass override for FriCAS.
752+
with NamedTemporaryFile(suffix=".input", delete=False) as f:
753+
self.__local_tmpfile = f.name
754+
atexit.register(lambda: os.remove(f.name))
755+
return self.__local_tmpfile
737756

738757
def _remote_tmpdir(self):
739758
return self.__remote_tmpdir

src/sage/interfaces/fricas.py

-12
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@
200200
import os
201201
from sage.interfaces.tab_completion import ExtraTabCompletion
202202
from sage.interfaces.expect import Expect, ExpectElement, FunctionElement, ExpectFunction
203-
from sage.misc.misc import SAGE_TMP_INTERFACE
204203
from sage.env import DOT_SAGE, LOCAL_IDENTIFIER
205204
from sage.misc.instancedoc import instancedoc
206205
from sage.rings.integer_ring import ZZ
@@ -475,17 +474,6 @@ def _read_in_file_command(self, filename):
475474

476475
return ')read %s )quiet' % filename
477476

478-
def _local_tmpfile(self):
479-
"""
480-
Return a local tmpfile ending with ".input" used to buffer long
481-
command lines sent to FriCAS.
482-
483-
"""
484-
try:
485-
return self.__local_tmpfile
486-
except AttributeError:
487-
self.__local_tmpfile = os.path.join(SAGE_TMP_INTERFACE, 'tmp' + str(self.pid()) + '.input')
488-
return self.__local_tmpfile
489477

490478
def _remote_tmpfile(self):
491479
"""

0 commit comments

Comments
 (0)