4040"""
4141import datetime
4242import difflib
43- import distutils .dir_util
4443import fileinput
4544import glob
4645import hashlib
@@ -499,7 +498,13 @@ def pypi_source_urls(pkg_name):
499498 _log .debug ("Failed to download %s to determine available PyPI URLs for %s" , simple_url , pkg_name )
500499 res = []
501500 else :
502- parsed_html = ElementTree .parse (urls_html )
501+ urls_txt = read_file (urls_html )
502+
503+ # ignore yanked releases (see https://pypi.org/help/#yanked)
504+ # see https://github.com/easybuilders/easybuild-framework/issues/3301
505+ urls_txt = re .sub (r'<a.*?data-yanked.*?</a>' , '' , urls_txt )
506+
507+ parsed_html = ElementTree .ElementTree (ElementTree .fromstring (urls_txt ))
503508 if hasattr (parsed_html , 'iter' ):
504509 res = [a .attrib ['href' ] for a in parsed_html .iter ('a' )]
505510 else :
@@ -762,6 +767,18 @@ def find_easyconfigs(path, ignore_dirs=None):
762767 return files
763768
764769
770+ def find_glob_pattern (glob_pattern , fail_on_no_match = True ):
771+ """Find unique file/dir matching glob_pattern (raises error if more than one match is found)"""
772+ if build_option ('extended_dry_run' ):
773+ return glob_pattern
774+ res = glob .glob (glob_pattern )
775+ if len (res ) == 0 and not fail_on_no_match :
776+ return None
777+ if len (res ) != 1 :
778+ raise EasyBuildError ("Was expecting exactly one match for '%s', found %d: %s" , glob_pattern , len (res ), res )
779+ return res [0 ]
780+
781+
765782def search_file (paths , query , short = False , ignore_dirs = None , silent = False , filename_only = False , terse = False ,
766783 case_sensitive = False ):
767784 """
@@ -2008,7 +2025,12 @@ def copy_file(path, target_path, force_in_dry_run=False):
20082025 _log .info ("Copied contents of file %s to %s" , path , target_path )
20092026 else :
20102027 mkdir (os .path .dirname (target_path ), parents = True )
2011- shutil .copy2 (path , target_path )
2028+ if os .path .exists (path ):
2029+ shutil .copy2 (path , target_path )
2030+ elif os .path .islink (path ):
2031+ # special care for copying broken symlinks
2032+ link_target = os .readlink (path )
2033+ symlink (link_target , target_path )
20122034 _log .info ("%s copied to %s" , path , target_path )
20132035 except (IOError , OSError , shutil .Error ) as err :
20142036 raise EasyBuildError ("Failed to copy file %s to %s: %s" , path , target_path , err )
@@ -2043,16 +2065,13 @@ def copy_dir(path, target_path, force_in_dry_run=False, dirs_exist_ok=False, **k
20432065 :param path: the original directory path
20442066 :param target_path: path to copy the directory to
20452067 :param force_in_dry_run: force running the command during dry run
2046- :param dirs_exist_ok: wrapper around shutil.copytree option, which was added in Python 3.8
2068+ :param dirs_exist_ok: boolean indicating whether it's OK if the target directory already exists
20472069
2048- On Python >= 3.8 shutil.copytree is always used
2049- On Python < 3.8 if 'dirs_exist_ok' is False - shutil.copytree is used
2050- On Python < 3.8 if 'dirs_exist_ok' is True - distutils.dir_util.copy_tree is used
2070+ shutil.copytree is used if the target path does not exist yet;
2071+ if the target path already exists, the 'copy' function will be used to copy the contents of
2072+ the source path to the target path
20512073
2052- Additional specified named arguments are passed down to shutil.copytree if used.
2053-
2054- Because distutils.dir_util.copy_tree supports only 'symlinks' named argument,
2055- using any other will raise EasyBuildError.
2074+ Additional specified named arguments are passed down to shutil.copytree/copy if used.
20562075 """
20572076 if not force_in_dry_run and build_option ('extended_dry_run' ):
20582077 dry_run_msg ("copied directory %s to %s" % (path , target_path ))
@@ -2061,38 +2080,49 @@ def copy_dir(path, target_path, force_in_dry_run=False, dirs_exist_ok=False, **k
20612080 if not dirs_exist_ok and os .path .exists (target_path ):
20622081 raise EasyBuildError ("Target location %s to copy %s to already exists" , target_path , path )
20632082
2064- if sys .version_info >= (3 , 8 ):
2065- # on Python >= 3.8, shutil.copytree works fine, thanks to availability of dirs_exist_ok named argument
2066- shutil .copytree (path , target_path , dirs_exist_ok = dirs_exist_ok , ** kwargs )
2083+ # note: in Python >= 3.8 shutil.copytree works just fine thanks to the 'dirs_exist_ok' argument,
2084+ # but since we need to be more careful in earlier Python versions we use our own implementation
2085+ # in case the target directory exists and 'dirs_exist_ok' is enabled
2086+ if dirs_exist_ok and os .path .exists (target_path ):
2087+ # if target directory already exists (and that's allowed via dirs_exist_ok),
2088+ # we need to be more careful, since shutil.copytree will fail (in Python < 3.8)
2089+ # if target directory already exists;
2090+ # so, recurse via 'copy' function to copy files/dirs in source path to target path
2091+ # (NOTE: don't use distutils.dir_util.copy_tree here, see
2092+ # https://github.com/easybuilders/easybuild-framework/issues/3306)
2093+
2094+ entries = os .listdir (path )
20672095
2068- elif dirs_exist_ok :
2069- # use distutils.dir_util.copy_tree with Python < 3.8 if dirs_exist_ok is enabled
2096+ # take into account 'ignore' function that is supported by shutil.copytree
2097+ # (but not by 'copy_file' function used by 'copy')
2098+ ignore = kwargs .get ('ignore' )
2099+ if ignore :
2100+ ignored_entries = ignore (path , entries )
2101+ entries = [x for x in entries if x not in ignored_entries ]
20702102
2071- # first get value for symlinks named argument (if any)
2072- preserve_symlinks = kwargs . pop ( 'symlinks' , False )
2103+ # determine list of paths to copy
2104+ paths_to_copy = [ os . path . join ( path , x ) for x in entries ]
20732105
2074- # check if there are other named arguments (there shouldn't be, only 'symlinks' is supported)
2075- if kwargs :
2076- raise EasyBuildError ("Unknown named arguments passed to copy_dir with dirs_exist_ok=True: %s" ,
2077- ', ' .join (sorted (kwargs .keys ())))
2078- distutils .dir_util .copy_tree (path , target_path , preserve_symlinks = preserve_symlinks )
2106+ copy (paths_to_copy , target_path ,
2107+ force_in_dry_run = force_in_dry_run , dirs_exist_ok = dirs_exist_ok , ** kwargs )
20792108
20802109 else :
2081- # if dirs_exist_ok is not enabled, just use shutil.copytree
2110+ # if dirs_exist_ok is not enabled or target directory doesn't exist , just use shutil.copytree
20822111 shutil .copytree (path , target_path , ** kwargs )
20832112
20842113 _log .info ("%s copied to %s" , path , target_path )
2085- except (IOError , OSError ) as err :
2114+ except (IOError , OSError , shutil . Error ) as err :
20862115 raise EasyBuildError ("Failed to copy directory %s to %s: %s" , path , target_path , err )
20872116
20882117
2089- def copy (paths , target_path , force_in_dry_run = False ):
2118+ def copy (paths , target_path , force_in_dry_run = False , ** kwargs ):
20902119 """
20912120 Copy single file/directory or list of files and directories to specified location
20922121
20932122 :param paths: path(s) to copy
20942123 :param target_path: target location
20952124 :param force_in_dry_run: force running the command during dry run
2125+ :param kwargs: additional named arguments to pass down to copy_dir
20962126 """
20972127 if isinstance (paths , string_type ):
20982128 paths = [paths ]
@@ -2103,10 +2133,11 @@ def copy(paths, target_path, force_in_dry_run=False):
21032133 full_target_path = os .path .join (target_path , os .path .basename (path ))
21042134 mkdir (os .path .dirname (full_target_path ), parents = True )
21052135
2106- if os .path .isfile (path ):
2136+ # copy broken symlinks only if 'symlinks=True' is used
2137+ if os .path .isfile (path ) or (os .path .islink (path ) and kwargs .get ('symlinks' )):
21072138 copy_file (path , full_target_path , force_in_dry_run = force_in_dry_run )
21082139 elif os .path .isdir (path ):
2109- copy_dir (path , full_target_path , force_in_dry_run = force_in_dry_run )
2140+ copy_dir (path , full_target_path , force_in_dry_run = force_in_dry_run , ** kwargs )
21102141 else :
21112142 raise EasyBuildError ("Specified path to copy is not an existing file or directory: %s" , path )
21122143
0 commit comments