Skip to content

Commit e086444

Browse files
committed
fix copying of broken symlinks with copy/copy_file
1 parent 52feb0b commit e086444

File tree

2 files changed

+39
-3
lines changed

2 files changed

+39
-3
lines changed

easybuild/tools/filetools.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,7 +1924,12 @@ def copy_file(path, target_path, force_in_dry_run=False):
19241924
_log.info("Copied contents of file %s to %s", path, target_path)
19251925
else:
19261926
mkdir(os.path.dirname(target_path), parents=True)
1927-
shutil.copy2(path, target_path)
1927+
if os.path.exists(path):
1928+
shutil.copy2(path, target_path)
1929+
elif os.path.islink(path):
1930+
# special care for copying broken symlinks
1931+
link_target = os.readlink(path)
1932+
symlink(link_target, target_path)
19281933
_log.info("%s copied to %s", path, target_path)
19291934
except (IOError, OSError, shutil.Error) as err:
19301935
raise EasyBuildError("Failed to copy file %s to %s: %s", path, target_path, err)
@@ -2029,7 +2034,8 @@ def copy(paths, target_path, force_in_dry_run=False, **kwargs):
20292034
full_target_path = os.path.join(target_path, os.path.basename(path))
20302035
mkdir(os.path.dirname(full_target_path), parents=True)
20312036

2032-
if os.path.isfile(path):
2037+
# copy broken symlinks only if 'symlinks=True' is used
2038+
if os.path.isfile(path) or (os.path.islink(path) and kwargs.get('symlinks')):
20332039
copy_file(path, full_target_path, force_in_dry_run=force_in_dry_run)
20342040
elif os.path.isdir(path):
20352041
copy_dir(path, full_target_path, force_in_dry_run=force_in_dry_run, **kwargs)

test/framework/filetools.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,36 @@ def ignore_func(_, names):
15181518
self.assertEqual(sorted(os.listdir(testdir)), expected)
15191519
self.assertFalse(os.path.exists(os.path.join(testdir, 'GCC-6.4.0-2.28.eb')))
15201520

1521+
# test copy_dir when broken symlinks are involved
1522+
srcdir = os.path.join(self.test_prefix, 'topdir_to_copy')
1523+
ft.mkdir(srcdir)
1524+
ft.write_file(os.path.join(srcdir, 'test.txt'), '123')
1525+
subdir = os.path.join(srcdir, 'subdir')
1526+
# introduce broken file symlink
1527+
foo_txt = os.path.join(subdir, 'foo.txt')
1528+
ft.write_file(foo_txt, 'bar')
1529+
ft.symlink(foo_txt, os.path.join(subdir, 'bar.txt'))
1530+
ft.remove_file(foo_txt)
1531+
# introduce broken dir symlink
1532+
subdir_tmp = os.path.join(srcdir, 'subdir_tmp')
1533+
ft.mkdir(subdir_tmp)
1534+
ft.symlink(subdir_tmp, os.path.join(srcdir, 'subdir_link'))
1535+
ft.remove_dir(subdir_tmp)
1536+
1537+
target_dir = os.path.join(self.test_prefix, 'target_to_copy_to')
1538+
1539+
# trying this without symlinks=True ends in tears, because bar.txt points to a non-existing file
1540+
self.assertErrorRegex(EasyBuildError, "Failed to copy directory", ft.copy_dir, srcdir, target_dir)
1541+
ft.remove_dir(target_dir)
1542+
1543+
ft.copy_dir(srcdir, target_dir, symlinks=True)
1544+
1545+
# copying directory with broken symlinks should also work if target directory already exists
1546+
ft.remove_dir(target_dir)
1547+
ft.mkdir(target_dir)
1548+
ft.mkdir(subdir)
1549+
ft.copy_dir(srcdir, target_dir, symlinks=True, dirs_exist_ok=True)
1550+
15211551
# also test behaviour of copy_file under --dry-run
15221552
build_options = {
15231553
'extended_dry_run': True,
@@ -1535,7 +1565,7 @@ def ignore_func(_, names):
15351565
self.mock_stdout(False)
15361566

15371567
self.assertFalse(os.path.exists(target_dir))
1538-
self.assertTrue(re.search("^copied directory .*/GCC to .*/GCC", txt))
1568+
self.assertTrue(re.search("^copied directory .*/GCC to .*/%s" % os.path.basename(target_dir), txt))
15391569

15401570
# forced copy, even in dry run mode
15411571
self.mock_stdout(True)

0 commit comments

Comments
 (0)