It looks like gnulib-tool.py never cleaned up temporary directories
for modes other than the --import, --create-testdir, and related.

Since the --extract-* functions seem less used, hopefully this hasn't
caused too much spam yet.

In gnulib-tool-tests:

    $ rm -rf /tmp/glpy*
    $ ./test-all.sh
    $ find '/tmp' -name 'glpy*' -type d 2> /dev/null | wc -l
    54

in gnulib-tool-tests/info-tests:

    $ rm -rf /tmp/glpy*
    $ ./test-all.sh
    $ find '/tmp' -name 'glpy*' -type d 2> /dev/null | wc -l
    54

GLImport.__init__() and main() are pretty massive and ideally I would
like to make them a bit easier to read. It seems like a pain to track
down every code path that can lead to 'sys.exit()' or 'exit()' with a
temporary directory still existing.

With that said, GLConfig's are only created in two places. Once in
main() for the lifetime of the program. The other in
GLImport.__init__() to represent 'gnulib-cache.m4'. The one
representing the cache has it's temporary directory removed right
after.

Therefore, we can use tempfile.TemporaryDirectory as a context manager
and let Python cleanup for us [1]:

 def main_with_exception_handling() -> None:
     try:  # Try to execute
-        main()
+        with tempfile.TemporaryDirectory() as temporary_directory:
+            main(temporary_directory)
     except GLError as error:
         errmode = 0  # gnulib-style errors
         errno = error.errno


Solves the spam and seems like the best solution for now. I applied
the attached patch doing this.

[1] https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryDirectory

Collin
From 3169fd03dc5c917dbfe3096ec54921ca0e5f7253 Mon Sep 17 00:00:00 2001
From: Collin Funk <collin.fu...@gmail.com>
Date: Thu, 2 May 2024 00:49:58 -0700
Subject: [PATCH] gnulib-tool.py: Don't leave temporary directories on exit.

* pygnulib/main.py (main_with_exception_handling): Use
tempfile.TemporaryDirectory as a context manager so it is removed before
the program exits.
(main): Expect a temporary directory to be passed as an argument.
* pygnulib/GLConfig.py (GLConfig.__init__): Accept an optional temporary
directory parameter instead of creating one.
* pygnulib/GLImport.py (GLImport.__init__): Don't remove the cache's
temporary directory since it doesn't create one anymore.
(GLImport.execute): Don't remove the temporary directory explicitly. It
is handled by the usage of a context manager.
* pygnulib/GLTestDir.py (GLTestDir.execute, GLMegaTestDir.execute):
Likewise.
---
 ChangeLog             | 16 ++++++++++++++++
 pygnulib/GLConfig.py  |  4 ++--
 pygnulib/GLImport.py  |  2 --
 pygnulib/GLTestDir.py |  2 --
 pygnulib/main.py      |  9 +++++----
 5 files changed, 23 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index bd2c45d9af..2257857847 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2024-05-02  Collin Funk  <collin.fu...@gmail.com>
+
+	gnulib-tool.py: Don't leave temporary directories on exit.
+	* pygnulib/main.py (main_with_exception_handling): Use
+	tempfile.TemporaryDirectory as a context manager so it is removed before
+	the program exits.
+	(main): Expect a temporary directory to be passed as an argument.
+	* pygnulib/GLConfig.py (GLConfig.__init__): Accept an optional temporary
+	directory parameter instead of creating one.
+	* pygnulib/GLImport.py (GLImport.__init__): Don't remove the cache's
+	temporary directory since it doesn't create one anymore.
+	(GLImport.execute): Don't remove the temporary directory explicitly. It
+	is handled by the usage of a context manager.
+	* pygnulib/GLTestDir.py (GLTestDir.execute, GLMegaTestDir.execute):
+	Likewise.
+
 2024-05-01  Collin Funk  <collin.fu...@gmail.com>
 
 	gnulib-tool.py: Quote file names passed to 'patch'.
