@@ -756,64 +756,170 @@ def post_postproc_cuda(self, *args, **kwargs):
756
756
if 'libcudart' not in allowlist :
757
757
raise EasyBuildError ("Did not find 'libcudart' in allowlist: %s" % allowlist )
758
758
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 )
789
762
else :
790
763
raise EasyBuildError ("CUDA-specific hook triggered for non-CUDA easyconfig?!" )
791
764
792
765
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
+
793
874
def inject_gpu_property (ec ):
794
875
"""
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
796
878
"""
797
879
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
815
920
else :
816
- ec [key ] = value
921
+ ec [modluafooter ] = '\n ' .join (extra_mod_footer_lines )
922
+
817
923
return ec
818
924
819
925
@@ -873,4 +979,5 @@ def inject_gpu_property(ec):
873
979
874
980
POST_POSTPROC_HOOKS = {
875
981
'CUDA' : post_postproc_cuda ,
982
+ 'cuDNN' : post_postproc_cudnn ,
876
983
}
0 commit comments