4040"""
4141import datetime
4242import difflib
43- import distutils .dir_util
4443import fileinput
4544import glob
4645import hashlib
@@ -514,7 +513,13 @@ def pypi_source_urls(pkg_name):
514513 _log .debug ("Failed to download %s to determine available PyPI URLs for %s" , simple_url , pkg_name )
515514 res = []
516515 else :
517- parsed_html = ElementTree .parse (urls_html )
516+ urls_txt = read_file (urls_html )
517+
518+ # ignore yanked releases (see https://pypi.org/help/#yanked)
519+ # see https://github.com/easybuilders/easybuild-framework/issues/3301
520+ urls_txt = re .sub (r'<a.*?data-yanked.*?</a>' , '' , urls_txt )
521+
522+ parsed_html = ElementTree .ElementTree (ElementTree .fromstring (urls_txt ))
518523 if hasattr (parsed_html , 'iter' ):
519524 res = [a .attrib ['href' ] for a in parsed_html .iter ('a' )]
520525 else :
@@ -777,6 +782,18 @@ def find_easyconfigs(path, ignore_dirs=None):
777782 return files
778783
779784
785+ def find_glob_pattern (glob_pattern , fail_on_no_match = True ):
786+ """Find unique file/dir matching glob_pattern (raises error if more than one match is found)"""
787+ if build_option ('extended_dry_run' ):
788+ return glob_pattern
789+ res = glob .glob (glob_pattern )
790+ if len (res ) == 0 and not fail_on_no_match :
791+ return None
792+ if len (res ) != 1 :
793+ raise EasyBuildError ("Was expecting exactly one match for '%s', found %d: %s" , glob_pattern , len (res ), res )
794+ return res [0 ]
795+
796+
780797def search_file (paths , query , short = False , ignore_dirs = None , silent = False , filename_only = False , terse = False ,
781798 case_sensitive = False ):
782799 """
@@ -1927,7 +1944,12 @@ def copy_file(path, target_path, force_in_dry_run=False):
19271944 _log .info ("Copied contents of file %s to %s" , path , target_path )
19281945 else :
19291946 mkdir (os .path .dirname (target_path ), parents = True )
1930- shutil .copy2 (path , target_path )
1947+ if os .path .exists (path ):
1948+ shutil .copy2 (path , target_path )
1949+ elif os .path .islink (path ):
1950+ # special care for copying broken symlinks
1951+ link_target = os .readlink (path )
1952+ symlink (link_target , target_path )
19311953 _log .info ("%s copied to %s" , path , target_path )
19321954 except (IOError , OSError , shutil .Error ) as err :
19331955 raise EasyBuildError ("Failed to copy file %s to %s: %s" , path , target_path , err )
@@ -1962,16 +1984,13 @@ def copy_dir(path, target_path, force_in_dry_run=False, dirs_exist_ok=False, **k
19621984 :param path: the original directory path
19631985 :param target_path: path to copy the directory to
19641986 :param force_in_dry_run: force running the command during dry run
1965- :param dirs_exist_ok: wrapper around shutil.copytree option, which was added in Python 3.8
1987+ :param dirs_exist_ok: boolean indicating whether it's OK if the target directory already exists
19661988
1967- On Python >= 3.8 shutil.copytree is always used
1968- On Python < 3.8 if 'dirs_exist_ok' is False - shutil.copytree is used
1969- On Python < 3.8 if 'dirs_exist_ok' is True - distutils.dir_util.copy_tree is used
1989+ shutil.copytree is used if the target path does not exist yet;
1990+ if the target path already exists, the 'copy' function will be used to copy the contents of
1991+ the source path to the target path
19701992
1971- Additional specified named arguments are passed down to shutil.copytree if used.
1972-
1973- Because distutils.dir_util.copy_tree supports only 'symlinks' named argument,
1974- using any other will raise EasyBuildError.
1993+ Additional specified named arguments are passed down to shutil.copytree/copy if used.
19751994 """
19761995 if not force_in_dry_run and build_option ('extended_dry_run' ):
19771996 dry_run_msg ("copied directory %s to %s" % (path , target_path ))
@@ -1980,38 +1999,49 @@ def copy_dir(path, target_path, force_in_dry_run=False, dirs_exist_ok=False, **k
19801999 if not dirs_exist_ok and os .path .exists (target_path ):
19812000 raise EasyBuildError ("Target location %s to copy %s to already exists" , target_path , path )
19822001
1983- if sys .version_info >= (3 , 8 ):
1984- # on Python >= 3.8, shutil.copytree works fine, thanks to availability of dirs_exist_ok named argument
1985- shutil .copytree (path , target_path , dirs_exist_ok = dirs_exist_ok , ** kwargs )
2002+ # note: in Python >= 3.8 shutil.copytree works just fine thanks to the 'dirs_exist_ok' argument,
2003+ # but since we need to be more careful in earlier Python versions we use our own implementation
2004+ # in case the target directory exists and 'dirs_exist_ok' is enabled
2005+ if dirs_exist_ok and os .path .exists (target_path ):
2006+ # if target directory already exists (and that's allowed via dirs_exist_ok),
2007+ # we need to be more careful, since shutil.copytree will fail (in Python < 3.8)
2008+ # if target directory already exists;
2009+ # so, recurse via 'copy' function to copy files/dirs in source path to target path
2010+ # (NOTE: don't use distutils.dir_util.copy_tree here, see
2011+ # https://github.com/easybuilders/easybuild-framework/issues/3306)
2012+
2013+ entries = os .listdir (path )
19862014
1987- elif dirs_exist_ok :
1988- # use distutils.dir_util.copy_tree with Python < 3.8 if dirs_exist_ok is enabled
2015+ # take into account 'ignore' function that is supported by shutil.copytree
2016+ # (but not by 'copy_file' function used by 'copy')
2017+ ignore = kwargs .get ('ignore' )
2018+ if ignore :
2019+ ignored_entries = ignore (path , entries )
2020+ entries = [x for x in entries if x not in ignored_entries ]
19892021
1990- # first get value for symlinks named argument (if any)
1991- preserve_symlinks = kwargs . pop ( 'symlinks' , False )
2022+ # determine list of paths to copy
2023+ paths_to_copy = [ os . path . join ( path , x ) for x in entries ]
19922024
1993- # check if there are other named arguments (there shouldn't be, only 'symlinks' is supported)
1994- if kwargs :
1995- raise EasyBuildError ("Unknown named arguments passed to copy_dir with dirs_exist_ok=True: %s" ,
1996- ', ' .join (sorted (kwargs .keys ())))
1997- distutils .dir_util .copy_tree (path , target_path , preserve_symlinks = preserve_symlinks )
2025+ copy (paths_to_copy , target_path ,
2026+ force_in_dry_run = force_in_dry_run , dirs_exist_ok = dirs_exist_ok , ** kwargs )
19982027
19992028 else :
2000- # if dirs_exist_ok is not enabled, just use shutil.copytree
2029+ # if dirs_exist_ok is not enabled or target directory doesn't exist , just use shutil.copytree
20012030 shutil .copytree (path , target_path , ** kwargs )
20022031
20032032 _log .info ("%s copied to %s" , path , target_path )
2004- except (IOError , OSError ) as err :
2033+ except (IOError , OSError , shutil . Error ) as err :
20052034 raise EasyBuildError ("Failed to copy directory %s to %s: %s" , path , target_path , err )
20062035
20072036
2008- def copy (paths , target_path , force_in_dry_run = False ):
2037+ def copy (paths , target_path , force_in_dry_run = False , ** kwargs ):
20092038 """
20102039 Copy single file/directory or list of files and directories to specified location
20112040
20122041 :param paths: path(s) to copy
20132042 :param target_path: target location
20142043 :param force_in_dry_run: force running the command during dry run
2044+ :param kwargs: additional named arguments to pass down to copy_dir
20152045 """
20162046 if isinstance (paths , string_type ):
20172047 paths = [paths ]
@@ -2022,10 +2052,11 @@ def copy(paths, target_path, force_in_dry_run=False):
20222052 full_target_path = os .path .join (target_path , os .path .basename (path ))
20232053 mkdir (os .path .dirname (full_target_path ), parents = True )
20242054
2025- if os .path .isfile (path ):
2055+ # copy broken symlinks only if 'symlinks=True' is used
2056+ if os .path .isfile (path ) or (os .path .islink (path ) and kwargs .get ('symlinks' )):
20262057 copy_file (path , full_target_path , force_in_dry_run = force_in_dry_run )
20272058 elif os .path .isdir (path ):
2028- copy_dir (path , full_target_path , force_in_dry_run = force_in_dry_run )
2059+ copy_dir (path , full_target_path , force_in_dry_run = force_in_dry_run , ** kwargs )
20292060 else :
20302061 raise EasyBuildError ("Specified path to copy is not an existing file or directory: %s" , path )
20312062
0 commit comments