Skip to content

Commit

Permalink
robust rename a file or folder to avoid 'Access is denied' on Windows. (
Browse files Browse the repository at this point in the history
#6774)

* robust rename a file or folder to avoid 'Access is denied' on Windows.

* fix robocopy command line

* Add check existence of robocopy and update error message

* fix renaming file

* use subprocess insted of os.system().

* add comments on each of robcopy parameters

* add unit test for tools.rename().

* raise ConanException if os.rename() failed.

* remove catch-all exception.

* add unit test for renaming files.
  • Loading branch information
yangcha committed Aug 25, 2020
1 parent ce173d2 commit 3b98d80
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
26 changes: 26 additions & 0 deletions conans/client/tools/files.py
@@ -1,6 +1,7 @@
import logging
import os
import platform
import subprocess
import sys
from contextlib import contextmanager
from fnmatch import fnmatch
Expand Down Expand Up @@ -369,6 +370,31 @@ def dos2unix(filepath):
_replace_with_separator(filepath, "\n")


def rename(src, dst):
"""
rename a file or folder to avoid "Access is denied" error on Windows
:param src: Source file or folder
:param dst: Destination file or folder
"""
if os.path.exists(dst):
raise ConanException("rename {} to {} failed, dst exists.".format(src, dst))

if platform.system() == "Windows" and which("robocopy") and os.path.isdir(src):
# /move Moves files and directories, and deletes them from the source after they are copied.
# /e Copies subdirectories. Note that this option includes empty directories.
# /ndl Specifies that directory names are not to be logged.
# /nfl Specifies that file names are not to be logged.
completed = subprocess.run(["robocopy", "/move", "/e", "/ndl", "/nfl", src, dst],
stdout=subprocess.PIPE)
if completed.returncode != 1:
raise ConanException("rename {} to {} failed.".format(src, dst))
else:
try:
os.rename(src, dst)
except Exception as err:
raise ConanException("rename {} to {} failed: {}".format(src, dst, err))


def remove_files_by_mask(directory, pattern):
removed_names = []
for root, _, filenames in os.walk(directory):
Expand Down
67 changes: 67 additions & 0 deletions conans/test/unittests/client/tools/files/rename_test.py
@@ -0,0 +1,67 @@
import os
import unittest

from conans.client.tools.files import rename, chdir
from conans.test.utils.tools import TestClient


class RenameTest(unittest.TestCase):
def verify_dir(self, basedir):
self.assertTrue(os.path.isdir(basedir))

self.assertTrue(os.path.isfile(os.path.join(basedir, "1.txt")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "1.pdb")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "1.pdb1")))

self.assertTrue(os.path.isdir(os.path.join(basedir, "dir.pdb")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "dir.pdb", "2.txt")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "dir.pdb", "2.pdb")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "dir.pdb", "2.pdb1")))

self.assertTrue(os.path.isdir(os.path.join(basedir, "middir")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "middir", "3.txt")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "middir", "3.pdb")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "middir", "3.pdb1")))

self.assertTrue(os.path.isdir(os.path.join(basedir, "middir", "deepdir")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "middir", "deepdir", "4.txt")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "middir", "deepdir", "4.pdb")))
self.assertTrue(os.path.isfile(os.path.join(basedir, "middir", "deepdir", "4.pdb1")))


def rename_test(self):
client = TestClient()
tmpdir = client.current_folder

sub_space_dir = "sub dir"
with chdir(tmpdir):
os.makedirs(sub_space_dir)
os.makedirs(os.path.join(sub_space_dir, "dir.pdb"))
os.makedirs(os.path.join(sub_space_dir, "middir"))
os.makedirs(os.path.join(sub_space_dir, "middir", "deepdir"))

client.save({os.path.join(sub_space_dir, "1.txt"): "",
os.path.join(sub_space_dir, "1.pdb"): "",
os.path.join(sub_space_dir, "1.pdb1"): "",
os.path.join(sub_space_dir, "dir.pdb", "2.txt"): "",
os.path.join(sub_space_dir, "dir.pdb", "2.pdb"): "",
os.path.join(sub_space_dir, "dir.pdb", "2.pdb1"): "",
os.path.join(sub_space_dir, "middir", "3.txt"): "",
os.path.join(sub_space_dir, "middir", "3.pdb"): "",
os.path.join(sub_space_dir, "middir", "3.pdb1"): "",
os.path.join(sub_space_dir, "middir", "deepdir", "4.txt"): "",
os.path.join(sub_space_dir, "middir", "deepdir", "4.pdb"): "",
os.path.join(sub_space_dir, "middir", "deepdir", "4.pdb1"): ""
})
self.verify_dir(os.path.join(tmpdir, sub_space_dir))

with chdir(tmpdir):
rename(sub_space_dir, "dst dir")
self.verify_dir(os.path.join(tmpdir, "dst dir"))

rename("dst dir", "subdir")
self.verify_dir(os.path.join(tmpdir, "subdir"))

rename(os.path.join("subdir", "1.txt"), "t.txt")
self.assertTrue(os.path.isfile(os.path.join(tmpdir, "t.txt")))
self.assertFalse(os.path.isfile(os.path.join(tmpdir, "subdir", "1.txt")))
1 change: 1 addition & 0 deletions conans/tools.py
Expand Up @@ -96,6 +96,7 @@ def get(*args, **kwargs):
which = tools_files.which
unix2dos = tools_files.unix2dos
dos2unix = tools_files.dos2unix
rename = tools_files.rename
fix_symlinks = tools_files.fix_symlinks


Expand Down

0 comments on commit 3b98d80

Please sign in to comment.