diff --git a/pygnulib/GLConfig.py b/pygnulib/GLConfig.py
index 92aa49d700..b8a7fc5b0b 100644
--- a/pygnulib/GLConfig.py
+++ b/pygnulib/GLConfig.py
@@ -20,7 +20,6 @@
 #===============================================================================
 import os
 import copy
-import tempfile
 from typing import Any
 from .constants import (
     MODES,
@@ -44,6 +43,7 @@ class GLConfig:
     table: dict[str, Any]
 
     def __init__(self,
+                 tempdir: str | None = None,
                  destdir: str | None = None,
                  localpath: list[str] | None = None,
                  auxdir: str | None = None,
@@ -82,7 +82,7 @@ def __init__(self,
                  errors: bool | None = None) -> None:
         '''Create new GLConfig instance.'''
         self.table = dict()
-        self.table['tempdir'] = tempfile.mkdtemp(prefix='glpy')
+        self.table['tempdir'] = tempdir
         # Check and store the attributes.
         # Remove trailing slashes from the directory names. This is necessary
         # for m4base (to avoid an error in func_import) and optional for the
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index 833e186b8f..a11da0e63d 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -92,7 +92,6 @@ def __init__(self, config: GLConfig, mode: int) -> None:
         # self.cache is the configuration extracted from some files on the
         # file system: configure.{ac,in}, gnulib-cache.m4, gnulib-comp.m4.
         self.cache = GLConfig()
-        os.rmdir(self.cache['tempdir'])
 
         # Read configure.{ac,in}.
         with open(self.config.getAutoconfFile(), mode='r', newline='\n', encoding='utf-8') as file:
@@ -1390,4 +1389,3 @@ def execute(self, filetable: GLFileTable, transformers: dict[str, tuple[re.Patte
             position_early_after = 'AC_PROG_CC'
         print('  - invoke %s_EARLY in %s, right after %s,' % (macro_prefix, configure_ac, position_early_after))
         print('  - invoke %s_INIT in %s.' % (macro_prefix, configure_ac))
-        rmtree(self.config['tempdir'])
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 2d4c684248..d3b819bd0d 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -840,7 +840,6 @@ def execute(self) -> None:
         if os.path.isfile(joinpath('build-aux', 'test-driver')):
             _patch_test_driver()
         os.chdir(DIRS['cwd'])
-        rmtree(self.config['tempdir'])
 
 
 #===============================================================================
@@ -1003,4 +1002,3 @@ def execute(self) -> None:
         if os.path.isfile(joinpath('build-aux', 'test-driver')):
             _patch_test_driver()
         os.chdir(DIRS['cwd'])
-        rmtree(self.config['tempdir'])
diff --git a/pygnulib/main.py b/pygnulib/main.py
index 3230c45a31..d429a2c47e 100644
--- a/pygnulib/main.py
+++ b/pygnulib/main.py
@@ -84,7 +84,7 @@
 import argparse
 import subprocess as sp
 import shlex
-from tempfile import mktemp
+import tempfile
 from pygnulib.constants import (
     APP,
     DIRS,
@@ -117,7 +117,7 @@
 #===============================================================================
 # Define main part
 #===============================================================================
-def main() -> None:
+def main(temp_directory: str) -> None:
     info = GLInfo()
     parser = argparse.ArgumentParser(
         prog=APP['name'],
@@ -815,6 +815,7 @@ def main() -> None:
 
     # Create pygnulib configuration.
     config = GLConfig(
+        tempdir=temp_directory,
         destdir=destdir,
         localpath=localpath,
         m4base=m4base,
@@ -853,7 +854,6 @@ def main() -> None:
         modulesystem = GLModuleSystem(config)
         listing = modulesystem.list()
         result = lines_to_multiline(listing)
-        os.rmdir(config['tempdir'])
         print(result, end='')
 
     elif mode == 'find':
@@ -1364,7 +1364,8 @@ def main() -> None:
 
 def main_with_exception_handling() -> None:
     try:  # Try to execute
-        main()
+        with tempfile.TemporaryDirectory() as temporary_directory:
+            main(temporary_directory)
     except GLError as error:
         errmode = 0  # gnulib-style errors
         errno = error.errno
-- 
2.44.0

Reply via email to