Skip to content

Tools files rm: Exclude pattern to avoid removing everything #16350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions conan/tools/files/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,25 @@ def rmdir(conanfile, path):
_internal_rmdir(path)


def rm(conanfile, pattern, folder, recursive=False):
def rm(conanfile, pattern, folder, recursive=False, excludes=None):
"""
Utility functions to remove files matching a ``pattern`` in a ``folder``.

:param conanfile: The current recipe object. Always use ``self``.
:param pattern: Pattern that the files to be removed have to match (fnmatch).
:param folder: Folder to search/remove the files.
:param recursive: If ``recursive`` is specified it will search in the subfolders.
:param excludes: (Optional, defaulted to None) A tuple/list of fnmatch patterns or even a
single one to be excluded from the remove pattern.
"""
if excludes and not isinstance(excludes, (tuple, list)):
excludes = (excludes,)
elif not excludes:
excludes = []

for root, _, filenames in os.walk(folder):
for filename in filenames:
if fnmatch(filename, pattern):
if fnmatch(filename, pattern) and not any(fnmatch(filename, it) for it in excludes):
fullname = os.path.join(root, filename)
os.unlink(fullname)
if not recursive:
Expand Down
44 changes: 44 additions & 0 deletions test/unittests/tools/files/test_rm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import pytest

# Check it is importable from tools
from conan.tools.files import rm, chdir
Expand Down Expand Up @@ -63,3 +64,46 @@ def test_remove_files_by_mask_non_recursively():

assert os.path.exists(os.path.join(tmpdir, "1.txt"))
assert os.path.exists(os.path.join(tmpdir, "subdir", "2.txt"))


@pytest.mark.parametrize("recursive", [False, True])
@pytest.mark.parametrize("results", [
["*.dll", ("foo.dll",)],
[("*.dll",), ("foo.dll",)],
[["*.dll"], ("foo.dll",)],
[("*.dll", "*.lib"), ("foo.dll", "foo.dll.lib")],
])
def test_exclude_pattern_from_remove_list(recursive, results):
""" conan.tools.files.rm should not remove files that match the pattern but are excluded
by the excludes parameter.
It should obey the recursive parameter, only excluding the files in the root folder in case
it is False.
"""
excludes, expected_files = results
temporary_folder = temp_folder()
with chdir(None, temporary_folder):
os.makedirs("subdir")

save_files(temporary_folder, {
"1.txt": "",
"1.pdb": "",
"1.pdb1": "",
"foo.dll": "",
"foo.dll.lib": "",
os.path.join("subdir", "2.txt"): "",
os.path.join("subdir", "2.pdb"): "",
os.path.join("subdir", "foo.dll"): "",
os.path.join("subdir", "foo.dll.lib"): "",
os.path.join("subdir", "2.pdb1"): ""})

rm(None, "*", temporary_folder, excludes=excludes, recursive=recursive)

for it in expected_files:
assert os.path.exists(os.path.join(temporary_folder, it))
assert not os.path.exists(os.path.join(temporary_folder, "1.pdb"))

# Check the recursive parameter and subfolder
condition = (lambda x: not x) if recursive else (lambda x: x)
assert condition(os.path.exists(os.path.join(temporary_folder, "subdir", "2.pdb")))
for it in expected_files:
assert os.path.exists(os.path.join(temporary_folder, "subdir", it))