4040"""
4141import datetime
4242import difflib
43- import distutils .dir_util
4443import fileinput
4544import glob
4645import hashlib
@@ -1925,7 +1924,12 @@ def copy_file(path, target_path, force_in_dry_run=False):
19251924 _log .info ("Copied contents of file %s to %s" , path , target_path )
19261925 else :
19271926 mkdir (os .path .dirname (target_path ), parents = True )
1928- 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 )
19291933 _log .info ("%s copied to %s" , path , target_path )
19301934 except (IOError , OSError , shutil .Error ) as err :
19311935 raise EasyBuildError ("Failed to copy file %s to %s: %s" , path , target_path , err )
@@ -1960,16 +1964,13 @@ def copy_dir(path, target_path, force_in_dry_run=False, dirs_exist_ok=False, **k
19601964 :param path: the original directory path
19611965 :param target_path: path to copy the directory to
19621966 :param force_in_dry_run: force running the command during dry run
1963- :param dirs_exist_ok: wrapper around shutil.copytree option, which was added in Python 3.8
1967+ :param dirs_exist_ok: boolean indicating whether it's OK if the target directory already exists
19641968
1965- On Python >= 3.8 shutil.copytree is always used
1966- On Python < 3.8 if 'dirs_exist_ok' is False - shutil.copytree is used
1967- On Python < 3.8 if 'dirs_exist_ok' is True - distutils.dir_util.copy_tree is used
1969+ shutil.copytree is used if the target path does not exist yet;
1970+ if the target path already exists, the 'copy' function will be used to copy the contents of
1971+ the source path to the target path
19681972
1969- Additional specified named arguments are passed down to shutil.copytree if used.
1970-
1971- Because distutils.dir_util.copy_tree supports only 'symlinks' named argument,
1972- using any other will raise EasyBuildError.
1973+ Additional specified named arguments are passed down to shutil.copytree/copy if used.
19731974 """
19741975 if not force_in_dry_run and build_option ('extended_dry_run' ):
19751976 dry_run_msg ("copied directory %s to %s" % (path , target_path ))
@@ -1978,38 +1979,49 @@ def copy_dir(path, target_path, force_in_dry_run=False, dirs_exist_ok=False, **k
19781979 if not dirs_exist_ok and os .path .exists (target_path ):
19791980 raise EasyBuildError ("Target location %s to copy %s to already exists" , target_path , path )
19801981
1981- if sys .version_info >= (3 , 8 ):
1982- # on Python >= 3.8, shutil.copytree works fine, thanks to availability of dirs_exist_ok named argument
1983- shutil .copytree (path , target_path , dirs_exist_ok = dirs_exist_ok , ** kwargs )
1982+ # note: in Python >= 3.8 shutil.copytree works just fine thanks to the 'dirs_exist_ok' argument,
1983+ # but since we need to be more careful in earlier Python versions we use our own implementation
1984+ # in case the target directory exists and 'dirs_exist_ok' is enabled
1985+ if dirs_exist_ok and os .path .exists (target_path ):
1986+ # if target directory already exists (and that's allowed via dirs_exist_ok),
1987+ # we need to be more careful, since shutil.copytree will fail (in Python < 3.8)
1988+ # if target directory already exists;
1989+ # so, recurse via 'copy' function to copy files/dirs in source path to target path
1990+ # (NOTE: don't use distutils.dir_util.copy_tree here, see
1991+ # https://github.com/easybuilders/easybuild-framework/issues/3306)
1992+
1993+ entries = os .listdir (path )
19841994
1985- elif dirs_exist_ok :
1986- # use distutils.dir_util.copy_tree with Python < 3.8 if dirs_exist_ok is enabled
1995+ # take into account 'ignore' function that is supported by shutil.copytree
1996+ # (but not by 'copy_file' function used by 'copy')
1997+ ignore = kwargs .get ('ignore' )
1998+ if ignore :
1999+ ignored_entries = ignore (path , entries )
2000+ entries = [x for x in entries if x not in ignored_entries ]
19872001
1988- # first get value for symlinks named argument (if any)
1989- preserve_symlinks = kwargs . pop ( 'symlinks' , False )
2002+ # determine list of paths to copy
2003+ paths_to_copy = [ os . path . join ( path , x ) for x in entries ]
19902004
1991- # check if there are other named arguments (there shouldn't be, only 'symlinks' is supported)
1992- if kwargs :
1993- raise EasyBuildError ("Unknown named arguments passed to copy_dir with dirs_exist_ok=True: %s" ,
1994- ', ' .join (sorted (kwargs .keys ())))
1995- distutils .dir_util .copy_tree (path , target_path , preserve_symlinks = preserve_symlinks )
2005+ copy (paths_to_copy , target_path ,
2006+ force_in_dry_run = force_in_dry_run , dirs_exist_ok = dirs_exist_ok , ** kwargs )
19962007
19972008 else :
1998- # if dirs_exist_ok is not enabled, just use shutil.copytree
2009+ # if dirs_exist_ok is not enabled or target directory doesn't exist , just use shutil.copytree
19992010 shutil .copytree (path , target_path , ** kwargs )
20002011
20012012 _log .info ("%s copied to %s" , path , target_path )
2002- except (IOError , OSError ) as err :
2013+ except (IOError , OSError , shutil . Error ) as err :
20032014 raise EasyBuildError ("Failed to copy directory %s to %s: %s" , path , target_path , err )
20042015
20052016
2006- def copy (paths , target_path , force_in_dry_run = False ):
2017+ def copy (paths , target_path , force_in_dry_run = False , ** kwargs ):
20072018 """
20082019 Copy single file/directory or list of files and directories to specified location
20092020
20102021 :param paths: path(s) to copy
20112022 :param target_path: target location
20122023 :param force_in_dry_run: force running the command during dry run
2024+ :param kwargs: additional named arguments to pass down to copy_dir
20132025 """
20142026 if isinstance (paths , string_type ):
20152027 paths = [paths ]
@@ -2020,10 +2032,11 @@ def copy(paths, target_path, force_in_dry_run=False):
20202032 full_target_path = os .path .join (target_path , os .path .basename (path ))
20212033 mkdir (os .path .dirname (full_target_path ), parents = True )
20222034
2023- if os .path .isfile (path ):
2035+ # copy broken symlinks only if 'symlinks=True' is used
2036+ if os .path .isfile (path ) or (os .path .islink (path ) and kwargs .get ('symlinks' )):
20242037 copy_file (path , full_target_path , force_in_dry_run = force_in_dry_run )
20252038 elif os .path .isdir (path ):
2026- copy_dir (path , full_target_path , force_in_dry_run = force_in_dry_run )
2039+ copy_dir (path , full_target_path , force_in_dry_run = force_in_dry_run , ** kwargs )
20272040 else :
20282041 raise EasyBuildError ("Specified path to copy is not an existing file or directory: %s" , path )
20292042
0 commit comments