42
42
import copy
43
43
import glob
44
44
import inspect
45
+ import json
45
46
import os
46
47
import re
47
48
import stat
67
68
from easybuild .tools .build_details import get_build_stats
68
69
from easybuild .tools .build_log import EasyBuildError , dry_run_msg , dry_run_warning , dry_run_set_dirs
69
70
from 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
71
72
from easybuild .tools .config import FORCE_DOWNLOAD_ALL , FORCE_DOWNLOAD_PATCHES , FORCE_DOWNLOAD_SOURCES
72
73
from easybuild .tools .config import build_option , build_path , get_log_filename , get_repository , get_repositorypath
73
74
from easybuild .tools .config import install_path , log_path , package_path , source_paths
@@ -156,6 +157,7 @@ def __init__(self, ec):
156
157
self .patches = []
157
158
self .src = []
158
159
self .checksums = []
160
+ self .json_checksums = None
159
161
160
162
# build/install directories
161
163
self .builddir = None
@@ -347,23 +349,55 @@ def get_checksum_for(self, checksums, filename=None, index=None):
347
349
Obtain checksum for given filename.
348
350
349
351
: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
351
353
:param index: index of file in list
352
354
"""
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' ]
356
360
357
361
# 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 )):
359
368
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 ]
361
370
else :
362
- return None
371
+ checksum = None
363
372
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 )
365
381
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
367
401
368
402
def fetch_source (self , source , checksum = None , extension = False , download_instructions = None ):
369
403
"""
@@ -445,7 +479,8 @@ def fetch_sources(self, sources=None, checksums=None):
445
479
if source is None :
446
480
raise EasyBuildError ("Empty source in sources list at index %d" , index )
447
481
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 )
449
484
if src_spec :
450
485
self .src .append (src_spec )
451
486
else :
@@ -477,7 +512,7 @@ def fetch_patches(self, patch_specs=None, extension=False, checksums=None):
477
512
if path :
478
513
self .log .debug ('File %s found for patch %s' , path , patch_spec )
479
514
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 )
481
516
482
517
if extension :
483
518
patches .append (patch_info )
@@ -638,7 +673,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
638
673
639
674
# verify checksum (if provided)
640
675
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 )
642
677
if verify_checksum (src_path , fn_checksum ):
643
678
self .log .info ('Checksum for extension source %s verified' , src_fn )
644
679
elif build_option ('ignore_checksums' ):
@@ -672,7 +707,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
672
707
patch = patch ['path' ]
673
708
patch_fn = os .path .basename (patch )
674
709
675
- checksum = self .get_checksum_for (checksums [ 1 :], index = idx )
710
+ checksum = self .get_checksum_for (checksums , filename = patch_fn , index = idx + 1 )
676
711
if verify_checksum (patch , checksum ):
677
712
self .log .info ('Checksum for extension patch %s verified' , patch_fn )
678
713
elif build_option ('ignore_checksums' ):
@@ -694,7 +729,7 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
694
729
return exts_sources
695
730
696
731
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 ):
698
733
"""
699
734
Locate the file with the given name
700
735
- searches in different subdirectories of source path
@@ -705,6 +740,7 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
705
740
:param download_filename: filename with which the file should be downloaded, and then renamed to <filename>
706
741
:param force_download: always try to download file, even if it's already available in source path
707
742
:param git_config: dictionary to define how to download a git repository
743
+ :param no_download: do not try to download the file
708
744
:param download_instructions: instructions to manually add source (used for complex cases)
709
745
:param alt_location: alternative location to use instead of self.name
710
746
"""
@@ -818,6 +854,13 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
818
854
if self .dry_run :
819
855
self .dry_run_msg (" * %s found at %s" , filename , foundfile )
820
856
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 ))
821
864
elif git_config :
822
865
return get_source_tarball_from_git (filename , targetdir , git_config )
823
866
else :
@@ -2280,7 +2323,7 @@ def fetch_step(self, skip_checksums=False):
2280
2323
2281
2324
# fetch patches
2282
2325
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 )):
2284
2327
# if checksums are provided as a list, first entries are assumed to be for sources
2285
2328
patches_checksums = self .cfg ['checksums' ][len (self .cfg ['sources' ]):]
2286
2329
else :
@@ -2367,6 +2410,20 @@ def check_checksums_for(self, ent, sub='', source_cnt=None):
2367
2410
patches = ent .get ('patches' , [])
2368
2411
checksums = ent .get ('checksums' , [])
2369
2412
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
+
2370
2427
if source_cnt is None :
2371
2428
source_cnt = len (sources )
2372
2429
patch_cnt , checksum_cnt = len (patches ), len (checksums )
@@ -4406,6 +4463,67 @@ class StopException(Exception):
4406
4463
pass
4407
4464
4408
4465
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
+
4409
4527
def inject_checksums (ecs , checksum_type ):
4410
4528
"""
4411
4529
Inject checksums of given type in specified easyconfig files
0 commit comments