Re: gnulib-tool.py: Refactor directory tree removals

2024-04-13 Thread Bruno Haible
Hi Collin,

> > +try:
> > +shutil.rmtree(dest)
> > +except FileNotFoundError:
> > +pass
> 
> You should be able to use 'shutil.rmtree(dest, ignore_errors=True)'
> here [1]. Unless you have a reason for not doing so that I missed.

It can be useful to get PermissionError explicitly signalled.
I know that 'rm -rf' will just print an error message to stderr in this case.
If we ever find that we need better error handling than what 'rm -rf'
provides, we are ready to use shutil.rmtree.

Bruno






Re: gnulib-tool.py: Refactor directory tree removals

2024-04-13 Thread Collin Funk
Hi Bruno,

On 4/13/24 3:17 AM, Bruno Haible wrote:
> +def rmtree(dest: str) -> None:
> +'''Removes the file or directory tree at dest, if it exists.'''
> +# These two implementations are nearly equivalent.
> +# Speed: 'rm -rf' can be a little faster.
> +# Exceptions: shutil.rmtree raises Python exceptions, e.g. 
> PermissionError.
> +if True:
> +sp.run(['rm', '-rf', dest], shell=False)
> +else:
> +try:
> +shutil.rmtree(dest)
> +except FileNotFoundError:
> +pass

You should be able to use 'shutil.rmtree(dest, ignore_errors=True)'
here [1]. Unless you have a reason for not doing so that I missed.

>>> shutil.rmtree('bad-directory-name')
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib64/python3.12/shutil.py", line 775, in rmtree
onexc(os.lstat, path, err)
  File "/usr/lib64/python3.12/shutil.py", line 773, in rmtree
orig_st = os.lstat(path, dir_fd=dir_fd)
  ^
FileNotFoundError: [Errno 2] No such file or directory: 'bad-directory-name'
>>> shutil.rmtree('bad-directory-name', ignore_errors=True)
>>> shutil.rmtree('bad-directory-name', ignore_errors=True)

[1] https://docs.python.org/3/library/shutil.html#shutil.rmtree

Collin



gnulib-tool.py: Refactor directory tree removals

2024-04-13 Thread Bruno Haible
There are two ways to remove a directory tree: via 'rm -rf' and through
shutil.rmtree. The former is ca. 10% faster on average. Also, it is more
likely to include platform-specific optimizations. So, let's use it.


2024-04-13  Bruno Haible  

gnulib-tool.py: Refactor directory tree removals.
* pygnulib/constants.py (rmtree): New function.
* pygnulib/GLImport.py (GLImport.execute): Use it instead of calling
'rm -rf' directly or shutil.rmtree.
* pygnulib/GLTestDir.py (GLTestDir.execute, GLMegaTestDir.execute):
Likewise.
* pygnulib/main.py (main): Likewise.

diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO
index 368c0addac..1eada15429 100644
--- a/gnulib-tool.py.TODO
+++ b/gnulib-tool.py.TODO
@@ -10,7 +10,6 @@ Optimize:
 
 Various other refactorings, as deemed useful:
   - Use an enum for 'all', 'old', 'new', 'added', 'removed' in GLImport.py.
-  - sp.call(['rm', '-rf' ...]) versus shutil.rmtree ?
   - go through all the open() and codecs.open() calls and turn them into
 with open(file_name, 'r', newline='\n', encoding='utf-8') as file:
 or
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index d45cf6b3ba..3d694a7919 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -1466,4 +1466,4 @@ in _a_LDFLAGS or _la_LDFLAGS when 
linking a library.''')
 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))
-sp.call(['rm', '-rf', self.config['tempdir']], shell=False)
+constants.rmtree(self.config['tempdir'])
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 5fd4197e52..be61d58b52 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -23,7 +23,6 @@ import re
 import sys
 import codecs
 import subprocess as sp
-import shutil
 from pathlib import Path
 from . import constants
 from .enums import CopyAction
@@ -709,7 +708,7 @@ class GLTestDir:
 # automake
 args = [UTILS['automake'], '--add-missing', '--copy']
 constants.execute(args, verbose)
-shutil.rmtree('autom4te.cache')
+constants.rmtree('autom4te.cache')
 os.chdir(DIRS['cwd'])
 if inctests and not single_configure:
 # Do not use "${AUTORECONF} --force --install", because it may 
invoke
@@ -744,7 +743,7 @@ class GLTestDir:
 # automake
 args = [UTILS['automake'], '--add-missing', '--copy']
 constants.execute(args, verbose)
-shutil.rmtree('autom4te.cache')
+constants.rmtree('autom4te.cache')
 os.chdir(DIRS['cwd'])
 
 # Need to run configure and make once, to create built files that are 
to be
@@ -879,7 +878,7 @@ class GLTestDir:
 if isfile(joinpath('build-aux', 'test-driver')):
 _patch_test_driver()
 os.chdir(DIRS['cwd'])
-sp.call(['rm', '-rf', self.config['tempdir']], shell=False)
+constants.rmtree(self.config['tempdir'])
 
 
 
#===
@@ -1036,8 +1035,8 @@ class GLMegaTestDir:
 constants.execute(args, verbose)
 args = [UTILS['automake'], '--add-missing', '--copy']
 constants.execute(args, verbose)
-shutil.rmtree('autom4te.cache')
+constants.rmtree('autom4te.cache')
 if isfile(joinpath('build-aux', 'test-driver')):
 _patch_test_driver()
 os.chdir(DIRS['cwd'])
-sp.call(['rm', '-rf', self.config['tempdir']], shell=False)
+constants.rmtree(self.config['tempdir'])
diff --git a/pygnulib/constants.py b/pygnulib/constants.py
index dc1297d529..f307ffa6b1 100644
--- a/pygnulib/constants.py
+++ b/pygnulib/constants.py
@@ -454,6 +454,20 @@ def hardlink(src: str, dest: str) -> None:
 ensure_writable(dest)
 
 
+def rmtree(dest: str) -> None:
+'''Removes the file or directory tree at dest, if it exists.'''
+# These two implementations are nearly equivalent.
+# Speed: 'rm -rf' can be a little faster.
+# Exceptions: shutil.rmtree raises Python exceptions, e.g. PermissionError.
+if True:
+sp.run(['rm', '-rf', dest], shell=False)
+else:
+try:
+shutil.rmtree(dest)
+except FileNotFoundError:
+pass
+
+
 def filter_filelist(separator: str, filelist: str, prefix: str, suffix: str,
 removed_prefix: str, removed_suffix: str,
 added_prefix: str = '', added_suffix: str = '') -> str:
diff --git a/pygnulib/main.py b/pygnulib/main.py
index 4b64305049..471f4b9fb9 100644
--- a/pygnulib/main.py
+++ b/pygnulib/main.py
@@ -1120,7 +1120,7 @@ def main() -> None:
 sys.stderr.write(message)
 sys.exit(1)
 os.chdir('../..')
-sp.call(['rm', '-rf',