@@ -756,64 +756,170 @@ def post_postproc_cuda(self, *args, **kwargs):
756756 if 'libcudart' not in allowlist :
757757 raise EasyBuildError ("Did not find 'libcudart' in allowlist: %s" % allowlist )
758758
759- # iterate over all files in the CUDA installation directory
760- for dir_path , _ , files in os .walk (self .installdir ):
761- for filename in files :
762- full_path = os .path .join (dir_path , filename )
763- # we only really care about real files, i.e. not symlinks
764- if not os .path .islink (full_path ):
765- # check if the current file name stub is part of the allowlist
766- basename = filename .split ('.' )[0 ]
767- if basename in allowlist :
768- self .log .debug ("%s is found in allowlist, so keeping it: %s" , basename , full_path )
769- else :
770- self .log .debug ("%s is not found in allowlist, so replacing it with symlink: %s" ,
771- basename , full_path )
772- # if it is not in the allowlist, delete the file and create a symlink to host_injections
773-
774- # the host_injections path is under a fixed repo/location for CUDA
775- host_inj_path = re .sub (EESSI_INSTALLATION_REGEX , HOST_INJECTIONS_LOCATION , full_path )
776- # CUDA itself doesn't care about compute capability so remove this duplication from
777- # under host_injections (symlink to a single CUDA installation for all compute
778- # capabilities)
779- accel_subdir = os .getenv ("EESSI_ACCELERATOR_TARGET" )
780- if accel_subdir :
781- host_inj_path = host_inj_path .replace ("/accel/%s" % accel_subdir , '' )
782- # make sure source and target of symlink are not the same
783- if full_path == host_inj_path :
784- raise EasyBuildError ("Source (%s) and target (%s) are the same location, are you sure you "
785- "are using this hook for an EESSI installation?" ,
786- full_path , host_inj_path )
787- remove_file (full_path )
788- symlink (host_inj_path , full_path )
759+ # replace files that are not distributable with symlinks into
760+ # host_injections
761+ replace_non_distributable_files_with_symlinks (self .log , self .installdir , self .name , allowlist )
789762 else :
790763 raise EasyBuildError ("CUDA-specific hook triggered for non-CUDA easyconfig?!" )
791764
792765
766+ def post_postproc_cudnn (self , * args , ** kwargs ):
767+ """
768+ Remove files from cuDNN installation that we are not allowed to ship,
769+ and replace them with a symlink to a corresponding installation under host_injections.
770+ """
771+
772+ # We need to check if we are doing an EESSI-distributed installation
773+ eessi_installation = bool (re .search (EESSI_INSTALLATION_REGEX , self .installdir ))
774+
775+ if self .name == 'cuDNN' and eessi_installation :
776+ print_msg ("Replacing files in cuDNN installation that we can not ship with symlinks to host_injections..." )
777+
778+ allowlist = ['LICENSE' ]
779+
780+ # read cuDNN LICENSE, construct allowlist based on section "2. Distribution" that specifies list of files that can be shipped
781+ license_path = os .path .join (self .installdir , 'LICENSE' )
782+ search_string = "2. Distribution. The following portions of the SDK are distributable under the Agreement:"
783+ found_search_string = False
784+ with open (license_path ) as infile :
785+ for line in infile :
786+ if line .strip ().startswith (search_string ):
787+ found_search_string = True
788+ # remove search string, split into words, remove trailing
789+ # dots '.' and only retain words starting with a dot '.'
790+ distributable = line [len (search_string ):]
791+ # distributable looks like ' the runtime files .so and .dll.'
792+ # note the '.' after '.dll'
793+ for word in distributable .split ():
794+ if word [0 ] == '.' :
795+ # rstrip is used to remove the '.' after '.dll'
796+ allowlist .append (word .rstrip ('.' ))
797+ if not found_search_string :
798+ # search string wasn't found in LICENSE file
799+ raise EasyBuildError ("search string '%s' was not found in license file '%s';"
800+ "hence installation may be replaced by symlinks only" ,
801+ search_string , license_path )
802+
803+ allowlist = sorted (set (allowlist ))
804+ self .log .info ("Allowlist for files in cuDNN installation that can be redistributed: " + ', ' .join (allowlist ))
805+
806+ # replace files that are not distributable with symlinks into
807+ # host_injections
808+ replace_non_distributable_files_with_symlinks (self .log , self .installdir , self .name , allowlist )
809+ else :
810+ raise EasyBuildError ("cuDNN-specific hook triggered for non-cuDNN easyconfig?!" )
811+
812+
813+ def replace_non_distributable_files_with_symlinks (log , install_dir , pkg_name , allowlist ):
814+ """
815+ Replace files that cannot be distributed with symlinks into host_injections
816+ """
817+ # Different packages use different ways to specify which files or file
818+ # 'types' may be redistributed. For CUDA, the 'EULA.txt' lists full file
819+ # names. For cuDNN, the 'LICENSE' lists file endings/suffixes (e.g., '.so')
820+ # that can be redistributed.
821+ # The map 'extension_based' defines which of these two ways are employed. If
822+ # full file names are used it maps a package name (key) to False (value). If
823+ # endings/suffixes are used, it maps a package name to True. Later we can
824+ # easily use this data structure to employ the correct method for
825+ # postprocessing an installation.
826+ extension_based = {
827+ "CUDA" : False ,
828+ "cuDNN" : True ,
829+ }
830+ if not pkg_name in extension_based :
831+ raise EasyBuildError ("Don't know how to strip non-distributable files from package %s." , pkg_name )
832+
833+ # iterate over all files in the package installation directory
834+ for dir_path , _ , files in os .walk (install_dir ):
835+ for filename in files :
836+ full_path = os .path .join (dir_path , filename )
837+ # we only really care about real files, i.e. not symlinks
838+ if not os .path .islink (full_path ):
839+ check_by_extension = extension_based [pkg_name ] and '.' in filename
840+ if check_by_extension :
841+ # if the allowlist only contains extensions, we have to
842+ # determine that from filename. we assume the extension is
843+ # the second element when splitting the filename at dots
844+ # (e.g., for 'libcudnn_adv_infer.so.8.9.2' the extension
845+ # would be '.so')
846+ extension = '.' + filename .split ('.' )[1 ]
847+ # check if the current file name stub or its extension is part of the allowlist
848+ basename = filename .split ('.' )[0 ]
849+ if basename in allowlist :
850+ log .debug ("%s is found in allowlist, so keeping it: %s" , basename , full_path )
851+ elif check_by_extension and extension in allowlist :
852+ log .debug ("%s is found in allowlist, so keeping it: %s" , extension , full_path )
853+ else :
854+ print_name = filename if extension_based [pkg_name ] else basename
855+ log .debug ("%s is not found in allowlist, so replacing it with symlink: %s" ,
856+ print_name , full_path )
857+ # the host_injections path is under a fixed repo/location for CUDA or cuDNN
858+ host_inj_path = re .sub (EESSI_INSTALLATION_REGEX , HOST_INJECTIONS_LOCATION , full_path )
859+ # CUDA and cu* libraries themselves don't care about compute capability so remove this
860+ # duplication from under host_injections (symlink to a single CUDA or cu* library
861+ # installation for all compute capabilities)
862+ accel_subdir = os .getenv ("EESSI_ACCELERATOR_TARGET" )
863+ if accel_subdir :
864+ host_inj_path = host_inj_path .replace ("/accel/%s" % accel_subdir , '' )
865+ # make sure source and target of symlink are not the same
866+ if full_path == host_inj_path :
867+ raise EasyBuildError ("Source (%s) and target (%s) are the same location, are you sure you "
868+ "are using this hook for an EESSI installation?" ,
869+ full_path , host_inj_path )
870+ remove_file (full_path )
871+ symlink (host_inj_path , full_path )
872+
873+
793874def inject_gpu_property (ec ):
794875 """
795- Add 'gpu' property, via modluafooter easyconfig parameter
876+ Add 'gpu' property and EESSI<PACKAGE>VERSION envvars via modluafooter
877+ easyconfig parameter, and drop dependencies to build dependencies
796878 """
797879 ec_dict = ec .asdict ()
798- # Check if CUDA is in the dependencies, if so add the 'gpu' Lmod property
799- if ('CUDA' in [dep [0 ] for dep in iter (ec_dict ['dependencies' ])]):
800- ec .log .info ("Injecting gpu as Lmod arch property and envvar with CUDA version" )
801- key = 'modluafooter'
802- value = 'add_property("arch","gpu")'
803- cuda_version = 0
804- for dep in iter (ec_dict ['dependencies' ]):
805- # Make CUDA a build dependency only (rpathing saves us from link errors)
806- if 'CUDA' in dep [0 ]:
807- cuda_version = dep [1 ]
808- ec_dict ['dependencies' ].remove (dep )
809- if dep not in ec_dict ['builddependencies' ]:
810- ec_dict ['builddependencies' ].append (dep )
811- value = '\n ' .join ([value , 'setenv("EESSICUDAVERSION","%s")' % cuda_version ])
812- if key in ec_dict :
813- if value not in ec_dict [key ]:
814- ec [key ] = '\n ' .join ([ec_dict [key ], value ])
880+ # Check if CUDA, cuDNN, you-name-it is in the dependencies, if so
881+ # - drop dependency to build dependency
882+ # - add 'gpu' Lmod property
883+ # - add envvar with package version
884+ pkg_names = ( "CUDA" , "cuDNN" )
885+ pkg_versions = { }
886+ add_gpu_property = ''
887+
888+ for pkg_name in pkg_names :
889+ # Check if pkg_name is in the dependencies, if so drop dependency to build
890+ # dependency and set variable for later adding the 'gpu' Lmod property
891+ # to '.remove' dependencies from ec_dict['dependencies'] we make a copy,
892+ # iterate over the copy and can then savely use '.remove' on the original
893+ # ec_dict['dependencies'].
894+ deps = ec_dict ['dependencies' ][:]
895+ if (pkg_name in [dep [0 ] for dep in deps ]):
896+ add_gpu_property = 'add_property("arch","gpu")'
897+ for dep in deps :
898+ if pkg_name == dep [0 ]:
899+ # make pkg_name a build dependency only (rpathing saves us from link errors)
900+ ec .log .info ("Dropping dependency on %s to build dependency" % pkg_name )
901+ ec_dict ['dependencies' ].remove (dep )
902+ if dep not in ec_dict ['builddependencies' ]:
903+ ec_dict ['builddependencies' ].append (dep )
904+ # take note of version for creating the modluafooter
905+ pkg_versions [pkg_name ] = dep [1 ]
906+ if add_gpu_property :
907+ ec .log .info ("Injecting gpu as Lmod arch property and envvars for dependencies with their version" )
908+ modluafooter = 'modluafooter'
909+ extra_mod_footer_lines = [add_gpu_property ]
910+ for pkg_name , version in pkg_versions .items ():
911+ envvar = "EESSI%sVERSION" % pkg_name .upper ()
912+ extra_mod_footer_lines .append ('setenv("%s","%s")' % (envvar , version ))
913+ # take into account that modluafooter may already be set
914+ if modluafooter in ec_dict :
915+ value = ec_dict [modluafooter ]
916+ for line in extra_mod_footer_lines :
917+ if not line in value :
918+ value = '\n ' .join ([value , line ])
919+ ec [modluafooter ] = value
815920 else :
816- ec [key ] = value
921+ ec [modluafooter ] = '\n ' .join (extra_mod_footer_lines )
922+
817923 return ec
818924
819925
@@ -873,4 +979,5 @@ def inject_gpu_property(ec):
873979
874980POST_POSTPROC_HOOKS = {
875981 'CUDA' : post_postproc_cuda ,
982+ 'cuDNN' : post_postproc_cudnn ,
876983}
0 commit comments