4242import copy
4343import glob
4444import inspect
45+ import json
4546import os
4647import re
4748import stat
6768from easybuild .tools .build_details import get_build_stats
6869from easybuild .tools .build_log import EasyBuildError , dry_run_msg , dry_run_warning , dry_run_set_dirs
6970from easybuild .tools .build_log import print_error , print_msg , print_warning
70- from easybuild .tools .config import DEFAULT_ENVVAR_USERS_MODULES
71+ from easybuild .tools .config import CHECKSUM_PRIORITY_JSON , DEFAULT_ENVVAR_USERS_MODULES
7172from easybuild .tools .config import FORCE_DOWNLOAD_ALL , FORCE_DOWNLOAD_PATCHES , FORCE_DOWNLOAD_SOURCES
7273from easybuild .tools .config import build_option , build_path , get_log_filename , get_repository , get_repositorypath
7374from easybuild .tools .config import install_path , log_path , package_path , source_paths
@@ -156,6 +157,7 @@ def __init__(self, ec):
156157 self .patches = []
157158 self .src = []
158159 self .checksums = []
160+ self .json_checksums = None
159161
160162 # build/install directories
161163 self .builddir = None
@@ -347,23 +349,55 @@ def get_checksum_for(self, checksums, filename=None, index=None):
347349 Obtain checksum for given filename.
348350
349351 :param checksums: a list or tuple of checksums (or None)
350- :param filename: name of the file to obtain checksum for (Deprecated)
352+ :param filename: name of the file to obtain checksum for
351353 :param index: index of file in list
352354 """
353- # Filename has never been used; flag it as deprecated
354- if filename :
355- self .log .deprecated ("Filename argument to get_checksum_for() is deprecated" , '5.0' )
355+ checksum = None
356+
357+ # sometimes, filename are specified as a dict
358+ if isinstance (filename , dict ):
359+ filename = filename ['filename' ]
356360
357361 # if checksums are provided as a dict, lookup by source filename as key
358- if isinstance (checksums , (list , tuple )):
362+ if isinstance (checksums , dict ):
363+ if filename is not None and filename in checksums :
364+ checksum = checksums [filename ]
365+ else :
366+ checksum = None
367+ elif isinstance (checksums , (list , tuple )):
359368 if index is not None and index < len (checksums ) and (index >= 0 or abs (index ) <= len (checksums )):
360- return checksums [index ]
369+ checksum = checksums [index ]
361370 else :
362- return None
371+ checksum = None
363372 elif checksums is None :
364- return None
373+ checksum = None
374+ else :
375+ raise EasyBuildError ("Invalid type for checksums (%s), should be dict, list, tuple or None." ,
376+ type (checksums ))
377+
378+ if checksum is None or build_option ("checksum_priority" ) == CHECKSUM_PRIORITY_JSON :
379+ json_checksums = self .get_checksums_from_json ()
380+ return json_checksums .get (filename , None )
365381 else :
366- raise EasyBuildError ("Invalid type for checksums (%s), should be list, tuple or None." , type (checksums ))
382+ return checksum
383+
384+ def get_checksums_from_json (self , always_read = False ):
385+ """
386+ Get checksums for this software that are provided in a checksums.json file
387+
388+ :param: always_read: always read the checksums.json file, even if it has been read before
389+ """
390+ if always_read or self .json_checksums is None :
391+ try :
392+ path = self .obtain_file ("checksums.json" , no_download = True )
393+ self .log .info ("Loading checksums from file %s" , path )
394+ json_txt = read_file (path )
395+ self .json_checksums = json .loads (json_txt )
396+ # if the file can't be found, return an empty dict
397+ except EasyBuildError :
398+ self .json_checksums = {}
399+
400+ return self .json_checksums
367401
368402 def fetch_source (self , source , checksum = None , extension = False , download_instructions = None ):
369403 """
@@ -445,7 +479,8 @@ def fetch_sources(self, sources=None, checksums=None):
445479 if source is None :
446480 raise EasyBuildError ("Empty source in sources list at index %d" , index )
447481
448- src_spec = self .fetch_source (source , self .get_checksum_for (checksums = checksums , index = index ))
482+ checksum = self .get_checksum_for (checksums = checksums , filename = source , index = index )
483+ src_spec = self .fetch_source (source , checksum = checksum )
449484 if src_spec :
450485 self .src .append (src_spec )
451486 else :
@@ -477,7 +512,7 @@ def fetch_patches(self, patch_specs=None, extension=False, checksums=None):
477512 if path :
478513 self .log .debug ('File %s found for patch %s' , path , patch_spec )
479514 patch_info ['path' ] = path
480- patch_info ['checksum' ] = self .get_checksum_for (checksums , index = index )
515+ patch_info ['checksum' ] = self .get_checksum_for (checksums , filename = patch_info [ 'name' ], index = index )
481516
482517 if extension :
483518 patches .append (patch_info )
@@ -638,7 +673,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
638673
639674 # verify checksum (if provided)
640675 self .log .debug ('Verifying checksums for extension source...' )
641- fn_checksum = self .get_checksum_for (checksums , index = 0 )
676+ fn_checksum = self .get_checksum_for (checksums , filename = src_fn , index = 0 )
642677 if verify_checksum (src_path , fn_checksum ):
643678 self .log .info ('Checksum for extension source %s verified' , src_fn )
644679 elif build_option ('ignore_checksums' ):
@@ -672,7 +707,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
672707 patch = patch ['path' ]
673708 patch_fn = os .path .basename (patch )
674709
675- checksum = self .get_checksum_for (checksums [ 1 :], index = idx )
710+ checksum = self .get_checksum_for (checksums , filename = patch_fn , index = idx + 1 )
676711 if verify_checksum (patch , checksum ):
677712 self .log .info ('Checksum for extension patch %s verified' , patch_fn )
678713 elif build_option ('ignore_checksums' ):
@@ -694,7 +729,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
694729 return exts_sources
695730
696731 def obtain_file (self , filename , extension = False , urls = None , download_filename = None , force_download = False ,
697- git_config = None , download_instructions = None , alt_location = None ):
732+ git_config = None , no_download = False , download_instructions = None , alt_location = None ):
698733 """
699734 Locate the file with the given name
700735 - searches in different subdirectories of source path
@@ -705,6 +740,7 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
705740 :param download_filename: filename with which the file should be downloaded, and then renamed to <filename>
706741 :param force_download: always try to download file, even if it's already available in source path
707742 :param git_config: dictionary to define how to download a git repository
743+ :param no_download: do not try to download the file
708744 :param download_instructions: instructions to manually add source (used for complex cases)
709745 :param alt_location: alternative location to use instead of self.name
710746 """
@@ -818,6 +854,13 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
818854 if self .dry_run :
819855 self .dry_run_msg (" * %s found at %s" , filename , foundfile )
820856 return foundfile
857+ elif no_download :
858+ if self .dry_run :
859+ self .dry_run_msg (" * %s (MISSING)" , filename )
860+ return filename
861+ else :
862+ raise EasyBuildError ("Couldn't find file %s anywhere, and downloading it is disabled... "
863+ "Paths attempted (in order): %s " , filename , ', ' .join (failedpaths ))
821864 elif git_config :
822865 return get_source_tarball_from_git (filename , targetdir , git_config )
823866 else :
@@ -2280,7 +2323,7 @@ def fetch_step(self, skip_checksums=False):
22802323
22812324 # fetch patches
22822325 if self .cfg ['patches' ] + self .cfg ['postinstallpatches' ]:
2283- if isinstance (self .cfg ['checksums' ], (list , tuple )):
2326+ if self . cfg [ 'checksums' ] and isinstance (self .cfg ['checksums' ], (list , tuple )):
22842327 # if checksums are provided as a list, first entries are assumed to be for sources
22852328 patches_checksums = self .cfg ['checksums' ][len (self .cfg ['sources' ]):]
22862329 else :
@@ -2367,6 +2410,20 @@ def check_checksums_for(self, ent, sub='', source_cnt=None):
23672410 patches = ent .get ('patches' , [])
23682411 checksums = ent .get ('checksums' , [])
23692412
2413+ if not checksums :
2414+ checksums_from_json = self .get_checksums_from_json ()
2415+ # recreate a list of checksums. If each filename is found, the generated list of checksums should match
2416+ # what is expected in list format
2417+ for fn in sources + patches :
2418+ # if the filename is a tuple, the actual source file name is the first element
2419+ if isinstance (fn , tuple ):
2420+ fn = fn [0 ]
2421+ # if the filename is a dict, the actual source file name is the "filename" element
2422+ if isinstance (fn , dict ):
2423+ fn = fn ["filename" ]
2424+ if fn in checksums_from_json .keys ():
2425+ checksums += [checksums_from_json [fn ]]
2426+
23702427 if source_cnt is None :
23712428 source_cnt = len (sources )
23722429 patch_cnt , checksum_cnt = len (patches ), len (checksums )
@@ -4406,6 +4463,67 @@ class StopException(Exception):
44064463 pass
44074464
44084465
4466+ def inject_checksums_to_json (ecs , checksum_type ):
4467+ """
4468+ Inject checksums of given type in corresponding json files
4469+
4470+ :param ecs: list of EasyConfig instances to calculate checksums and inject them into checksums.json
4471+ :param checksum_type: type of checksum to use
4472+ """
4473+ for ec in ecs :
4474+ ec_fn = os .path .basename (ec ['spec' ])
4475+ ec_dir = os .path .dirname (ec ['spec' ])
4476+ print_msg ("injecting %s checksums for %s in checksums.json" % (checksum_type , ec ['spec' ]), log = _log )
4477+
4478+ # get easyblock instance and make sure all sources/patches are available by running fetch_step
4479+ print_msg ("fetching sources & patches for %s..." % ec_fn , log = _log )
4480+ app = get_easyblock_instance (ec )
4481+ app .update_config_template_run_step ()
4482+ app .fetch_step (skip_checksums = True )
4483+
4484+ # compute & inject checksums for sources/patches
4485+ print_msg ("computing %s checksums for sources & patches for %s..." % (checksum_type , ec_fn ), log = _log )
4486+ checksums = {}
4487+ for entry in app .src + app .patches :
4488+ checksum = compute_checksum (entry ['path' ], checksum_type )
4489+ print_msg ("* %s: %s" % (os .path .basename (entry ['path' ]), checksum ), log = _log )
4490+ checksums [os .path .basename (entry ['path' ])] = checksum
4491+
4492+ # compute & inject checksums for extension sources/patches
4493+ if app .exts :
4494+ print_msg ("computing %s checksums for extensions for %s..." % (checksum_type , ec_fn ), log = _log )
4495+
4496+ for ext in app .exts :
4497+ # compute checksums for extension sources & patches
4498+ if 'src' in ext :
4499+ src_fn = os .path .basename (ext ['src' ])
4500+ checksum = compute_checksum (ext ['src' ], checksum_type )
4501+ print_msg (" * %s: %s" % (src_fn , checksum ), log = _log )
4502+ checksums [src_fn ] = checksum
4503+ for ext_patch in ext .get ('patches' , []):
4504+ patch_fn = os .path .basename (ext_patch ['path' ])
4505+ checksum = compute_checksum (ext_patch ['path' ], checksum_type )
4506+ print_msg (" * %s: %s" % (patch_fn , checksum ), log = _log )
4507+ checksums [patch_fn ] = checksum
4508+
4509+ # actually inject new checksums or overwrite existing ones (if --force)
4510+ existing_checksums = app .get_checksums_from_json (always_read = True )
4511+ for filename in checksums :
4512+ if filename not in existing_checksums :
4513+ existing_checksums [filename ] = checksums [filename ]
4514+ # don't do anything if the checksum already exist and is the same
4515+ elif checksums [filename ] != existing_checksums [filename ]:
4516+ if build_option ('force' ):
4517+ print_warning ("Found existing checksums for %s, overwriting them (due to --force)..." % ec_fn )
4518+ existing_checksums [filename ] = checksums [filename ]
4519+ else :
4520+ raise EasyBuildError ("Found existing checksum for %s, use --force to overwrite them" % filename )
4521+
4522+ # actually write the checksums
4523+ with open (os .path .join (ec_dir , 'checksums.json' ), 'w' ) as outfile :
4524+ json .dump (existing_checksums , outfile , indent = 2 , sort_keys = True )
4525+
4526+
44094527def inject_checksums (ecs , checksum_type ):
44104528 """
44114529 Inject checksums of given type in specified easyconfig files
0 commit comments